fix: 5 issues - leads 500, prepaid amount/invoice, install RESOLVED, dup tickets, confirm fail
This commit is contained in:
@@ -151,25 +151,26 @@ export default function NewClientScreen() {
|
|||||||
startDate: new Date().toISOString(),
|
startDate: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// 3. Create installation ticket
|
// 3. Fetch the auto-created installation ticket (API creates one on client creation)
|
||||||
const ticketRes = await api.post('/api/v1/tickets', {
|
let ticket: any = null;
|
||||||
clientId: client.id,
|
try {
|
||||||
subject: `New Installation — ${firstName.trim()} ${lastName.trim()}`,
|
const ticketsRes = await api.get(`/api/v1/tickets?clientId=${client.id}&type=INSTALLATION&limit=1`);
|
||||||
type: 'INSTALLATION',
|
const items = ticketsRes.data?.data ?? ticketsRes.data ?? [];
|
||||||
priority: 'NORMAL',
|
ticket = items[0] ?? null;
|
||||||
// Pass subType so activation ticket knows to gate on payment for PREPAID
|
} catch {}
|
||||||
...(subType === 'PREPAID' ? { priority: 'HIGH' } : {}),
|
|
||||||
});
|
|
||||||
const ticket = ticketRes.data;
|
|
||||||
|
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
'Client Onboarded! 🎉',
|
'Client Onboarded! 🎉',
|
||||||
`${firstName} ${lastName} has been registered.\n\nAn installation ticket has been created.`,
|
`${firstName} ${lastName} has been registered.\n\nAn installation ticket has been created.`,
|
||||||
[{
|
[{
|
||||||
text: 'View Ticket',
|
text: ticket ? 'View Ticket' : 'View Client',
|
||||||
onPress: () => {
|
onPress: () => {
|
||||||
|
if (ticket) {
|
||||||
router.replace('/(app)/clients');
|
router.replace('/(app)/clients');
|
||||||
setTimeout(() => router.push(`/(app)/tasks/${ticket.id}`), 300);
|
setTimeout(() => router.push(`/(app)/tasks/${ticket.id}`), 300);
|
||||||
|
} else {
|
||||||
|
router.replace('/(app)/clients');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
text: 'Done',
|
text: 'Done',
|
||||||
|
|||||||
@@ -72,14 +72,13 @@ export default function LeadDetailScreen() {
|
|||||||
});
|
});
|
||||||
const client = clientRes.data;
|
const client = clientRes.data;
|
||||||
|
|
||||||
// Create installation ticket
|
// Fetch the auto-created installation ticket (API creates one on client creation)
|
||||||
const ticketRes = await api.post('/api/v1/tickets', {
|
let ticket: any = null;
|
||||||
clientId: client.id,
|
try {
|
||||||
subject: `New Installation — ${lead.firstName} ${lead.lastName !== '—' ? lead.lastName : ''}`.trim(),
|
const tRes = await api.get(`/api/v1/tickets?clientId=${client.id}&type=INSTALLATION&limit=1`);
|
||||||
type: 'INSTALLATION',
|
const items = tRes.data?.data ?? tRes.data ?? [];
|
||||||
priority: 'NORMAL',
|
ticket = items[0] ?? null;
|
||||||
});
|
} catch {}
|
||||||
const ticket = ticketRes.data;
|
|
||||||
|
|
||||||
// Mark lead as CONVERTED
|
// Mark lead as CONVERTED
|
||||||
await api.patch(`/api/v1/leads/${id}`, {
|
await api.patch(`/api/v1/leads/${id}`, {
|
||||||
|
|||||||
@@ -147,13 +147,12 @@ export default function TicketDetailScreen() {
|
|||||||
: `Installation confirmed. Location recorded: ${coordStr}`;
|
: `Installation confirmed. Location recorded: ${coordStr}`;
|
||||||
await api.post(`/api/v1/tickets/${id}/messages`, { body: note }).catch(() => {});
|
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', {
|
await api.post('/api/v1/tickets', {
|
||||||
clientId: ticket?.clientId,
|
clientId: ticket?.clientId,
|
||||||
subject: `Account Activation — ${ticket?.client?.firstName ?? ''} ${ticket?.client?.lastName ?? ''}`.trim(),
|
subject: `Account Activation — ${ticket?.client?.firstName ?? ''} ${ticket?.client?.lastName ?? ''}`.trim(),
|
||||||
type: 'BILLING',
|
type: 'BILLING',
|
||||||
priority: 'NORMAL',
|
priority: 'NORMAL',
|
||||||
description: `Follow-up after installation confirmed. Please activate the client account and generate the first invoice.\n\nInstallation ref: ${id}\nLocation: ${coordStr}`,
|
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
|
|
||||||
setInstNotes('');
|
setInstNotes('');
|
||||||
@@ -222,17 +221,26 @@ export default function TicketDetailScreen() {
|
|||||||
const planPrice = Number(sub?.monthlyPrice ?? 0);
|
const planPrice = Number(sub?.monthlyPrice ?? 0);
|
||||||
|
|
||||||
const submitPrepaidPayment = async () => {
|
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; }
|
if (!amt || amt <= 0) { Alert.alert('Invalid Amount', 'Enter a valid amount.'); return; }
|
||||||
setPrepaidPaying(true);
|
setPrepaidPaying(true);
|
||||||
try {
|
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', {
|
await api.post('/api/v1/payments', {
|
||||||
clientId: ticket.clientId,
|
clientId: ticket.clientId,
|
||||||
amount: amt,
|
amount: amt,
|
||||||
channel: prepaidPayMethod,
|
channel: prepaidPayMethod,
|
||||||
paymentDate: new Date().toISOString(),
|
paymentDate: new Date().toISOString(),
|
||||||
notes: 'First prepaid payment — account activation',
|
notes: 'First prepaid payment — account activation',
|
||||||
|
...(invoiceId ? { invoiceId } : {}),
|
||||||
});
|
});
|
||||||
// 2. Activate subscription
|
// 2. Activate subscription
|
||||||
if (sub?.id) {
|
if (sub?.id) {
|
||||||
@@ -366,11 +374,11 @@ export default function TicketDetailScreen() {
|
|||||||
{/* Amount */}
|
{/* Amount */}
|
||||||
<Text style={{ fontSize: 13, fontWeight: '600', color: '#92400E', marginBottom: 6 }}>Amount</Text>
|
<Text style={{ fontSize: 13, fontWeight: '600', color: '#92400E', marginBottom: 6 }}>Amount</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
value={prepaidPayAmount || String(planPrice)}
|
value={prepaidPayAmount}
|
||||||
onChangeText={setPrepaidPayAmount}
|
onChangeText={setPrepaidPayAmount}
|
||||||
keyboardType="decimal-pad"
|
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' }}
|
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 */}
|
{/* Method */}
|
||||||
@@ -388,7 +396,7 @@ export default function TicketDetailScreen() {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
<SlideToConfirm
|
<SlideToConfirm
|
||||||
label={`Slide to collect ₱${parseFloat(prepaidPayAmount || String(planPrice)).toLocaleString()} & activate`}
|
label={`Slide to collect ₱${parseFloat(prepaidPayAmount || String(planPrice) || '0').toLocaleString()} & activate`}
|
||||||
color="#D97706"
|
color="#D97706"
|
||||||
onConfirm={submitPrepaidPayment}
|
onConfirm={submitPrepaidPayment}
|
||||||
disabled={prepaidPaying}
|
disabled={prepaidPaying}
|
||||||
|
|||||||
Reference in New Issue
Block a user