Deploy Ladill Care / deploy (push) Successful in 31s
Add plan lifecycle, visit stages with chair/recovery points, primary dentition charting, imaging void, revenue reports, perio exams, lab cases, and recalls. Co-authored-by: Cursor <cursoragent@cursor.com>
210 lines
7.6 KiB
PHP
210 lines
7.6 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Care\Dentistry;
|
|
|
|
/**
|
|
* FDI adult + primary dentition helpers and catalog labels for dentistry.
|
|
*/
|
|
class DentalCatalog
|
|
{
|
|
public const DENTITION_ADULT = 'adult';
|
|
|
|
public const DENTITION_PRIMARY = 'primary';
|
|
|
|
public const DENTITION_MIXED = 'mixed';
|
|
|
|
/** @return list<string> */
|
|
public static function adultToothCodes(): array
|
|
{
|
|
$codes = [];
|
|
foreach ([18, 17, 16, 15, 14, 13, 12, 11, 21, 22, 23, 24, 25, 26, 27, 28] as $n) {
|
|
$codes[] = (string) $n;
|
|
}
|
|
foreach ([48, 47, 46, 45, 44, 43, 42, 41, 31, 32, 33, 34, 35, 36, 37, 38] as $n) {
|
|
$codes[] = (string) $n;
|
|
}
|
|
|
|
return $codes;
|
|
}
|
|
|
|
/** @return list<string> */
|
|
public static function primaryToothCodes(): array
|
|
{
|
|
$codes = [];
|
|
foreach ([55, 54, 53, 52, 51, 61, 62, 63, 64, 65] as $n) {
|
|
$codes[] = (string) $n;
|
|
}
|
|
foreach ([85, 84, 83, 82, 81, 71, 72, 73, 74, 75] as $n) {
|
|
$codes[] = (string) $n;
|
|
}
|
|
|
|
return $codes;
|
|
}
|
|
|
|
/** @return list<string> */
|
|
public static function toothCodesForDentition(string $dentition): array
|
|
{
|
|
return match ($dentition) {
|
|
self::DENTITION_PRIMARY => self::primaryToothCodes(),
|
|
self::DENTITION_MIXED => array_values(array_unique(array_merge(
|
|
self::adultToothCodes(),
|
|
self::primaryToothCodes(),
|
|
))),
|
|
default => self::adultToothCodes(),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @return array{upper: list<string>, lower: list<string>}
|
|
*/
|
|
public static function odontogramRows(?string $dentition = self::DENTITION_ADULT): array
|
|
{
|
|
return match ($dentition) {
|
|
self::DENTITION_PRIMARY => [
|
|
'upper' => ['55', '54', '53', '52', '51', '61', '62', '63', '64', '65'],
|
|
'lower' => ['85', '84', '83', '82', '81', '71', '72', '73', '74', '75'],
|
|
],
|
|
self::DENTITION_MIXED => [
|
|
'upper' => ['18', '17', '16', '55', '54', '53', '52', '51', '61', '62', '63', '64', '65', '26', '27', '28'],
|
|
'lower' => ['48', '47', '46', '85', '84', '83', '82', '81', '71', '72', '73', '74', '75', '36', '37', '38'],
|
|
],
|
|
default => [
|
|
'upper' => ['18', '17', '16', '15', '14', '13', '12', '11', '21', '22', '23', '24', '25', '26', '27', '28'],
|
|
'lower' => ['48', '47', '46', '45', '44', '43', '42', '41', '31', '32', '33', '34', '35', '36', '37', '38'],
|
|
],
|
|
};
|
|
}
|
|
|
|
/** @return list<string> */
|
|
public static function surfaces(): array
|
|
{
|
|
return ['M', 'O', 'D', 'B', 'L', 'I'];
|
|
}
|
|
|
|
/** @return list<string> */
|
|
public static function perioSurfaces(): array
|
|
{
|
|
return ['MB', 'B', 'DB', 'ML', 'L', 'DL'];
|
|
}
|
|
|
|
/** @return array<string, string> */
|
|
public static function dentitions(): array
|
|
{
|
|
return [
|
|
self::DENTITION_ADULT => 'Adult',
|
|
self::DENTITION_PRIMARY => 'Primary',
|
|
self::DENTITION_MIXED => 'Mixed',
|
|
];
|
|
}
|
|
|
|
/** @return array<string, string> */
|
|
public static function conditions(): array
|
|
{
|
|
return [
|
|
'caries' => 'Caries',
|
|
'filled' => 'Filled',
|
|
'crown' => 'Crown',
|
|
'root_canal' => 'Root canal',
|
|
'fracture' => 'Fracture',
|
|
'watch' => 'Watch',
|
|
'missing' => 'Missing',
|
|
'implant' => 'Implant',
|
|
];
|
|
}
|
|
|
|
/** @return array<string, string> */
|
|
public static function toothStatuses(): array
|
|
{
|
|
return [
|
|
'present' => 'Present',
|
|
'missing' => 'Missing',
|
|
'extracted' => 'Extracted',
|
|
'implant' => 'Implant',
|
|
];
|
|
}
|
|
|
|
/** @return array<string, string> */
|
|
public static function modalities(): array
|
|
{
|
|
return [
|
|
'pa' => 'Periapical (PA)',
|
|
'bw' => 'Bitewing (BW)',
|
|
'opg' => 'OPG / panoramic',
|
|
'photo' => 'Clinical photo',
|
|
];
|
|
}
|
|
|
|
/** @return array<string, string> */
|
|
public static function labCaseTypes(): array
|
|
{
|
|
return [
|
|
'crown' => 'Crown',
|
|
'bridge' => 'Bridge',
|
|
'denture' => 'Denture',
|
|
'veneer' => 'Veneer',
|
|
'ortho' => 'Ortho appliance',
|
|
'other' => 'Other',
|
|
];
|
|
}
|
|
|
|
/** @return array<string, string> */
|
|
public static function labCaseStatuses(): array
|
|
{
|
|
return [
|
|
'draft' => 'Draft',
|
|
'ordered' => 'Ordered',
|
|
'sent' => 'Sent to lab',
|
|
'received' => 'Received',
|
|
'fitted' => 'Fitted',
|
|
'cancelled' => 'Cancelled',
|
|
];
|
|
}
|
|
|
|
/** @return array<string, array{label: string, amount_minor: int, type: string}> */
|
|
public static function defaultServices(): array
|
|
{
|
|
return [
|
|
'den.consult' => ['label' => 'Dental consultation', 'amount_minor' => 5000, 'type' => 'consultation'],
|
|
'den.exam' => ['label' => 'Dental examination', 'amount_minor' => 4000, 'type' => 'consultation'],
|
|
'den.cleaning' => ['label' => 'Scaling / polishing', 'amount_minor' => 8000, 'type' => 'procedure'],
|
|
'den.fill' => ['label' => 'Filling', 'amount_minor' => 12000, 'type' => 'procedure'],
|
|
'den.extraction' => ['label' => 'Extraction', 'amount_minor' => 15000, 'type' => 'procedure'],
|
|
'den.rct' => ['label' => 'Root canal treatment', 'amount_minor' => 35000, 'type' => 'procedure'],
|
|
'den.crown' => ['label' => 'Crown', 'amount_minor' => 45000, 'type' => 'procedure'],
|
|
'den.bridge' => ['label' => 'Bridge unit', 'amount_minor' => 40000, 'type' => 'procedure'],
|
|
'den.implant_consult' => ['label' => 'Implant consultation', 'amount_minor' => 10000, 'type' => 'consultation'],
|
|
'den.perio' => ['label' => 'Periodontal treatment', 'amount_minor' => 20000, 'type' => 'procedure'],
|
|
'den.xray_pa' => ['label' => 'Periapical X-ray', 'amount_minor' => 3000, 'type' => 'imaging'],
|
|
'den.xray_bw' => ['label' => 'Bitewing X-ray', 'amount_minor' => 3500, 'type' => 'imaging'],
|
|
'den.xray_opg' => ['label' => 'OPG / panoramic X-ray', 'amount_minor' => 8000, 'type' => 'imaging'],
|
|
'den.photo' => ['label' => 'Clinical photography', 'amount_minor' => 2000, 'type' => 'imaging'],
|
|
];
|
|
}
|
|
|
|
public static function isValidToothCode(string $code): bool
|
|
{
|
|
return in_array($code, self::toothCodesForDentition(self::DENTITION_MIXED), true);
|
|
}
|
|
|
|
public static function toothCssClass(string $status, array $conditions = []): string
|
|
{
|
|
if (in_array($status, ['missing', 'extracted'], true) || in_array('missing', $conditions, true)) {
|
|
return 'bg-slate-200 text-slate-500 line-through';
|
|
}
|
|
if ($status === 'implant' || in_array('implant', $conditions, true)) {
|
|
return 'bg-sky-100 text-sky-800 ring-1 ring-sky-300';
|
|
}
|
|
if (in_array('caries', $conditions, true) || in_array('fracture', $conditions, true)) {
|
|
return 'bg-amber-100 text-amber-900 ring-1 ring-amber-400';
|
|
}
|
|
if (in_array('filled', $conditions, true) || in_array('crown', $conditions, true) || in_array('root_canal', $conditions, true)) {
|
|
return 'bg-emerald-50 text-emerald-800 ring-1 ring-emerald-300';
|
|
}
|
|
if (in_array('watch', $conditions, true)) {
|
|
return 'bg-violet-50 text-violet-800 ring-1 ring-violet-300';
|
|
}
|
|
|
|
return 'bg-white text-slate-800 ring-1 ring-slate-200';
|
|
}
|
|
}
|