Compare commits
1
Commits
ab5e402ee9
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
afd6d03a71 |
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
import { pickCustomer, sameNumber, type StoredAddress } from './addresses'
|
import { pickCaller, sameNumber, type StoredAddress } from './addresses'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SwyxIt! meldet Rufnummern mit Amtsholung ("001602107449"), die Webhooks
|
* SwyxIt! meldet Rufnummern mit Amtsholung ("001602107449"), die Webhooks
|
||||||
@@ -32,7 +32,7 @@ describe('sameNumber', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('pickCustomer', () => {
|
describe('pickCaller', () => {
|
||||||
const entries: StoredAddress[] = [
|
const entries: StoredAddress[] = [
|
||||||
{ number: '5215', name: 'Abdul, Rokhsareh', description: 'Globales Telefonbuch' },
|
{ number: '5215', name: 'Abdul, Rokhsareh', description: 'Globales Telefonbuch' },
|
||||||
{
|
{
|
||||||
@@ -43,21 +43,39 @@ describe('pickCustomer', () => {
|
|||||||
cscId: 100164,
|
cscId: 100164,
|
||||||
url: 'https://test.sb.assecutor.de/admin/tapi_wrapper.php?phoneNo=01602107449',
|
url: 'https://test.sb.assecutor.de/admin/tapi_wrapper.php?phoneNo=01602107449',
|
||||||
},
|
},
|
||||||
{ number: '+49171677xxxx', name: 'Rainer Peters (HH1003)', role: 'courier' },
|
{
|
||||||
|
number: '+49171677xxxx',
|
||||||
|
name: 'Rainer Peters (HH1003)',
|
||||||
|
role: 'courier',
|
||||||
|
jobIds: [21891233],
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
it('findet den Kunden zur gemeldeten Rufnummer', () => {
|
it('findet den Kunden zur gemeldeten Rufnummer', () => {
|
||||||
const match = pickCustomer(entries, '001602107449')
|
const match = pickCaller(entries, '001602107449')
|
||||||
expect(match?.name).toBe('SYSGEN GmbH')
|
expect(match?.name).toBe('SYSGEN GmbH')
|
||||||
expect(match?.url).toContain('tapi_wrapper')
|
expect(match?.url).toContain('tapi_wrapper')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('übergeht Telefonbuch-Einträge und Kuriere', () => {
|
it('findet auch den Kurier zur gemeldeten Rufnummer', () => {
|
||||||
expect(pickCustomer(entries, '5215')).toBeNull()
|
const match = pickCaller(entries, '+49171677xxxx')
|
||||||
expect(pickCustomer(entries, '+49171677xxxx')).toBeNull()
|
expect(match?.name).toBe('Rainer Peters (HH1003)')
|
||||||
|
expect(match?.jobIds).toEqual([21891233])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('übergeht Telefonbuch-Einträge (ohne Rolle)', () => {
|
||||||
|
expect(pickCaller(entries, '5215')).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('bevorzugt bei doppelter Rufnummer den Kunden vor dem Kurier', () => {
|
||||||
|
const both: StoredAddress[] = [
|
||||||
|
{ number: '+49404711', name: 'Kurier', role: 'courier' },
|
||||||
|
{ number: '+49404711', name: 'Kunde', role: 'customer' },
|
||||||
|
]
|
||||||
|
expect(pickCaller(both, '+49404711')?.name).toBe('Kunde')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('liefert null, wenn keine Rufnummer passt', () => {
|
it('liefert null, wenn keine Rufnummer passt', () => {
|
||||||
expect(pickCustomer(entries, '+49404711')).toBeNull()
|
expect(pickCaller(entries, '+49404711')).toBeNull()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
+14
-10
@@ -63,25 +63,29 @@ export function sameNumber(a: string, b: string): boolean {
|
|||||||
return left.slice(-tail) === right.slice(-tail)
|
return left.slice(-tail) === right.slice(-tail)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Der erste Kunde (Rolle `customer`) aus `entries`, dessen Rufnummer passt. */
|
/**
|
||||||
export function pickCustomer(entries: StoredAddress[], number: string): StoredAddress | null {
|
* Der erste Kunde oder Kurier aus `entries`, dessen Rufnummer passt – Einträge
|
||||||
return (
|
* ohne Rolle (SwyxTray-Telefonbuch) zählen nicht. Passen beide Rollen, gewinnt
|
||||||
|
* der Kunde.
|
||||||
|
*/
|
||||||
|
export function pickCaller(entries: StoredAddress[], number: string): StoredAddress | null {
|
||||||
|
const withRole = (role: string) =>
|
||||||
entries.find(
|
entries.find(
|
||||||
(entry) => entry.role === 'customer' && entry.number && sameNumber(entry.number, number),
|
(entry) => entry.role === role && entry.number && sameNumber(entry.number, number),
|
||||||
) ?? null
|
|
||||||
)
|
)
|
||||||
|
return withRole('customer') ?? withRole('courier') ?? null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sucht in der Ablage den Kunden zur Rufnummer eines Anrufs. Die Datenbank
|
* Sucht in der Ablage den Kunden oder Kurier zur Rufnummer eines Anrufs. Die
|
||||||
* sucht als Teilzeichenkette – abgefragt werden deshalb die letzten Ziffern,
|
* Datenbank sucht als Teilzeichenkette – abgefragt werden deshalb die letzten
|
||||||
* das genaue Passen prüft {@link sameNumber} hier im Browser.
|
* Ziffern, das genaue Passen prüft {@link sameNumber} hier im Browser.
|
||||||
*/
|
*/
|
||||||
export async function findCustomerByNumber(number: string): Promise<StoredAddress | null> {
|
export async function findCallerByNumber(number: string): Promise<StoredAddress | null> {
|
||||||
const digits = significantDigits(number)
|
const digits = significantDigits(number)
|
||||||
if (!digits) return null
|
if (!digits) return null
|
||||||
const entries = await fetchAddressEntries(digits.slice(-9))
|
const entries = await fetchAddressEntries(digits.slice(-9))
|
||||||
return pickCustomer(entries, number)
|
return pickCaller(entries, number)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { StoredAddress } from '../addresses'
|
|||||||
import { fetchJob } from '../jobs'
|
import { fetchJob } from '../jobs'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
/** Der Kunde aus der Adress-Ablage, dessen Rufnummer zum Anruf passt. */
|
/** Der Kunde oder Kurier aus der Adress-Ablage, dessen Rufnummer zum Anruf passt. */
|
||||||
customer: StoredAddress
|
customer: StoredAddress
|
||||||
/**
|
/**
|
||||||
* Nimmt den Anruf an; solange er klingelt gesetzt, danach `undefined` –
|
* Nimmt den Anruf an; solange er klingelt gesetzt, danach `undefined` –
|
||||||
@@ -14,8 +14,8 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Popup zu einem eingehenden Anruf, dessen Rufnummer zu einem Kunden in der
|
* Popup zu einem eingehenden Anruf: zeigt die Daten des Anrufers aus der
|
||||||
* Adress-Ablage gehört: zeigt die Kundendaten, dazu je Job-Kennung des Kunden
|
* Adress-Ablage (Kunde oder Kurier), dazu je Job-Kennung des Eintrags
|
||||||
* (`job_ids`) einen Knopf, der den Job aus der Ablage holt und dessen
|
* (`job_ids`) einen Knopf, der den Job aus der Ablage holt und dessen
|
||||||
* Sprungadresse (`url`) in einem neuen Tab öffnet. Gleiches Overlay wie der
|
* Sprungadresse (`url`) in einem neuen Tab öffnet. Gleiches Overlay wie der
|
||||||
* Wartedialog (siehe LoadingDialog zur Begründung gegen `<dialog>`).
|
* Wartedialog (siehe LoadingDialog zur Begründung gegen `<dialog>`).
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useCallNotifications } from '../hooks/useCallNotifications'
|
|||||||
import { useWebhook } from '../hooks/useWebhook'
|
import { useWebhook } from '../hooks/useWebhook'
|
||||||
import { countUnseen } from '../webhook'
|
import { countUnseen } from '../webhook'
|
||||||
import { applyBrand, loadBrand, type Brand } from '../brand'
|
import { applyBrand, loadBrand, type Brand } from '../brand'
|
||||||
import { findCustomerByNumber, type StoredAddress } from '../addresses'
|
import { findCallerByNumber, type StoredAddress } from '../addresses'
|
||||||
import StatusBadge from '../components/StatusBadge'
|
import StatusBadge from '../components/StatusBadge'
|
||||||
import CustomerCallPopup from '../components/CustomerCallPopup'
|
import CustomerCallPopup from '../components/CustomerCallPopup'
|
||||||
import StadtboteSignet from '../components/StadtboteSignet'
|
import StadtboteSignet from '../components/StadtboteSignet'
|
||||||
@@ -53,9 +53,9 @@ export default function HomePage() {
|
|||||||
const { permission, requestPermission } = useCallNotifications(ringingCall)
|
const { permission, requestPermission } = useCallNotifications(ringingCall)
|
||||||
|
|
||||||
// Meldet SwyxIt! einen eingehenden Anruf, geht das Popup auf – mit den
|
// Meldet SwyxIt! einen eingehenden Anruf, geht das Popup auf – mit den
|
||||||
// Kundendaten, wenn die Rufnummer in der Adress-Ablage einem Kunden gehört,
|
// Daten aus der Adress-Ablage, wenn die Rufnummer einem Kunden oder Kurier
|
||||||
// sonst nur mit der Rufnummer. Je Anruf nur eine Abfrage – der Snapshot
|
// gehört, sonst nur mit der Rufnummer. Je Anruf nur eine Abfrage – der
|
||||||
// kommt mehrfach.
|
// Snapshot kommt mehrfach.
|
||||||
const [caller, setCaller] = useState<StoredAddress | null>(null)
|
const [caller, setCaller] = useState<StoredAddress | null>(null)
|
||||||
const lookedUpRef = useRef<string | null>(null)
|
const lookedUpRef = useRef<string | null>(null)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -70,7 +70,7 @@ export default function HomePage() {
|
|||||||
if (lookedUpRef.current === key) return
|
if (lookedUpRef.current === key) return
|
||||||
lookedUpRef.current = key
|
lookedUpRef.current = key
|
||||||
let stale = false
|
let stale = false
|
||||||
findCustomerByNumber(number)
|
findCallerByNumber(number)
|
||||||
.then((match) => {
|
.then((match) => {
|
||||||
if (!stale) setCaller(match ?? { number })
|
if (!stale) setCaller(match ?? { number })
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user