Update form report summary, gắn api
This commit is contained in:
parent
9cd3defc1d
commit
884c113a03
|
|
@ -11,6 +11,7 @@ import {
|
||||||
detectConfigRamByModel,
|
detectConfigRamByModel,
|
||||||
detectScenarioByModel,
|
detectScenarioByModel,
|
||||||
escapeHtml,
|
escapeHtml,
|
||||||
|
getIncomingInfoBySN,
|
||||||
isRamSufficient,
|
isRamSufficient,
|
||||||
isValidJson,
|
isValidJson,
|
||||||
LogStreamBuffer,
|
LogStreamBuffer,
|
||||||
|
|
@ -1933,6 +1934,14 @@ Ports Missing/Down: ${missing.length}\n\n`
|
||||||
const totalPoE = testedPoE.length + missingPoE.length
|
const totalPoE = testedPoE.length + missingPoE.length
|
||||||
const totalSFP = testedSFP.length + missingSFP.length
|
const totalSFP = testedSFP.length + missingSFP.length
|
||||||
|
|
||||||
|
const dataIncomingBySN = await getIncomingInfoBySN(config?.inventory?.sn)
|
||||||
|
const serialInfo = dataIncomingBySN?.serialNumbersInfo?.find(
|
||||||
|
(s: any) => s.serialNumberA === config?.inventory?.sn
|
||||||
|
)
|
||||||
|
const listImages = dataIncomingBySN?.packagePo?.listFiles?.filter(
|
||||||
|
(s: any) => s.kind === 'other'
|
||||||
|
)
|
||||||
|
|
||||||
const showVersion = config?.data?.find(
|
const showVersion = config?.data?.find(
|
||||||
(d) => d.command?.trim()?.includes('show ver') || d.command?.trim()?.includes('sh ver')
|
(d) => d.command?.trim()?.includes('show ver') || d.command?.trim()?.includes('sh ver')
|
||||||
)
|
)
|
||||||
|
|
@ -2077,13 +2086,20 @@ Ports Missing/Down: ${missing.length}\n\n`
|
||||||
|
|
||||||
// Physical Check checklist
|
// Physical Check checklist
|
||||||
const checklistItems: Array<[string, string]> = [
|
const checklistItems: Array<[string, string]> = [
|
||||||
['ok', 'Packaging intact — no damage to box or foam'],
|
[
|
||||||
['ok', 'No physical damage — chassis, fans, PSU'],
|
serialInfo?.optionVisualInspection?.statusChassis ? 'ok' : 'warn',
|
||||||
['ok', `S/N matches label — ${productSN} verified`],
|
serialInfo?.optionVisualInspection?.statusChassis
|
||||||
['ok', 'All 48 GigE + 4 SFP+ ports clean'],
|
? 'Overall hardware status is normal'
|
||||||
['ok', 'Accessories — power cable, rack ears, console cable'],
|
: 'Hardware issue detected on chassis/system',
|
||||||
['warn', 'Minor scratch on top chassis (2cm) — cosmetic only'],
|
],
|
||||||
|
[
|
||||||
|
serialInfo?.optionVisualInspection?.statusPortsPOE ? 'ok' : 'warn',
|
||||||
|
serialInfo?.optionVisualInspection?.statusPortsPOE
|
||||||
|
? 'All ports and PoE functions are operating normally'
|
||||||
|
: 'Port or PoE issue detected',
|
||||||
|
],
|
||||||
]
|
]
|
||||||
|
|
||||||
const checklistRowsHtml = checklistItems
|
const checklistRowsHtml = checklistItems
|
||||||
.map(([k, t]) =>
|
.map(([k, t]) =>
|
||||||
k === 'ok'
|
k === 'ok'
|
||||||
|
|
@ -2096,6 +2112,30 @@ Ports Missing/Down: ${missing.length}\n\n`
|
||||||
const photoCellHtml = (label: string) =>
|
const photoCellHtml = (label: string) =>
|
||||||
`<table cellpadding="0" cellspacing="0" border="0" width="100%" style="border:1px dashed #e5e7eb;border-radius:6px;background:#f9fafb;border-collapse:separate;"><tr><td align="center" style="padding:18px 0;color:#9ca3af;"><svg viewBox="0 0 40 40" width="22" height="22" fill="none" style="display:inline-block;color:#9ca3af;"><rect x="4" y="8" width="32" height="24" rx="3" stroke="currentColor" stroke-width="1.5"/><circle cx="14" cy="18" r="3" stroke="currentColor" stroke-width="1.5"/><path d="M4 28l8-6 6 4 8-8 10 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg><div style="font-size:9px;font-weight:600;margin-top:3px;">${label}</div></td></tr></table>`
|
`<table cellpadding="0" cellspacing="0" border="0" width="100%" style="border:1px dashed #e5e7eb;border-radius:6px;background:#f9fafb;border-collapse:separate;"><tr><td align="center" style="padding:18px 0;color:#9ca3af;"><svg viewBox="0 0 40 40" width="22" height="22" fill="none" style="display:inline-block;color:#9ca3af;"><rect x="4" y="8" width="32" height="24" rx="3" stroke="currentColor" stroke-width="1.5"/><circle cx="14" cy="18" r="3" stroke="currentColor" stroke-width="1.5"/><path d="M4 28l8-6 6 4 8-8 10 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg><div style="font-size:9px;font-weight:600;margin-top:3px;">${label}</div></td></tr></table>`
|
||||||
|
|
||||||
|
// Photo cell with actual image
|
||||||
|
const imageCellHtml = (url: string, label: string) =>
|
||||||
|
`<a href="${url}" target="_blank" style="display:block;text-decoration:none;"><table cellpadding="0" cellspacing="0" border="0" width="100%" style="border:1px solid #e5e7eb;border-radius:6px;background:#f9fafb;border-collapse:separate;overflow:hidden;height:120px;cursor:pointer;"><tr><td align="center" style="padding:0;background-size:cover;background-position:center;background-image:url('${url}');position:relative;"></td></tr></table></a>`
|
||||||
|
|
||||||
|
// Prepare image grid: get first 4 images from listImages if available
|
||||||
|
const imageList = listImages && Array.isArray(listImages) ? listImages.slice(0, 4) : []
|
||||||
|
const imageLabels = ['Front', 'Rear', 'S/N Label', 'Package']
|
||||||
|
const getPhotoCell = (idx: number) => {
|
||||||
|
const image = imageList[idx]
|
||||||
|
const label = imageLabels[idx]
|
||||||
|
return image && image.url
|
||||||
|
? imageCellHtml(process.env.ERP_URL + image.url, label)
|
||||||
|
: photoCellHtml(label)
|
||||||
|
}
|
||||||
|
const photoGridRowsHtml = `
|
||||||
|
<tr>
|
||||||
|
<td width="50%" style="padding:0 3px 6px 0;">${getPhotoCell(0)}</td>
|
||||||
|
<td width="50%" style="padding:0 0 6px 3px;">${getPhotoCell(1)}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="50%" style="padding:0 3px 0 0;">${getPhotoCell(2)}</td>
|
||||||
|
<td width="50%" style="padding:0 0 0 3px;">${getPhotoCell(3)}</td>
|
||||||
|
</tr>`
|
||||||
|
|
||||||
// Helper function to highlight SNs from listInventory in outputTestLog
|
// Helper function to highlight SNs from listInventory in outputTestLog
|
||||||
const highlightSnInConsoleOutput = (text: string, listInventory: any[] | undefined) => {
|
const highlightSnInConsoleOutput = (text: string, listInventory: any[] | undefined) => {
|
||||||
if (!text || !listInventory || listInventory.length === 0) {
|
if (!text || !listInventory || listInventory.length === 0) {
|
||||||
|
|
@ -2184,8 +2224,8 @@ Ports Missing/Down: ${missing.length}\n\n`
|
||||||
<tr><td style="font-size:10px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:.4px;padding:3px 8px 3px 0;white-space:nowrap;vertical-align:top;">P/N</td><td style="padding:3px 0;font-weight:500;vertical-align:top;font-size:12px;"><strong>${productPN}</strong></td></tr>
|
<tr><td style="font-size:10px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:.4px;padding:3px 8px 3px 0;white-space:nowrap;vertical-align:top;">P/N</td><td style="padding:3px 0;font-weight:500;vertical-align:top;font-size:12px;"><strong>${productPN}</strong></td></tr>
|
||||||
<tr><td style="font-size:10px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:.4px;padding:3px 8px 3px 0;white-space:nowrap;vertical-align:top;">S/N</td><td style="padding:3px 0;font-weight:500;vertical-align:top;font-size:12px;"><strong>${productSN}</strong></td></tr>
|
<tr><td style="font-size:10px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:.4px;padding:3px 8px 3px 0;white-space:nowrap;vertical-align:top;">S/N</td><td style="padding:3px 0;font-weight:500;vertical-align:top;font-size:12px;"><strong>${productSN}</strong></td></tr>
|
||||||
<tr><td style="font-size:10px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:.4px;padding:3px 8px 3px 0;white-space:nowrap;vertical-align:top;">MAC</td><td style="padding:3px 0;font-weight:500;vertical-align:top;font-size:12px;">${macAddress || '-'}</td></tr>
|
<tr><td style="font-size:10px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:.4px;padding:3px 8px 3px 0;white-space:nowrap;vertical-align:top;">MAC</td><td style="padding:3px 0;font-weight:500;vertical-align:top;font-size:12px;">${macAddress || '-'}</td></tr>
|
||||||
<tr><td style="font-size:10px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:.4px;padding:3px 8px 3px 0;white-space:nowrap;vertical-align:top;">Cond.</td><td style="padding:3px 0;font-weight:500;vertical-align:top;font-size:12px;">${'-'}</td></tr>
|
<tr><td style="font-size:10px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:.4px;padding:3px 8px 3px 0;white-space:nowrap;vertical-align:top;">Cond.</td><td style="padding:3px 0;font-weight:500;vertical-align:top;font-size:12px;">${serialInfo?.condition || '-'}</td></tr>
|
||||||
<tr><td style="font-size:10px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:.4px;padding:3px 8px 3px 0;white-space:nowrap;vertical-align:top;">Supplier</td><td style="padding:3px 0;font-weight:500;vertical-align:top;font-size:12px;">${'-'}</td></tr>
|
<tr><td style="font-size:10px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:.4px;padding:3px 8px 3px 0;white-space:nowrap;vertical-align:top;">Supplier</td><td style="padding:3px 0;font-weight:500;vertical-align:top;font-size:12px;">${serialInfo?.supplier?.name || '-'}</td></tr>
|
||||||
</table>
|
</table>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
@ -2240,9 +2280,10 @@ Ports Missing/Down: ${missing.length}\n\n`
|
||||||
<div style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.8px;color:#9ca3af;padding-bottom:7px;border-bottom:1px solid #f0f1f3;margin-bottom:10px;">Receiving & Inspection Notes</div>
|
<div style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.8px;color:#9ca3af;padding-bottom:7px;border-bottom:1px solid #f0f1f3;margin-bottom:10px;">Receiving & Inspection Notes</div>
|
||||||
<div style="padding:10px 14px;background:#fffbeb;border-left:3px solid #f59e0b;border-radius:0 6px 6px 0;font-size:12px;color:#92400e;margin-bottom:8px;">
|
<div style="padding:10px 14px;background:#fffbeb;border-left:3px solid #f59e0b;border-radius:0 6px 6px 0;font-size:12px;color:#92400e;margin-bottom:8px;">
|
||||||
<div style="font-weight:700;margin-bottom:4px;font-size:11px;">⚠ Warning from Warehouse</div>
|
<div style="font-weight:700;margin-bottom:4px;font-size:11px;">⚠ Warning from Warehouse</div>
|
||||||
<p style="display:none; margin:0;">Box arrived with slight indentation on the left corner. Internal foam was still intact. Serial number on box was partially obscured by shipping label but verified upon unboxing.</p>
|
<p style="margin:0;">${dataIncomingBySN?.packagePo?.notes || ''}</p>
|
||||||
<p style="padding:6px 0;border-bottom:1px dashed #f0f1f3;font-weight:600;color:#5f6978; font-size:11px; font-style:italic;">Not Available</p>
|
<p style="margin:0;">${serialInfo?.notes || ''}</p>
|
||||||
</div>
|
${!dataIncomingBySN?.packagePo?.notes && !serialInfo?.notes ? '<p style="margin:0;">No notes available.</p>' : ''}
|
||||||
|
</div>
|
||||||
<div style="padding:10px 14px;background:#f9fafb;border-left:3px solid #e5e7eb;border-radius:0 6px 6px 0;font-size:12px;color:#5f6978;">
|
<div style="padding:10px 14px;background:#f9fafb;border-left:3px solid #e5e7eb;border-radius:0 6px 6px 0;font-size:12px;color:#5f6978;">
|
||||||
<div style="font-weight:700;margin-bottom:4px;font-size:11px;">Accessory Checklist</div>
|
<div style="font-weight:700;margin-bottom:4px;font-size:11px;">Accessory Checklist</div>
|
||||||
<table cellpadding="0" cellspacing="0" border="0" style="margin-top:6px;">
|
<table cellpadding="0" cellspacing="0" border="0" style="margin-top:6px;">
|
||||||
|
|
@ -2287,10 +2328,10 @@ Ports Missing/Down: ${missing.length}\n\n`
|
||||||
Received
|
Received
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size:11px;font-weight:600;color:#1a1d23;font-style:italic;">
|
<div style="font-size:11px;font-weight:600;color:#1a1d23;font-style:italic;">
|
||||||
Not Available
|
${dataIncomingBySN?.packagePo?.receivedBy?.fullName || 'Unknown'}
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-top:4px;font-size:10px;color:#9ca3af;">
|
<div style="margin-top:4px;font-size:10px;color:#9ca3af;">
|
||||||
${momentTZ().tz(timeZone).format('DD MMM')}
|
${dataIncomingBySN?.packagePo?.receivedDate ? momentTZ(dataIncomingBySN?.packagePo?.receivedDate).tz(timeZone).format('DD MMM, HH:mm') : ''}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<!-- Step 2 -->
|
<!-- Step 2 -->
|
||||||
|
|
@ -2303,10 +2344,10 @@ Ports Missing/Down: ${missing.length}\n\n`
|
||||||
Visual Check
|
Visual Check
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size:11px;font-weight:600;color:#1a1d23;font-style:italic;">
|
<div style="font-size:11px;font-weight:600;color:#1a1d23;font-style:italic;">
|
||||||
Not Available
|
${dataIncomingBySN?.packagePo?.receivedBy?.fullName || 'Unknown'}
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-top:4px;font-size:10px;color:#9ca3af;">
|
<div style="margin-top:4px;font-size:10px;color:#9ca3af;">
|
||||||
${momentTZ().tz(timeZone).format('DD MMM')}
|
${dataIncomingBySN?.packagePo?.receivedDate ? momentTZ(dataIncomingBySN?.packagePo?.receivedDate).tz(timeZone).format('DD MMM, HH:mm') : ''}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<!-- Step 3 -->
|
<!-- Step 3 -->
|
||||||
|
|
@ -2318,7 +2359,7 @@ Ports Missing/Down: ${missing.length}\n\n`
|
||||||
Software Test
|
Software Test
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size:11px;font-weight:600;color:#1a1d23;">
|
<div style="font-size:11px;font-weight:600;color:#1a1d23;">
|
||||||
${this?.userTest?.dpelp?.name || ''}
|
${this?.userTest?.dpelp?.name || 'Unknown'}
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-top:4px;font-size:10px;color:#9ca3af;">
|
<div style="margin-top:4px;font-size:10px;color:#9ca3af;">
|
||||||
${momentTZ(this?.userTest?.dpelp?.time).tz(timeZone).format('DD MMM, HH:mm')}
|
${momentTZ(this?.userTest?.dpelp?.time).tz(timeZone).format('DD MMM, HH:mm')}
|
||||||
|
|
@ -2351,29 +2392,19 @@ Ports Missing/Down: ${missing.length}\n\n`
|
||||||
<svg viewBox="0 0 20 20" width="17" height="17" fill="none" style="vertical-align:middle;color:#166534;"><rect x="2" y="2" width="16" height="16" rx="3" stroke="currentColor" stroke-width="1.5"/><path d="M7 10h6M10 7v6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
|
<svg viewBox="0 0 20 20" width="17" height="17" fill="none" style="vertical-align:middle;color:#166534;"><rect x="2" y="2" width="16" height="16" rx="3" stroke="currentColor" stroke-width="1.5"/><path d="M7 10h6M10 7v6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
|
||||||
<span style="vertical-align:middle;margin-left:8px;">Visual Check</span>
|
<span style="vertical-align:middle;margin-left:8px;">Visual Check</span>
|
||||||
</td>
|
</td>
|
||||||
<td align="right" style="display:none;padding:7px 12px;color:#166534;font-size:11px;font-weight:500;opacity:.65;">${this?.userTest?.physical?.name || ''} · ${momentTZ(this?.userTest?.physical?.time).tz(timeZone).format('DD MMM, HH:mm')}</td>
|
<td align="right" style="padding:7px 12px;color:#166534;font-size:11px;font-weight:500;opacity:.65;">${dataIncomingBySN?.packagePo?.receivedBy?.fullName || 'Unknown'} · ${dataIncomingBySN?.packagePo?.receivedDate ? momentTZ(dataIncomingBySN?.packagePo?.receivedDate).tz(timeZone).format('DD MMM, HH:mm') : ''}</td>
|
||||||
<td align="right" style="padding:7px 12px;color:#166534;font-size:11px;font-weight:500;opacity:.65; font-style:italic;">Not Available</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<table cellpadding="0" cellspacing="0" border="0" width="100%">
|
<table cellpadding="0" cellspacing="0" border="0" width="100%">
|
||||||
<tr>
|
<tr>
|
||||||
<td width="200" valign="top" style="padding-right:14px;">
|
<td width="200" valign="top" style="padding-right:14px;">
|
||||||
<table cellpadding="0" cellspacing="0" border="0" width="100%">
|
<table cellpadding="0" cellspacing="0" border="0" width="100%">
|
||||||
<tr>
|
${photoGridRowsHtml}
|
||||||
<td width="50%" style="padding:0 3px 6px 0;">${photoCellHtml('Front')}</td>
|
|
||||||
<td width="50%" style="padding:0 0 6px 3px;">${photoCellHtml('Rear')}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td width="50%" style="padding:0 3px 0 0;">${photoCellHtml('S/N Label')}</td>
|
|
||||||
<td width="50%" style="padding:0 0 0 3px;">${photoCellHtml('Package')}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
<td style="display: none;" valign="top">
|
<td valign="top">
|
||||||
${checklistRowsHtml}
|
${checklistRowsHtml}
|
||||||
</td>
|
</td>
|
||||||
<td style="padding:6px 0;border-bottom:1px dashed #f0f1f3;font-weight:600;color:#5f6978; font-size:11px; font-style:italic;">Not Available</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
</table>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
||||||
|
|
@ -1437,3 +1437,35 @@ export function canInputCommand(buffer: string): boolean {
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getIncomingInfoBySN(sn: string) {
|
||||||
|
try {
|
||||||
|
if (!sn) return
|
||||||
|
const remoteUrl = process.env.ERP_URL || 'https://stage.nswteam.net'
|
||||||
|
const header = {
|
||||||
|
Authorization: 'Bearer ' + process.env.ERP_TOKEN,
|
||||||
|
}
|
||||||
|
const responseDataSN = await axios.post(
|
||||||
|
remoteUrl + '/api/transferGetData',
|
||||||
|
{
|
||||||
|
urlAPI: '/api/package-po/get-incoming-by-sn',
|
||||||
|
filter: {
|
||||||
|
where: {
|
||||||
|
serialNumber: sn,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: header,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!responseDataSN?.data?.data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseDataSN?.data?.data
|
||||||
|
} catch (error) {
|
||||||
|
console.log('getIncomingInfoBySN', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1447,8 +1447,8 @@ export class WebSocketIo {
|
||||||
*/
|
*/
|
||||||
generateZulipMessage(results: any[]) {
|
generateZulipMessage(results: any[]) {
|
||||||
let msg = ``
|
let msg = ``
|
||||||
msg += `| Line | PID | SN | MAC | IOS | License | Summary | Issues |\n`
|
msg += `| Line | PID | SN | MAC | IOS | License | Issues |\n`
|
||||||
msg += `| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |\n`
|
msg += `| ---- | ---- | ---- | ---- | ---- | ---- | ---- |\n`
|
||||||
|
|
||||||
for (const item of results) {
|
for (const item of results) {
|
||||||
if (!item) continue
|
if (!item) continue
|
||||||
|
|
@ -1464,7 +1464,7 @@ export class WebSocketIo {
|
||||||
|
|
||||||
// Format issues
|
// Format issues
|
||||||
const issuesMd = item.issues?.length
|
const issuesMd = item.issues?.length
|
||||||
? item.issues.map((i: string) => `• ${i}`).join(' --')
|
? item.issues.map((i: string) => `• ${i.replace('|', '')}`).join(' --')
|
||||||
: '- No issues detected.'
|
: '- No issues detected.'
|
||||||
|
|
||||||
msg +=
|
msg +=
|
||||||
|
|
@ -1474,7 +1474,6 @@ export class WebSocketIo {
|
||||||
` | ${item.mac || ''}` +
|
` | ${item.mac || ''}` +
|
||||||
` | ${item.ios || ''}` +
|
` | ${item.ios || ''}` +
|
||||||
` | ${licenseMd}` +
|
` | ${licenseMd}` +
|
||||||
` | ${item.summary || ''}` +
|
|
||||||
` | ${issuesMd}` +
|
` | ${issuesMd}` +
|
||||||
` |\n`
|
` |\n`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue