mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 12:18:11 +00:00
Production cleanups
This commit is contained in:
@@ -34,7 +34,7 @@ The Passenger Backoffice Application is a comprehensive management system for th
|
||||
- **Live Tracking**: Monitor trip status and real-time updates
|
||||
- **Security Monitoring**: Fraud detection and audit logging
|
||||
- **Comprehensive Analytics**: Revenue, occupancy, and performance reports
|
||||
- **🆕 Excess Baggage Management**: Handle boarding baggage charges with agent tools
|
||||
- **🆕 Excess Luggage Management**: Handle boarding baggage charges with agent tools
|
||||
- **🆕 Travel Packages**: Manage pilgrimage and group travel packages with tiered pricing
|
||||
- **🆕 System Health Monitoring**: Real-time API health checks and system status
|
||||
- **🆕 Advanced Fare Configuration**: Dynamic pricing with segment-based rules
|
||||
@@ -94,7 +94,7 @@ The application is organized into 8 main sections:
|
||||
├── System Config
|
||||
└── Settings
|
||||
└── Enhanced Features
|
||||
├── Excess Baggage
|
||||
├── Excess Luggage
|
||||
├── Travel Packages
|
||||
├── Package Inquiries
|
||||
├── Health Monitoring
|
||||
@@ -2862,7 +2862,7 @@ Action: Block user
|
||||
|
||||
### Version 1.0.0 (January 15, 2026)
|
||||
- **Complete Platform Release** - Full-featured passenger management system
|
||||
- **Excess Baggage Management** - Complete boarding baggage handling with agent tools and passenger self-pay options
|
||||
- **Excess Luggage Management** - Complete boarding baggage handling with agent tools and passenger self-pay options
|
||||
- **Travel Packages** - Pilgrimage and group travel packages with tiered pricing, capacity management, and inquiry handling
|
||||
- **System Health Monitoring** - Real-time API health checks with liveness, readiness, and performance metrics
|
||||
- **System Configuration** - Centralized config management with feature flags, rate limiting, and operational controls
|
||||
@@ -2920,7 +2920,7 @@ Action: Block user
|
||||
|
||||
## Enhanced Features
|
||||
|
||||
### Excess Baggage
|
||||
### Excess Luggage
|
||||
|
||||
**Purpose**: Manage excess baggage charges at boarding with agent tools and passenger self-pay
|
||||
**Access Level**: Agent, Supervisor, Admin
|
||||
@@ -2941,7 +2941,7 @@ Action: Block user
|
||||
└──────────────────────────────┘
|
||||
```
|
||||
|
||||
#### Excess Baggage Process
|
||||
#### Excess Luggage Process
|
||||
|
||||
1. **At Boarding**: Agent weighs passenger baggage
|
||||
2. **If Excess**: Agent creates charge in system
|
||||
@@ -2962,8 +2962,8 @@ Action: Block user
|
||||
|
||||
##### READ (List Charges)
|
||||
|
||||
1. **Access Excess Baggage Page**:
|
||||
- Click **Excess Baggage** in Enhanced Features section
|
||||
1. **Access Excess Luggage Page**:
|
||||
- Click **Excess Luggage** in Enhanced Features section
|
||||
- Shows all baggage charges
|
||||
|
||||
2. **Search & Filter**:
|
||||
@@ -3000,7 +3000,7 @@ Action: Block user
|
||||
|
||||
#### Agent Workflow
|
||||
|
||||
1. **Weigh Baggage**: Use station scales
|
||||
1. **Weigh Luggage**: Use station scales
|
||||
2. **Check Allowance**: Compare to passenger's seat class allowance
|
||||
3. **Create Charge**: If excess weight found
|
||||
4. **Offer Payment Options**:
|
||||
|
||||
@@ -311,41 +311,25 @@ export default function ClassesPage() {
|
||||
<p className="text-xs text-muted-foreground mt-1">Per-km distance-based fare rate</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
|
||||
<div>
|
||||
<label className="label">Premium Fee (ETB)</label>
|
||||
<input
|
||||
type="number"
|
||||
name="premiumMinor"
|
||||
className="input"
|
||||
defaultValue={editingClass?.premiumMinor ? (editingClass.premiumMinor / 100).toFixed(2) : '0.00'}
|
||||
min="0"
|
||||
step="0.01"
|
||||
placeholder="e.g., 50.00"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">Flat fee per passenger (e.g., lounge access, extra legroom)</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="label">Insurance Fee (ETB)</label>
|
||||
<input
|
||||
type="number"
|
||||
name="insuranceFeeMinor"
|
||||
className="input"
|
||||
defaultValue={editingClass?.insuranceFeeMinor ? (editingClass.insuranceFeeMinor / 100).toFixed(2) : '0.00'}
|
||||
min="0"
|
||||
step="0.01"
|
||||
placeholder="e.g., 25.00"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">Flat fee per passenger (e.g., travel insurance)</p>
|
||||
</div>
|
||||
<input type="hidden" name="premiumMinor" value="0" />
|
||||
<div className="mt-4">
|
||||
<label className="label">Insurance Fee (ETB)</label>
|
||||
<input
|
||||
type="number"
|
||||
name="insuranceFeeMinor"
|
||||
className="input"
|
||||
defaultValue={editingClass?.insuranceFeeMinor ? (editingClass.insuranceFeeMinor / 100).toFixed(2) : '0.00'}
|
||||
min="0"
|
||||
step="0.01"
|
||||
placeholder="e.g., 25.00"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">Flat fee per passenger (e.g., travel insurance)</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 p-3 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded text-sm text-blue-800 dark:text-blue-200">
|
||||
<p className="font-medium mb-1">Total Fare Calculation:</p>
|
||||
<p>Total = (Base Fare × Distance) + Premium + Insurance</p>
|
||||
<p className="mt-2 text-xs">• Premium applies per passenger</p>
|
||||
<p className="text-xs">• Insurance applies per passenger</p>
|
||||
<p>Total = (Base Fare × Distance) + Insurance</p>
|
||||
<p className="mt-2 text-xs">• Insurance applies per passenger</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -220,13 +220,6 @@ export default function CurrenciesPage() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="card bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 text-sm text-blue-800 dark:text-blue-300 space-y-1">
|
||||
<p className="font-semibold text-blue-900 dark:text-blue-200 mb-2">How it works</p>
|
||||
<p>• ETB is the transaction currency — all fares are stored in ETB minor units (1 ETB = 100 minor)</p>
|
||||
<p>• DJF and USD rates are used to display prices to passengers in their preferred currency</p>
|
||||
<p>• Rates apply globally; changes take effect immediately on the next booking or fare quote</p>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
isOpen={showAddModal}
|
||||
onClose={() => { setShowAddModal(false); setError(null); }}
|
||||
|
||||
@@ -82,7 +82,7 @@ export default function OperationsSection() {
|
||||
|
||||
{/* EXCESS BAGGAGE */}
|
||||
<div id="excess-baggage" className="border-t pt-8">
|
||||
<h2 className="text-2xl font-bold text-slate-900 dark:text-white mb-2">📦 Luggage (Excess Baggage)</h2>
|
||||
<h2 className="text-2xl font-bold text-slate-900 dark:text-white mb-2">📦 Luggage (Excess Luggage)</h2>
|
||||
<p className={li}>Handle excess baggage charges at boarding — passenger self-pay or agent cash collection. Access level: Agent, Supervisor, Admin.</p>
|
||||
<div className="mt-3 flex flex-wrap gap-2 text-xs">
|
||||
{['PENDING','PAID','CASH_COLLECTED','EXPIRED','WAIVED'].map(s => (
|
||||
@@ -91,7 +91,7 @@ export default function OperationsSection() {
|
||||
</div>
|
||||
</div>
|
||||
<div id="excess-baggage-how" className="border-t pt-8">
|
||||
<h3 className="text-xl font-bold text-slate-900 dark:text-white mb-4">📦 How-To: Handle Excess Baggage</h3>
|
||||
<h3 className="text-xl font-bold text-slate-900 dark:text-white mb-4">📦 How-To: Handle Excess Luggage</h3>
|
||||
<div className="space-y-3">
|
||||
<HowToStep number={1} title="Find Charges">
|
||||
<ol className={ol}><li>Click <strong>Luggage</strong> in Operations</li><li>Search by booking reference; filter by status or date</li></ol>
|
||||
|
||||
@@ -609,7 +609,7 @@ export default function PricingPage() {
|
||||
className={`px-4 py-2 font-medium border-b-2 transition-colors ${tab === 'baggage' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground'
|
||||
}`}
|
||||
>
|
||||
Excess Baggage Rates
|
||||
Excess Luggage Rates
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -765,24 +765,6 @@ export default function PricingPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800">
|
||||
<h3 className="font-semibold text-blue-900 dark:text-blue-200 mb-3">Pricing Structure</h3>
|
||||
<ul className="text-sm text-blue-800 dark:text-blue-300 space-y-2">
|
||||
<li>
|
||||
• <strong>Segment Fares:</strong> Set fares for specific stop-to-stop segments (e.g., Addis → Dire Dawa)
|
||||
</li>
|
||||
<li>
|
||||
• <strong>Schedule Fares:</strong> Set custom pricing for each schedule by seat class and passenger type
|
||||
</li>
|
||||
<li>
|
||||
• <strong>Passenger Type:</strong> ADULT (5+ years) or CHILD (<5) — first child travels free, subsequent children pay full fare
|
||||
</li>
|
||||
<li>
|
||||
• <strong>Nationality-based:</strong> Override fares for specific nationalities (Ethiopian, Djiboutian, Other)
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Delete Confirmation */}
|
||||
<ConfirmDialog
|
||||
isOpen={deleteConfirm.isOpen}
|
||||
@@ -1113,7 +1095,7 @@ export default function PricingPage() {
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
{/* Baggage Allowance Modal */}
|
||||
{/* Luggage Allowance Modal */}
|
||||
<Modal isOpen={baggageModal} onClose={() => { setBaggageModal(false); setEditingAllowance(null); setBaggageError(null); }} title={editingAllowance ? 'Edit Allowance Rule' : 'Add Allowance Rule'} size="md">
|
||||
<div className="space-y-4">
|
||||
{baggageError && <div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 p-3 rounded-lg text-sm text-red-800 dark:text-red-200">{baggageError}</div>}
|
||||
|
||||
@@ -52,15 +52,28 @@ export default function SeatsPage() {
|
||||
queryFn: async () => {
|
||||
if (!selectedRoute) return null;
|
||||
const template: any[] = await routeCoachTemplatesApi.get(selectedRoute);
|
||||
if (!template?.length) return [];
|
||||
const fullCoaches = await Promise.all(
|
||||
template.map((entry: any) => fleetApi.getCoach(entry.coachId ?? entry.coach?.id))
|
||||
);
|
||||
return fullCoaches.map((coach: any, i: number) => ({
|
||||
if (template?.length) {
|
||||
const fullCoaches = await Promise.all(
|
||||
template.map((entry: any) => fleetApi.getCoach(entry.coachId ?? entry.coach?.id))
|
||||
);
|
||||
return fullCoaches.map((coach: any, i: number) => ({
|
||||
...coach,
|
||||
coachNumber: coach.number,
|
||||
positionNumber: template[i].positionNumber,
|
||||
seatArrangement: coach.arrangement,
|
||||
}));
|
||||
}
|
||||
// No template — fetch coaches from the most recent schedule for this route
|
||||
const schedules: any = await schedulesApi.getAll({ routeId: selectedRoute });
|
||||
const scheduleList: any[] = schedules?.items || schedules?.data || (Array.isArray(schedules) ? schedules : []);
|
||||
if (!scheduleList.length) return [];
|
||||
const latestSchedule = scheduleList[scheduleList.length - 1];
|
||||
const seatMap: any = await seatsApi.getSeatMap(latestSchedule.id);
|
||||
return (seatMap?.coaches || []).map((coach: any, i: number) => ({
|
||||
...coach,
|
||||
coachNumber: coach.number,
|
||||
positionNumber: template[i].positionNumber,
|
||||
seatArrangement: coach.arrangement,
|
||||
coachNumber: coach.number ?? coach.coachNumber,
|
||||
positionNumber: coach.positionNumber ?? i + 1,
|
||||
seatArrangement: coach.arrangement ?? coach.seatArrangement,
|
||||
}));
|
||||
},
|
||||
enabled: !!selectedRoute,
|
||||
|
||||
@@ -237,16 +237,6 @@ export default function TariffRatesPage() {
|
||||
</ActionButton>
|
||||
</div>
|
||||
|
||||
{/* Tariff reference card */}
|
||||
<div className="card bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800">
|
||||
<h3 className="font-semibold text-blue-900 dark:text-blue-200 mb-2">Official Tariff Formula</h3>
|
||||
<p className="text-sm text-blue-800 dark:text-blue-300 font-mono">
|
||||
Fare = KM × rate × 1.02 × ExchangeRate
|
||||
</p>
|
||||
<p className="text-xs text-blue-700 dark:text-blue-400 mt-1">
|
||||
Rate is stored as <strong>baseFareMinor = tariff_decimal × 100,000</strong> (e.g. 0.03 → 3000). The ×1.02 insurance coefficient is applied automatically by the fare engine.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="card">
|
||||
<div className="relative mb-4">
|
||||
|
||||
@@ -884,11 +884,11 @@ export default function TicketsPage() {
|
||||
})()}
|
||||
</Modal>
|
||||
|
||||
{/* Excess Baggage Modal */}
|
||||
{/* Excess Luggage Modal */}
|
||||
<Modal
|
||||
isOpen={excessModalOpen}
|
||||
onClose={() => { setExcessModalOpen(false); setExcessTicket(null); setExcessResult(null); }}
|
||||
title="Log Excess Baggage"
|
||||
title="Log Excess Luggage"
|
||||
size="sm"
|
||||
>
|
||||
{excessResult ? (
|
||||
|
||||
@@ -444,7 +444,7 @@ export const packageInquiriesApi = {
|
||||
remove: (id: string) => apiClient.delete(`/packages/inquiries/${id}`),
|
||||
};
|
||||
|
||||
// Excess Baggage API
|
||||
// Excess Luggage API
|
||||
export const excessBaggageApi = {
|
||||
logCharge: (data: any) => apiClient.post<any>('/agents/excess-baggage', data),
|
||||
getCharge: (id: string) => apiClient.get<any>(`/agents/excess-baggage/${id}`),
|
||||
|
||||
Reference in New Issue
Block a user