fix: 5 issues - leads 500, prepaid amount/invoice, install RESOLVED, dup tickets, confirm fail

This commit is contained in:
Nemo
2026-03-24 14:52:40 +08:00
parent 16cf1c0616
commit 882e416a40
3 changed files with 40 additions and 32 deletions

View File

@@ -151,25 +151,26 @@ export default function NewClientScreen() {
startDate: new Date().toISOString(),
});
// 3. Create installation ticket
const ticketRes = await api.post('/api/v1/tickets', {
clientId: client.id,
subject: `New Installation — ${firstName.trim()} ${lastName.trim()}`,
type: 'INSTALLATION',
priority: 'NORMAL',
// Pass subType so activation ticket knows to gate on payment for PREPAID
...(subType === 'PREPAID' ? { priority: 'HIGH' } : {}),
});
const ticket = ticketRes.data;
// 3. Fetch the auto-created installation ticket (API creates one on client creation)
let ticket: any = null;
try {
const ticketsRes = await api.get(`/api/v1/tickets?clientId=${client.id}&type=INSTALLATION&limit=1`);
const items = ticketsRes.data?.data ?? ticketsRes.data ?? [];
ticket = items[0] ?? null;
} catch {}
Alert.alert(
'Client Onboarded! 🎉',
`${firstName} ${lastName} has been registered.\n\nAn installation ticket has been created.`,
[{
text: 'View Ticket',
text: ticket ? 'View Ticket' : 'View Client',
onPress: () => {
router.replace('/(app)/clients');
setTimeout(() => router.push(`/(app)/tasks/${ticket.id}`), 300);
if (ticket) {
router.replace('/(app)/clients');
setTimeout(() => router.push(`/(app)/tasks/${ticket.id}`), 300);
} else {
router.replace('/(app)/clients');
}
},
}, {
text: 'Done',

View File

@@ -72,14 +72,13 @@ export default function LeadDetailScreen() {
});
const client = clientRes.data;
// Create installation ticket
const ticketRes = await api.post('/api/v1/tickets', {
clientId: client.id,
subject: `New Installation — ${lead.firstName} ${lead.lastName !== '—' ? lead.lastName : ''}`.trim(),
type: 'INSTALLATION',
priority: 'NORMAL',
});
const ticket = ticketRes.data;
// Fetch the auto-created installation ticket (API creates one on client creation)
let ticket: any = null;
try {
const tRes = await api.get(`/api/v1/tickets?clientId=${client.id}&type=INSTALLATION&limit=1`);
const items = tRes.data?.data ?? tRes.data ?? [];
ticket = items[0] ?? null;
} catch {}
// Mark lead as CONVERTED
await api.patch(`/api/v1/leads/${id}`, {

View File

@@ -147,13 +147,12 @@ export default function TicketDetailScreen() {
: `Installation confirmed. Location recorded: ${coordStr}`;
await api.post(`/api/v1/tickets/${id}/messages`, { body: note }).catch(() => {});
// 4. Create follow-up activation ticket
// 4. Create follow-up activation ticket (non-fatal if fails for role reasons)
await api.post('/api/v1/tickets', {
clientId: ticket?.clientId,
subject: `Account Activation — ${ticket?.client?.firstName ?? ''} ${ticket?.client?.lastName ?? ''}`.trim(),
type: 'BILLING',
priority: 'NORMAL',
description: `Follow-up after installation confirmed. Please activate the client account and generate the first invoice.\n\nInstallation ref: ${id}\nLocation: ${coordStr}`,
clientId: ticket?.clientId,
subject: `Account Activation — ${ticket?.client?.firstName ?? ''} ${ticket?.client?.lastName ?? ''}`.trim(),
type: 'BILLING',
priority: 'NORMAL',
}).catch(() => {});
setInstNotes('');
@@ -222,17 +221,26 @@ export default function TicketDetailScreen() {
const planPrice = Number(sub?.monthlyPrice ?? 0);
const submitPrepaidPayment = async () => {
const amt = parseFloat(prepaidPayAmount);
const effectiveAmount = prepaidPayAmount || String(planPrice);
const amt = parseFloat(effectiveAmount);
if (!amt || amt <= 0) { Alert.alert('Invalid Amount', 'Enter a valid amount.'); return; }
setPrepaidPaying(true);
try {
// 1. Record payment
// 1. Generate first invoice for client
let invoiceId: string | undefined;
try {
const invRes = await api.post(`/api/v1/invoices/generate/${ticket.clientId}`);
invoiceId = invRes.data?.id;
} catch {}
// 2. Record payment (link to invoice if available)
await api.post('/api/v1/payments', {
clientId: ticket.clientId,
amount: amt,
channel: prepaidPayMethod,
paymentDate: new Date().toISOString(),
notes: 'First prepaid payment — account activation',
...(invoiceId ? { invoiceId } : {}),
});
// 2. Activate subscription
if (sub?.id) {
@@ -366,11 +374,11 @@ export default function TicketDetailScreen() {
{/* Amount */}
<Text style={{ fontSize: 13, fontWeight: '600', color: '#92400E', marginBottom: 6 }}>Amount</Text>
<TextInput
value={prepaidPayAmount || String(planPrice)}
value={prepaidPayAmount}
onChangeText={setPrepaidPayAmount}
keyboardType="decimal-pad"
style={{ borderWidth: 1.5, borderColor: '#FDE68A', borderRadius: 10, paddingHorizontal: 14, paddingVertical: 12, fontSize: 20, fontWeight: '700', color: '#92400E', marginBottom: 12, backgroundColor: '#fff' }}
placeholder={String(planPrice)}
placeholder={planPrice ? String(planPrice) : '0.00'}
/>
{/* Method */}
@@ -388,7 +396,7 @@ export default function TicketDetailScreen() {
</View>
<SlideToConfirm
label={`Slide to collect ₱${parseFloat(prepaidPayAmount || String(planPrice)).toLocaleString()} & activate`}
label={`Slide to collect ₱${parseFloat(prepaidPayAmount || String(planPrice) || '0').toLocaleString()} & activate`}
color="#D97706"
onConfirm={submitPrepaidPayment}
disabled={prepaidPaying}