import { portraitFor } from '@/lib/avatar' import type { Branch, FaqArticle, ModuleKey, Order, Organisation, Prescription, StaffRole } from '@/types' export const branches: Branch[] = [ { id: 'br-bole', name: 'Bole Branch', zone: 'Bole', phone: '+251 930 099 850' }, { id: 'br-cmc', name: 'CMC Branch', zone: 'CMC', phone: '+251 930 099 851' }, { id: 'br-piassa', name: 'Piassa Branch', zone: 'Piassa', phone: '+251 930 099 852' }, ] /** `avatarUrl` is optional throughout — a real uploaded photo URL drops in unchanged. */ export const riders = [ { id: 'rd-1', name: 'Kidus Rider', phone: '+251911000111', telegramId: '@kidus_gishen', branchId: 'br-bole', zone: 'Bole', avatarUrl: portraitFor('Kidus Rider') }, { id: 'rd-2', name: 'Meron Rider', phone: '+251911000222', telegramId: '@meron_gishen', branchId: 'br-cmc', zone: 'CMC', avatarUrl: portraitFor('Meron Rider') }, // Samuel has no photo on file yet, alongside the missing vehicle plate in `configIssues`. { id: 'rd-3', name: 'Samuel Rider', phone: '+251911000333', telegramId: '@sam_gishen', branchId: 'br-bole', zone: 'Bole', avatarUrl: portraitFor('Samuel Rider') }, ] export let prescriptions: Prescription[] = [ { id: 'rx-1001', customerName: 'Abebe Kebede', branchId: 'br-bole', status: 'under_review', items: [ { name: 'Amoxicillin 500mg', qty: 21 }, { name: 'Paracetamol 500mg', qty: 10 }, ], submittedAt: '2026-08-06T08:10:00Z', prescriber: 'Dr. Selam', }, { id: 'rx-1002', customerName: 'Marta Hailu', branchId: 'br-bole', status: 'submitted', items: [{ name: 'Metformin 850mg', qty: 60, controlled: false }], submittedAt: '2026-08-06T09:20:00Z', prescriber: 'Dr. Yohannes', }, { id: 'rx-1003', customerName: 'Tigist Alemu', branchId: 'br-cmc', status: 'under_review', items: [{ name: 'Tramadol 50mg', qty: 20, controlled: true }], submittedAt: '2026-08-06T07:45:00Z', prescriber: 'Dr. Helen', reviewStartedAt: '2026-08-06T08:05:00Z', }, { id: 'rx-1004', customerName: 'Dawit Lemma', branchId: 'br-piassa', status: 'submitted', items: [{ name: 'Salbutamol inhaler', qty: 2 }], submittedAt: '2026-08-05T16:30:00Z', prescriber: 'Dr. Kalkidan', }, { id: 'rx-1005', customerName: 'Hiwot Bekele', branchId: 'br-cmc', status: 'queried', items: [{ name: 'Amlodipine 5mg', qty: 30 }], submittedAt: '2026-08-05T11:15:00Z', prescriber: 'Dr. Yohannes', reviewStartedAt: '2026-08-05T11:40:00Z', reviewedBy: 'Hana Pharmacist', reviewedAt: '2026-08-05T12:05:00Z', decisionNote: 'Prescriber signature is illegible — a clearer scan was requested.', }, { id: 'rx-1006', customerName: 'Netsanet Desta', branchId: 'br-bole', status: 'approved', items: [{ name: 'Cetirizine 10mg', qty: 20 }], submittedAt: '2026-08-04T09:10:00Z', prescriber: 'Dr. Selam', reviewStartedAt: '2026-08-04T09:25:00Z', reviewedBy: 'Hana Pharmacist', reviewedAt: '2026-08-04T09:48:00Z', }, { id: 'rx-1007', customerName: 'Almaz Girma', branchId: 'br-bole', status: 'approved', items: [ { name: 'Metformin 850mg', qty: 60 }, { name: 'Amlodipine 5mg', qty: 30 }, ], submittedAt: '2026-08-03T14:05:00Z', prescriber: 'Dr. Helen', reviewStartedAt: '2026-08-03T14:20:00Z', reviewedBy: 'Hana Pharmacist', reviewedAt: '2026-08-03T14:52:00Z', }, { id: 'rx-1008', customerName: 'Bekele Tesfaye', branchId: 'br-cmc', status: 'rejected', items: [{ name: 'Diazepam 5mg', qty: 14, controlled: true }], submittedAt: '2026-08-02T10:40:00Z', prescriber: 'Dr. Unknown', reviewStartedAt: '2026-08-02T11:00:00Z', reviewedBy: 'Hana Pharmacist', reviewedAt: '2026-08-02T11:22:00Z', decisionNote: 'Prescriber is not on the national register.', }, { id: 'rx-1009', customerName: 'Yeshi Lemma', branchId: 'br-bole', status: 'approved', items: [{ name: 'Ferrous sulphate 200mg', qty: 30 }], submittedAt: '2026-08-01T08:20:00Z', prescriber: 'Dr. Selam', reviewStartedAt: '2026-08-01T08:35:00Z', reviewedBy: 'Selamawit Girma', reviewedAt: '2026-08-01T09:02:00Z', }, { id: 'rx-1010', customerName: 'Sofia Ahmed', branchId: 'br-bole', status: 'approved', items: [{ name: 'Azithromycin 250mg', qty: 6 }], submittedAt: '2026-07-31T13:45:00Z', prescriber: 'Dr. Yohannes', reviewStartedAt: '2026-07-31T14:00:00Z', reviewedBy: 'Hana Pharmacist', reviewedAt: '2026-07-31T14:18:00Z', }, { id: 'rx-1011', customerName: 'Genet Abera', branchId: 'br-piassa', status: 'approved', items: [{ name: 'Hydrocortisone cream 1%', qty: 2 }], submittedAt: '2026-07-30T11:05:00Z', prescriber: 'Dr. Kalkidan', reviewStartedAt: '2026-07-30T11:20:00Z', reviewedBy: 'Nardos Alemu', reviewedAt: '2026-07-30T11:44:00Z', }, { id: 'rx-1012', customerName: 'Solomon Wolde', branchId: 'br-cmc', status: 'queried', items: [{ name: 'Ibuprofen 400mg', qty: 30 }], submittedAt: '2026-07-29T15:20:00Z', prescriber: 'Dr. Helen', reviewStartedAt: '2026-07-29T15:35:00Z', reviewedBy: 'Rediet Tesfa', reviewedAt: '2026-07-29T15:58:00Z', decisionNote: 'Dosage exceeds the maximum daily limit — prescriber clarification requested.', }, { id: 'rx-1013', customerName: 'Rahel Tadesse', branchId: 'br-bole', status: 'approved', items: [{ name: 'Amoxicillin 500mg', qty: 21 }], submittedAt: '2026-07-28T09:30:00Z', prescriber: 'Dr. Selam', reviewStartedAt: '2026-07-28T09:45:00Z', reviewedBy: 'Hana Pharmacist', reviewedAt: '2026-07-28T10:05:00Z', }, { id: 'rx-1014', customerName: 'Meseret Kassa', branchId: 'br-cmc', status: 'approved', items: [{ name: 'Oral rehydration salts', qty: 10 }], submittedAt: '2026-07-26T16:10:00Z', prescriber: 'Dr. Yohannes', reviewStartedAt: '2026-07-26T16:25:00Z', reviewedBy: 'Rediet Tesfa', reviewedAt: '2026-07-26T16:40:00Z', }, { id: 'rx-1015', customerName: 'Tesfaye Negash', branchId: 'br-bole', status: 'rejected', items: [{ name: 'Tramadol 50mg', qty: 40, controlled: true }], submittedAt: '2026-07-24T12:00:00Z', prescriber: 'Dr. Helen', reviewStartedAt: '2026-07-24T12:15:00Z', reviewedBy: 'Hana Pharmacist', reviewedAt: '2026-07-24T12:38:00Z', decisionNote: 'Controlled substance quantity is not supported by the diagnosis on file.', }, { id: 'rx-1016', customerName: 'Kalkidan Berhe', branchId: 'br-piassa', status: 'approved', items: [{ name: 'Paracetamol 500mg', qty: 20 }], submittedAt: '2026-07-22T10:15:00Z', prescriber: 'Dr. Kalkidan', reviewStartedAt: '2026-07-22T10:30:00Z', reviewedBy: 'Nardos Alemu', reviewedAt: '2026-07-22T10:47:00Z', }, { id: 'rx-1017', customerName: 'Eyob Mekonnen', branchId: 'br-bole', status: 'approved', items: [ { name: 'Vitamin C 1000mg', qty: 2 }, { name: 'Ferrous sulphate 200mg', qty: 30 }, ], submittedAt: '2026-07-20T14:40:00Z', prescriber: 'Dr. Selam', reviewStartedAt: '2026-07-20T14:55:00Z', reviewedBy: 'Selamawit Girma', reviewedAt: '2026-07-20T15:12:00Z', }, { id: 'rx-1018', customerName: 'Fikru Assefa', branchId: 'br-cmc', status: 'approved', items: [{ name: 'Metformin 850mg', qty: 60 }], submittedAt: '2026-07-17T09:05:00Z', prescriber: 'Dr. Yohannes', reviewStartedAt: '2026-07-17T09:20:00Z', reviewedBy: 'Rediet Tesfa', reviewedAt: '2026-07-17T09:41:00Z', }, { id: 'rx-1019', customerName: 'Mulugeta Fikadu', branchId: 'br-bole', status: 'approved', items: [{ name: 'Salbutamol inhaler', qty: 1 }], submittedAt: '2026-07-14T17:25:00Z', prescriber: 'Dr. Helen', reviewStartedAt: '2026-07-14T17:40:00Z', reviewedBy: 'Hana Pharmacist', reviewedAt: '2026-07-14T17:58:00Z', }, { id: 'rx-1020', customerName: 'Selam Getahun', branchId: 'br-piassa', status: 'rejected', items: [{ name: 'Azithromycin 250mg', qty: 12 }], submittedAt: '2026-07-11T11:50:00Z', prescriber: 'Dr. Kalkidan', reviewStartedAt: '2026-07-11T12:05:00Z', reviewedBy: 'Nardos Alemu', reviewedAt: '2026-07-11T12:26:00Z', decisionNote: 'Duplicate submission — rx-1010 already covers this course.', }, { id: 'rx-1021', customerName: 'Zenebech Haile', branchId: 'br-cmc', status: 'approved', items: [{ name: 'Cetirizine 10mg', qty: 30 }], submittedAt: '2026-07-09T08:45:00Z', prescriber: 'Dr. Yohannes', reviewStartedAt: '2026-07-09T09:00:00Z', reviewedBy: 'Rediet Tesfa', reviewedAt: '2026-07-09T09:19:00Z', }, { id: 'rx-1022', customerName: 'Abebe Kebede', branchId: 'br-bole', status: 'approved', items: [{ name: 'Amoxicillin 500mg', qty: 21 }], submittedAt: '2026-08-06T07:20:00Z', prescriber: 'Dr. Selam', reviewStartedAt: '2026-08-06T07:35:00Z', reviewedBy: 'Hana Pharmacist', reviewedAt: '2026-08-06T07:52:00Z', }, { id: 'rx-1023', customerName: 'Marta Hailu', branchId: 'br-bole', status: 'approved', items: [{ name: 'Tramadol 50mg', qty: 20, controlled: true }], submittedAt: '2026-08-05T15:10:00Z', prescriber: 'Dr. Helen', reviewStartedAt: '2026-08-05T15:25:00Z', reviewedBy: 'Hana Pharmacist', reviewedAt: '2026-08-05T15:58:00Z', decisionNote: 'Controlled register entry signed; dispense against photo ID only.', }, ] export let orders: Order[] = [ { id: 'ord-5001', customerName: 'Abebe Kebede', branchId: 'br-bole', fulfillment: 'delivery', status: 'confirmed', totalEtb: 1250, channel: 'web', zone: 'Bole', address: 'Bole Atlas, Addis Ababa', createdAt: '2026-08-06T08:30:00Z', paid: true, rxApproved: true, }, { id: 'ord-5002', customerName: 'Guest · 0911223344', branchId: 'br-bole', fulfillment: 'pickup', status: 'picking', totalEtb: 420, channel: 'telegram', createdAt: '2026-08-06T09:00:00Z', paid: true, }, { id: 'ord-5003', customerName: 'Bank staff · Hiwot', branchId: 'br-cmc', fulfillment: 'delivery', status: 'confirmed', totalEtb: 3400, channel: 'b2b', zone: 'CMC', address: 'CMC Road', createdAt: '2026-08-06T10:00:00Z', paid: true, rxApproved: true, }, { id: 'ord-5004', customerName: 'Netsanet D.', branchId: 'br-bole', fulfillment: 'delivery', status: 'out_for_delivery', totalEtb: 890, channel: 'mobile', zone: 'Bole', address: 'Gerji Mebrat Hail', assignedRiderId: 'rd-1', createdAt: '2026-08-06T07:15:00Z', paid: true, rxApproved: true, }, { id: 'ord-5005', customerName: 'Rahel Tadesse', branchId: 'br-bole', fulfillment: 'delivery', status: 'confirmed', totalEtb: 420, channel: 'mobile', zone: 'Bole', address: 'Bole Rwanda', createdAt: '2026-08-06T06:40:00Z', paid: true, rxApproved: true }, { id: 'ord-5006', customerName: 'Guest · 0913445566', branchId: 'br-cmc', fulfillment: 'pickup', status: 'picking', totalEtb: 300, channel: 'web', createdAt: '2026-08-06T07:50:00Z', paid: true }, { id: 'ord-5007', customerName: 'Dawit Lemma', branchId: 'br-piassa', fulfillment: 'delivery', status: 'pending', totalEtb: 420, channel: 'telegram', zone: 'Piassa', address: 'Piassa Adwa Ave', createdAt: '2026-08-06T10:40:00Z', paid: false }, { id: 'ord-5008', customerName: 'Almaz Girma', branchId: 'br-bole', fulfillment: 'delivery', status: 'delivered', totalEtb: 458, channel: 'web', zone: 'Bole', address: 'Bole Medhanialem', assignedRiderId: 'rd-1', createdAt: '2026-08-05T08:20:00Z', paid: true, rxApproved: true }, { id: 'ord-5009', customerName: 'Horizon Bank · Hiwot', branchId: 'br-cmc', fulfillment: 'pickup', status: 'delivered', totalEtb: 3480, channel: 'b2b', createdAt: '2026-08-05T13:10:00Z', paid: true }, { id: 'ord-5010', customerName: 'Sofia Ahmed', branchId: 'br-bole', fulfillment: 'delivery', status: 'delivered', totalEtb: 280, channel: 'mobile', zone: 'Bole', address: 'Gerji Roba', assignedRiderId: 'rd-3', createdAt: '2026-08-04T15:25:00Z', paid: true }, { id: 'ord-5011', customerName: 'Bekele Tesfaye', branchId: 'br-cmc', fulfillment: 'delivery', status: 'delivered', totalEtb: 436, channel: 'web', zone: 'CMC', address: 'CMC Michael', assignedRiderId: 'rd-2', createdAt: '2026-08-04T11:05:00Z', paid: true, rxApproved: true }, { id: 'ord-5012', customerName: 'Genet Abera', branchId: 'br-piassa', fulfillment: 'pickup', status: 'delivered', totalEtb: 480, channel: 'telegram', createdAt: '2026-08-03T09:35:00Z', paid: true }, { id: 'ord-5013', customerName: 'Solomon Wolde', branchId: 'br-cmc', fulfillment: 'delivery', status: 'cancelled', totalEtb: 380, channel: 'web', zone: 'CMC', address: 'CMC Summit', createdAt: '2026-08-02T14:50:00Z', paid: false }, { id: 'ord-5014', customerName: 'Yeshi Lemma', branchId: 'br-bole', fulfillment: 'delivery', status: 'delivered', totalEtb: 540, channel: 'mobile', zone: 'Bole', address: 'Bole Atlas', assignedRiderId: 'rd-1', createdAt: '2026-08-02T10:15:00Z', paid: true }, { id: 'ord-5015', customerName: 'Netsanet Desta', branchId: 'br-bole', fulfillment: 'pickup', status: 'delivered', totalEtb: 460, channel: 'web', createdAt: '2026-08-01T16:20:00Z', paid: true }, { id: 'ord-5016', customerName: 'Meseret Kassa', branchId: 'br-cmc', fulfillment: 'delivery', status: 'delivered', totalEtb: 330, channel: 'telegram', zone: 'CMC', address: 'CMC Road', assignedRiderId: 'rd-2', createdAt: '2026-08-01T12:40:00Z', paid: true }, { id: 'ord-5017', customerName: 'Abebe Kebede', branchId: 'br-bole', fulfillment: 'delivery', status: 'delivered', totalEtb: 492, channel: 'web', zone: 'Bole', address: 'Bole Atlas', assignedRiderId: 'rd-3', createdAt: '2026-07-31T09:50:00Z', paid: true, rxApproved: true }, { id: 'ord-5018', customerName: 'Kalkidan Berhe', branchId: 'br-piassa', fulfillment: 'delivery', status: 'delivered', totalEtb: 185, channel: 'mobile', zone: 'Piassa', address: 'Piassa Arada', assignedRiderId: 'rd-3', createdAt: '2026-07-30T17:05:00Z', paid: true }, { id: 'ord-5019', customerName: 'Horizon Bank · Daniel', branchId: 'br-cmc', fulfillment: 'delivery', status: 'delivered', totalEtb: 6080, channel: 'b2b', zone: 'CMC', address: 'Horizon Bank HQ', assignedRiderId: 'rd-2', createdAt: '2026-07-30T10:20:00Z', paid: true }, { id: 'ord-5020', customerName: 'Tigist Alemu', branchId: 'br-cmc', fulfillment: 'pickup', status: 'delivered', totalEtb: 326, channel: 'telegram', createdAt: '2026-07-29T15:40:00Z', paid: true }, { id: 'ord-5021', customerName: 'Marta Hailu', branchId: 'br-bole', fulfillment: 'delivery', status: 'delivered', totalEtb: 1680, channel: 'mobile', zone: 'Bole', address: 'Bole Rwanda', assignedRiderId: 'rd-1', createdAt: '2026-07-28T11:30:00Z', paid: true, rxApproved: true }, { id: 'ord-5022', customerName: 'Guest · 0917889900', branchId: 'br-bole', fulfillment: 'pickup', status: 'delivered', totalEtb: 280, channel: 'web', createdAt: '2026-07-28T08:15:00Z', paid: true }, { id: 'ord-5023', customerName: 'Fikru Assefa', branchId: 'br-cmc', fulfillment: 'delivery', status: 'delivered', totalEtb: 400, channel: 'web', zone: 'CMC', address: 'CMC Michael', assignedRiderId: 'rd-2', createdAt: '2026-07-27T13:55:00Z', paid: true }, { id: 'ord-5024', customerName: 'Zenebech Haile', branchId: 'br-cmc', fulfillment: 'pickup', status: 'delivered', totalEtb: 370, channel: 'telegram', createdAt: '2026-07-26T16:45:00Z', paid: true }, { id: 'ord-5025', customerName: 'Dawit Alemayehu', branchId: 'br-bole', fulfillment: 'delivery', status: 'delivered', totalEtb: 840, channel: 'mobile', zone: 'Bole', address: 'Summit Safari', assignedRiderId: 'rd-3', createdAt: '2026-07-26T09:25:00Z', paid: true }, { id: 'ord-5026', customerName: 'Tesfaye Negash', branchId: 'br-bole', fulfillment: 'delivery', status: 'delivered', totalEtb: 598, channel: 'web', zone: 'Bole', address: 'Bole Medhanialem', assignedRiderId: 'rd-1', createdAt: '2026-07-25T14:10:00Z', paid: true }, { id: 'ord-5027', customerName: 'Selam Getahun', branchId: 'br-piassa', fulfillment: 'pickup', status: 'delivered', totalEtb: 400, channel: 'telegram', createdAt: '2026-07-24T10:50:00Z', paid: true }, { id: 'ord-5028', customerName: 'Mulugeta Fikadu', branchId: 'br-bole', fulfillment: 'delivery', status: 'delivered', totalEtb: 730, channel: 'mobile', zone: 'Bole', address: 'Gerji Mebrat Hail', assignedRiderId: 'rd-3', createdAt: '2026-07-24T18:30:00Z', paid: true }, { id: 'ord-5029', customerName: 'Horizon Bank · Sara', branchId: 'br-cmc', fulfillment: 'delivery', status: 'delivered', totalEtb: 2600, channel: 'b2b', zone: 'CMC', address: 'Horizon Bank HQ', assignedRiderId: 'rd-2', createdAt: '2026-07-23T11:15:00Z', paid: true }, { id: 'ord-5030', customerName: 'Eyob Mekonnen', branchId: 'br-bole', fulfillment: 'pickup', status: 'delivered', totalEtb: 600, channel: 'web', createdAt: '2026-07-22T15:35:00Z', paid: true }, { id: 'ord-5031', customerName: 'Yohannes Desta', branchId: 'br-cmc', fulfillment: 'delivery', status: 'delivered', totalEtb: 1740, channel: 'mobile', zone: 'CMC', address: 'CMC Summit', assignedRiderId: 'rd-2', createdAt: '2026-07-22T09:05:00Z', paid: true }, { id: 'ord-5032', customerName: 'Getachew Mola', branchId: 'br-bole', fulfillment: 'delivery', status: 'delivered', totalEtb: 255, channel: 'telegram', zone: 'Bole', address: 'Bole Atlas', assignedRiderId: 'rd-1', createdAt: '2026-07-21T12:20:00Z', paid: true }, { id: 'ord-5033', customerName: 'Rahel Tadesse', branchId: 'br-bole', fulfillment: 'pickup', status: 'delivered', totalEtb: 290, channel: 'web', createdAt: '2026-07-20T16:50:00Z', paid: true }, { id: 'ord-5034', customerName: 'Guest · 0919001122', branchId: 'br-piassa', fulfillment: 'delivery', status: 'delivered', totalEtb: 230, channel: 'mobile', zone: 'Piassa', address: 'Piassa Arada', assignedRiderId: 'rd-3', createdAt: '2026-07-20T10:35:00Z', paid: true }, { id: 'ord-5035', customerName: 'Almaz Girma', branchId: 'br-bole', fulfillment: 'delivery', status: 'delivered', totalEtb: 654, channel: 'web', zone: 'Bole', address: 'Bole Medhanialem', assignedRiderId: 'rd-1', createdAt: '2026-07-19T14:45:00Z', paid: true, rxApproved: true }, { id: 'ord-5036', customerName: 'Hiwot Bekele', branchId: 'br-cmc', fulfillment: 'pickup', status: 'delivered', totalEtb: 330, channel: 'telegram', createdAt: '2026-07-18T11:10:00Z', paid: true }, { id: 'ord-5037', customerName: 'Sofia Ahmed', branchId: 'br-bole', fulfillment: 'delivery', status: 'delivered', totalEtb: 370, channel: 'mobile', zone: 'Bole', address: 'Gerji Roba', assignedRiderId: 'rd-3', createdAt: '2026-07-17T17:40:00Z', paid: true }, { id: 'ord-5038', customerName: 'Horizon Bank · Hiwot', branchId: 'br-cmc', fulfillment: 'delivery', status: 'delivered', totalEtb: 4560, channel: 'b2b', zone: 'CMC', address: 'Horizon Bank HQ', assignedRiderId: 'rd-2', createdAt: '2026-07-17T09:50:00Z', paid: true }, { id: 'ord-5039', customerName: 'Meseret Kassa', branchId: 'br-cmc', fulfillment: 'delivery', status: 'delivered', totalEtb: 490, channel: 'web', zone: 'CMC', address: 'CMC Road', assignedRiderId: 'rd-2', createdAt: '2026-07-16T13:25:00Z', paid: true }, { id: 'ord-5040', customerName: 'Bekele Tesfaye', branchId: 'br-bole', fulfillment: 'pickup', status: 'delivered', totalEtb: 220, channel: 'web', createdAt: '2026-07-15T10:05:00Z', paid: true }, { id: 'ord-5041', customerName: 'Genet Abera', branchId: 'br-piassa', fulfillment: 'delivery', status: 'delivered', totalEtb: 500, channel: 'mobile', zone: 'Piassa', address: 'Piassa Adwa Ave', assignedRiderId: 'rd-3', createdAt: '2026-07-14T15:15:00Z', paid: true }, { id: 'ord-5042', customerName: 'Marta Hailu', branchId: 'br-bole', fulfillment: 'delivery', status: 'delivered', totalEtb: 1440, channel: 'telegram', zone: 'Bole', address: 'Bole Rwanda', assignedRiderId: 'rd-1', createdAt: '2026-07-14T08:55:00Z', paid: true, rxApproved: true }, { id: 'ord-5043', customerName: 'Solomon Wolde', branchId: 'br-cmc', fulfillment: 'pickup', status: 'delivered', totalEtb: 190, channel: 'web', createdAt: '2026-07-13T12:35:00Z', paid: true }, { id: 'ord-5044', customerName: 'Kalkidan Berhe', branchId: 'br-piassa', fulfillment: 'delivery', status: 'delivered', totalEtb: 410, channel: 'mobile', zone: 'Piassa', address: 'Piassa Arada', assignedRiderId: 'rd-3', createdAt: '2026-07-12T16:00:00Z', paid: true }, { id: 'ord-5045', customerName: 'Dawit Lemma', branchId: 'br-piassa', fulfillment: 'pickup', status: 'delivered', totalEtb: 250, channel: 'telegram', createdAt: '2026-07-11T09:40:00Z', paid: true }, { id: 'ord-5046', customerName: 'Abebe Kebede', branchId: 'br-bole', fulfillment: 'delivery', status: 'delivered', totalEtb: 612, channel: 'web', zone: 'Bole', address: 'Bole Atlas', assignedRiderId: 'rd-1', createdAt: '2026-07-10T14:20:00Z', paid: true, rxApproved: true }, { id: 'ord-5047', customerName: 'Fikru Assefa', branchId: 'br-cmc', fulfillment: 'delivery', status: 'delivered', totalEtb: 371, channel: 'mobile', zone: 'CMC', address: 'CMC Michael', assignedRiderId: 'rd-2', createdAt: '2026-07-09T11:45:00Z', paid: true }, { id: 'ord-5048', customerName: 'Guest · 0921223344', branchId: 'br-bole', fulfillment: 'pickup', status: 'delivered', totalEtb: 300, channel: 'web', createdAt: '2026-07-08T10:10:00Z', paid: true }, ] export let organisations: Organisation[] = [ { id: 'org-1', name: 'Horizon Bank', tin: '0001234567', status: 'active', creditLimitEtb: 500000, usedEtb: 124000, billingContact: 'finance@horizon.et', source: 'admin', createdAt: '2026-05-01T00:00:00Z', reviewedBy: 'Yonas Finance', reviewedAt: '2026-05-02T09:40:00Z', }, { id: 'org-2', name: 'Green NGO Ethiopia', tin: '0009876543', status: 'pending', creditLimitEtb: 100000, usedEtb: 0, billingContact: 'hr@greenngo.et', source: 'self_register', createdAt: '2026-08-05T14:00:00Z', }, ] /** Canonical product categories — keep `category` on every stock row in sync with this list. */ export const productCategories = [ 'Antibiotics', 'Analgesics', 'Chronic care', 'Vitamins & supplements', 'Respiratory', 'Maternal & child', 'Dermatology', 'First aid', ] as const export type ProductCategory = (typeof productCategories)[number] export type StockRow = { sku: string name: string category: ProductCategory branchId: string qty: number batch: string /** "YYYY-MM" */ expiry: string erpQty: number unitEtb: number /** Master catalog Item this batch belongs to (optional for legacy lines). */ itemId?: string /** Units reserved for open orders — ERP-synced. */ reserved?: number } export const stockRows: StockRow[] = [ { sku: 'AMX-500', name: 'Amoxicillin 500mg', category: 'Antibiotics', branchId: 'br-bole', qty: 120, batch: 'B2411', expiry: '2027-01', erpQty: 118, unitEtb: 18 }, { sku: 'PCM-500', name: 'Paracetamol 500mg', category: 'Analgesics', branchId: 'br-bole', qty: 40, batch: 'B2409', expiry: '2026-11', erpQty: 40, unitEtb: 8 }, { sku: 'MTF-850', name: 'Metformin 850mg', category: 'Chronic care', branchId: 'br-cmc', qty: 8, batch: 'B2410', expiry: '2026-09', erpQty: 8, unitEtb: 22 }, { sku: 'VIT-C', name: 'Vitamin C 1000mg', category: 'Vitamins & supplements', branchId: 'br-bole', qty: 200, batch: 'B2501', expiry: '2028-03', erpQty: 200, unitEtb: 120 }, { sku: 'AMD-5', name: 'Amlodipine 5mg', category: 'Chronic care', branchId: 'br-bole', qty: 96, batch: 'B2503', expiry: '2027-06', erpQty: 96, unitEtb: 14 }, { sku: 'CTZ-10', name: 'Cetirizine 10mg', category: 'Respiratory', branchId: 'br-bole', qty: 180, batch: 'B2502', expiry: '2027-09', erpQty: 178, unitEtb: 9 }, { sku: 'IBU-400', name: 'Ibuprofen 400mg', category: 'Analgesics', branchId: 'br-bole', qty: 210, batch: 'B2412', expiry: '2027-04', erpQty: 210, unitEtb: 11 }, { sku: 'ORS-20', name: 'Oral rehydration salts', category: 'First aid', branchId: 'br-cmc', qty: 18, batch: 'B2504', expiry: '2026-10', erpQty: 18, unitEtb: 25 }, { sku: 'HYC-1', name: 'Hydrocortisone cream 1%', category: 'Dermatology', branchId: 'br-cmc', qty: 64, batch: 'B2505', expiry: '2027-02', erpQty: 64, unitEtb: 95 }, { sku: 'AZI-250', name: 'Azithromycin 250mg', category: 'Antibiotics', branchId: 'br-cmc', qty: 16, batch: 'B2506', expiry: '2026-12', erpQty: 16, unitEtb: 46 }, { sku: 'SLB-INH', name: 'Salbutamol inhaler', category: 'Respiratory', branchId: 'br-piassa', qty: 12, batch: 'B2507', expiry: '2027-03', erpQty: 15, unitEtb: 320 }, { sku: 'FEF-200', name: 'Ferrous sulphate 200mg', category: 'Maternal & child', branchId: 'br-piassa', qty: 150, batch: 'B2508', expiry: '2028-01', erpQty: 150, unitEtb: 12 }, { sku: 'MTF-850', name: 'Metformin 850mg', category: 'Chronic care', branchId: 'br-piassa', qty: 0, batch: 'B2509', expiry: '2026-11', erpQty: 0, unitEtb: 22 }, { sku: 'AZI-250', name: 'Azithromycin 250mg', category: 'Antibiotics', branchId: 'br-piassa', qty: 0, batch: 'B2510', expiry: '2027-01', erpQty: 6, unitEtb: 46 }, { sku: 'TRM-50', name: 'Tramadol 50mg', category: 'Analgesics', branchId: 'br-bole', qty: 24, batch: 'B2511', expiry: '2027-05', erpQty: 24, unitEtb: 34 }, { sku: 'DZP-5', name: 'Diazepam 5mg', category: 'Chronic care', branchId: 'br-bole', qty: 26, batch: 'B2512', expiry: '2027-08', erpQty: 26, unitEtb: 28 }, ] // Linked after catalog import at file end — see `linkStockRowsToCatalog`. export type Customer = { id: string name: string phone: string tier: string points: number /** Branch name, matching `branches[].name` minus the " Branch" suffix. */ branch: string /** When the customer record was first created — drives the "new this month" KPI. */ joinedAt: string orders: number /** Lifetime paid spend, used for the average-lifetime-value KPI. */ spendEtb: number lastOrderAt: string | null /** Profile photo. A real uploaded URL drops in here unchanged. */ avatarUrl?: string } export const customers: Customer[] = [ { id: 'c1', name: 'Abebe Kebede', phone: '+251911223344', tier: 'Gold', points: 2400, branch: 'Bole', joinedAt: '2025-11-14T00:00:00Z', orders: 14, spendEtb: 28400, lastOrderAt: '2026-08-06T08:30:00Z', avatarUrl: portraitFor('Abebe Kebede') }, { id: 'c2', name: 'Marta Hailu', phone: '+251922334455', tier: 'Silver', points: 800, branch: 'Bole', joinedAt: '2026-02-02T00:00:00Z', orders: 9, spendEtb: 11200, lastOrderAt: '2026-08-06T09:00:00Z', avatarUrl: portraitFor('Marta Hailu') }, { id: 'c3', name: 'Tigist Alemu', phone: '+251933445566', tier: 'Bronze', points: 120, branch: 'CMC', joinedAt: '2026-08-01T00:00:00Z', orders: 4, spendEtb: 3600, lastOrderAt: '2026-07-29T15:40:00Z', avatarUrl: portraitFor('Tigist Alemu') }, { id: 'c4', name: 'Netsanet Desta', phone: '+251944556677', tier: 'Gold', points: 1650, branch: 'Bole', joinedAt: '2025-09-21T00:00:00Z', orders: 11, spendEtb: 19850, lastOrderAt: '2026-08-06T07:15:00Z', avatarUrl: portraitFor('Netsanet Desta') }, { id: 'c5', name: 'Hiwot Bekele', phone: '+251955667788', tier: 'Silver', points: 540, branch: 'CMC', joinedAt: '2026-05-08T00:00:00Z', orders: 7, spendEtb: 9600, lastOrderAt: '2026-08-06T10:00:00Z', avatarUrl: portraitFor('Hiwot Bekele') }, // Signed up two days ago and has not added a photo yet — initials carry the row. { id: 'c6', name: 'Dawit Lemma', phone: '+251966778899', tier: 'Bronze', points: 260, branch: 'Piassa', joinedAt: '2026-08-04T00:00:00Z', orders: 2, spendEtb: 670, lastOrderAt: '2026-08-06T10:40:00Z' }, ] export let faqs: FaqArticle[] = [ { id: 'faq-1', category: 'Orders', title_en: 'How do I confirm a branch order?', title_am: 'የቅርንጫፍ ትዕዛዝን እንዴት አረጋግጣለሁ?', body_en: 'Open Orders, select the order for your branch, verify stock, then Confirm. For delivery, assign a rider from the order detail.', body_am: 'ትዕዛዞችን ይክፈቱ፣ የቅርንጫፍዎን ትዕዛዝ ይምረጡ፣ ክምችት ያረጋግጡ፣ ከዚያ አረጋግጥ ይጫኑ።', published: true, updatedAt: '2026-08-01T00:00:00Z', views: 1284, }, { id: 'faq-2', category: 'Prescriptions', title_en: 'Controlled substance workflow', title_am: 'የቁጥጥር ንጥረ ነገር ሂደት', body_en: 'Flag controlled items during review. Approval creates a dispensing record with batch and pharmacist stamp.', body_am: 'በግምገማ ጊዜ ቁጥጥር የሚደረግባቸውን ንጥረ ነገሮች ምልክት ያድርጉ።', published: true, updatedAt: '2026-08-02T00:00:00Z', views: 962, }, { id: 'faq-3', category: 'Stock', title_en: 'Resolving an ERP quantity mismatch', title_am: 'የERP መጠን አለመጣጣምን መፍታት', body_en: 'Open Stock, filter by ERP mismatch, then recount the shelf before you resolve to the ERP figure. Every resolution is written to the audit log.', body_am: 'ክምችትን ይክፈቱ፣ በERP አለመጣጣም ያጣሩ፣ ከዚያ ወደ ERP አኃዝ ከመፍታት በፊት መደርደሪያውን ይቁጠሩ።', published: true, updatedAt: '2026-08-03T00:00:00Z', views: 741, }, { id: 'faq-4', category: 'Payments', title_en: 'Which payment gateways can customers use?', title_am: 'ደንበኞች የትኞቹን የክፍያ መንገዶች ሊጠቀሙ ይችላሉ?', body_en: 'Chapa, Telebirr, M-Pesa and cash on delivery. Finance enables or disables each gateway from Finance → Gateways.', body_am: 'ቻፓ፣ ቴሌብር፣ ኤም-ፔሳ እና በመላኪያ ጊዜ ክፍያ። ፋይናንስ እያንዳንዱን መንገድ ማንቃት ወይም ማጥፋት ይችላል።', published: true, updatedAt: '2026-08-04T00:00:00Z', views: 508, }, { id: 'faq-5', category: 'B2B', title_en: 'Onboarding a corporate account', title_am: 'የድርጅት መዝገብ ማስገባት', body_en: 'Collect the TIN and billing contact, then raise the organisation for finance approval. Credit limits stay at zero until KYC clears.', body_am: 'የTIN እና የክፍያ አድራሻ ይሰብስቡ፣ ከዚያ ድርጅቱን ለፋይናንስ ማጽደቅ ያቅርቡ።', published: false, updatedAt: '2026-08-05T00:00:00Z', views: 0, }, { id: 'faq-6', category: 'Orders', title_en: 'What happens when a delivery fails?', title_am: 'መላኪያ ሲከሽፍ ምን ይሆናል?', body_en: 'The rider marks the trip failed from the Telegram bot, the order returns to the dispatch queue, and operations calls the customer before a second attempt.', body_am: 'መላኪያው ሲከሽፍ ትዕዛዙ ወደ የመላኪያ ወረፋ ይመለሳል፣ ኦፕሬሽንም ደንበኛውን ይደውላል።', published: false, updatedAt: '2026-08-06T00:00:00Z', views: 0, }, ] export type AuditCategory = | 'prescriptions' | 'orders' | 'stock' | 'procurement' | 'organisations' | 'finance' | 'marketing' | 'migrations' | 'settings' | 'security' export type AuditEvent = { id: string at: string actor: string actorRole: StaffRole action: string category: AuditCategory target: string targetLabel: string ip: string userAgent: string outcome: 'success' | 'failed' /** Flagged for the security review queue (permissions, overrides, controlled substances). */ security?: boolean before?: Record | null after?: Record | null note?: string } const CHROME_MAC = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0 Safari/537.36' const CHROME_WIN = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0 Safari/537.36' const SAFARI_IOS = 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1' const FIREFOX_LINUX = 'Mozilla/5.0 (X11; Linux x86_64; rv:135.0) Gecko/20100101 Firefox/135.0' export const auditLog: AuditEvent[] = [ { id: 'a-1032', at: '2026-08-06T10:12:00Z', actor: 'Hana Pharmacist', actorRole: 'pharmacist', action: 'rx.approve', category: 'prescriptions', target: 'rx-1001', targetLabel: 'Abebe Kebede · Amoxicillin 500mg', ip: '196.189.44.12', userAgent: CHROME_MAC, outcome: 'success', before: { status: 'under_review', approvedBy: null }, after: { status: 'approved', approvedBy: 'Hana Pharmacist', batch: 'B2411' }, }, { id: 'a-1031', at: '2026-08-06T10:04:00Z', actor: 'Abel Operations', actorRole: 'operations', action: 'dispatch.assign_rider', category: 'orders', target: 'ord-5004', targetLabel: 'Netsanet D. · Gerji Mebrat Hail', ip: '196.189.44.31', userAgent: CHROME_WIN, outcome: 'success', before: { assignedRiderId: null, status: 'confirmed' }, after: { assignedRiderId: 'rd-1', status: 'out_for_delivery' }, }, { id: 'a-1030', at: '2026-08-06T09:52:00Z', actor: 'Lidya Marketing', actorRole: 'marketing_manager', action: 'campaign.send', category: 'marketing', target: 'cmp-201', targetLabel: 'Aug refill nudge → Chronic refill', ip: '196.189.44.58', userAgent: CHROME_MAC, outcome: 'success', before: { status: 'scheduled', recipients: 1280 }, after: { status: 'sending', recipients: 1280, channel: 'SMS' }, }, { id: 'a-1029', at: '2026-08-06T09:40:00Z', actor: 'Yonas Finance', actorRole: 'finance', action: 'org.approve', category: 'organisations', target: 'org-1', targetLabel: 'Horizon Bank', ip: '196.189.44.77', userAgent: CHROME_WIN, outcome: 'success', before: { status: 'pending', creditLimitEtb: 250000 }, after: { status: 'active', creditLimitEtb: 500000 }, }, { id: 'a-1028', at: '2026-08-06T09:21:00Z', actor: 'Super Admin', actorRole: 'super_admin', action: 'auth.role_change', category: 'security', target: 'tm-ops', targetLabel: 'Abel Operations', ip: '196.189.44.2', userAgent: CHROME_MAC, outcome: 'success', security: true, before: { role: 'operations', modules: 6 }, after: { role: 'operations', modules: 7, added: ['migrations'] }, note: 'Temporary elevation for the Q3 branch data import.', }, { id: 'a-1027', at: '2026-08-06T09:05:00Z', actor: 'Dawit Stock', actorRole: 'stock_manager', action: 'migration.commit', category: 'migrations', target: 'mig-1048', targetLabel: 'stock-cmc-aug.csv', ip: '196.189.44.19', userAgent: FIREFOX_LINUX, outcome: 'success', before: { rowsPending: 412 }, after: { rowsImported: 404, rowsFailed: 8 }, }, { id: 'a-1026', at: '2026-08-06T08:47:00Z', actor: 'Tigist Alemu', actorRole: 'pharmacist', action: 'auth.login_failed', category: 'security', target: 'tm-pharmacist', targetLabel: 'pharmacist@gishen.et', ip: '41.86.190.204', userAgent: SAFARI_IOS, outcome: 'failed', security: true, note: 'Third consecutive failure from an unrecognised network. Account throttled for 15 minutes.', }, { id: 'a-1025', at: '2026-08-06T08:30:00Z', actor: 'Hana Pharmacist', actorRole: 'pharmacist', action: 'order.confirm', category: 'orders', target: 'ord-5001', targetLabel: 'Abebe Kebede · Bole Branch', ip: '196.189.44.12', userAgent: CHROME_MAC, outcome: 'success', before: { status: 'pending', rxApproved: false }, after: { status: 'confirmed', rxApproved: true }, }, { id: 'a-1024', at: '2026-08-06T08:05:00Z', actor: 'Dawit Stock', actorRole: 'stock_manager', action: 'stock.resolve_discrepancy', category: 'stock', target: 'AMX-500', targetLabel: 'Amoxicillin 500mg · Bole Branch', ip: '196.189.44.19', userAgent: FIREFOX_LINUX, outcome: 'success', before: { platformQty: 120, erpQty: 118 }, after: { platformQty: 118, erpQty: 118, adjustment: -2 }, }, { id: 'a-1023', at: '2026-08-06T07:58:00Z', actor: 'Sara Procurement', actorRole: 'procurement', action: 'procurement.raise_request', category: 'procurement', target: 'pr-301', targetLabel: 'Metformin 850mg × 500 · CMC Branch', ip: '196.189.44.63', userAgent: CHROME_WIN, outcome: 'success', before: null, after: { status: 'open', qty: 500, priority: 'high' }, }, { id: 'a-1022', at: '2026-08-06T07:31:00Z', actor: 'Abel Operations', actorRole: 'operations', action: 'order.cancel', category: 'orders', target: 'ord-4996', targetLabel: 'Guest · 0912004411', ip: '196.189.44.31', userAgent: CHROME_WIN, outcome: 'success', before: { status: 'picking' }, after: { status: 'cancelled', reason: 'Customer unreachable after three attempts' }, }, { id: 'a-1021', at: '2026-08-06T07:02:00Z', actor: 'Super Admin', actorRole: 'super_admin', action: 'settings.update', category: 'settings', target: 'platform.retention', targetLabel: 'Data retention policy', ip: '196.189.44.2', userAgent: CHROME_MAC, outcome: 'success', before: { retentionDays: 180 }, after: { retentionDays: 365 }, }, { id: 'a-1020', at: '2026-08-05T17:44:00Z', actor: 'Yonas Finance', actorRole: 'finance', action: 'finance.settlement_reconcile', category: 'finance', target: 'st-chapa-0805', targetLabel: 'Chapa · 05 Aug 2026', ip: '196.189.44.77', userAgent: CHROME_WIN, outcome: 'success', before: { status: 'pending', varianceEtb: 240 }, after: { status: 'reconciled', varianceEtb: 0 }, }, { id: 'a-1019', at: '2026-08-05T16:20:00Z', actor: 'Hana Pharmacist', actorRole: 'pharmacist', action: 'rx.dispense_controlled', category: 'prescriptions', target: 'rx-1003', targetLabel: 'Tigist Alemu · Tramadol 50mg', ip: '196.189.44.12', userAgent: CHROME_MAC, outcome: 'success', security: true, before: { status: 'approved', dispensed: false }, after: { status: 'dispensed', dispensed: true, witness: 'Super Admin' }, note: 'Controlled substance — dual sign-off recorded.', }, { id: 'a-1018', at: '2026-08-05T15:02:00Z', actor: 'Lidya Marketing', actorRole: 'marketing_manager', action: 'segment.update', category: 'marketing', target: 'seg-lapsed', targetLabel: 'Lapsed buyers', ip: '196.189.44.58', userAgent: CHROME_MAC, outcome: 'success', before: { window: '90 days', size: 812 }, after: { window: '120 days', size: 640 }, }, { id: 'a-1017', at: '2026-08-05T14:11:00Z', actor: 'Yonas Finance', actorRole: 'finance', action: 'org.credit_limit_change', category: 'organisations', target: 'org-2', targetLabel: 'Green NGO Ethiopia', ip: '196.189.44.77', userAgent: CHROME_WIN, outcome: 'failed', note: 'Rejected — organisation is still pending KYC verification.', before: { creditLimitEtb: 100000 }, after: null, }, { id: 'a-1016', at: '2026-08-05T13:35:00Z', actor: 'Dawit Stock', actorRole: 'stock_manager', action: 'stock.transfer', category: 'stock', target: 'VIT-C', targetLabel: 'Vitamin C 1000mg · Bole → CMC', ip: '196.189.44.19', userAgent: FIREFOX_LINUX, outcome: 'success', before: { boleQty: 260, cmcQty: 20 }, after: { boleQty: 200, cmcQty: 80 }, }, { id: 'a-1015', at: '2026-08-05T11:48:00Z', actor: 'Sara Procurement', actorRole: 'procurement', action: 'procurement.mark_ordered', category: 'procurement', target: 'pr-302', targetLabel: 'Paracetamol 500mg × 1000', ip: '196.189.44.63', userAgent: CHROME_WIN, outcome: 'success', before: { status: 'open' }, after: { status: 'ordered', po: 'PO-891', supplier: 'Ethio Pharma Import' }, }, { id: 'a-1014', at: '2026-08-05T10:12:00Z', actor: 'Super Admin', actorRole: 'super_admin', action: 'auth.permission_override', category: 'security', target: 'tm-marketing', targetLabel: 'Lidya Marketing', ip: '196.189.44.2', userAgent: CHROME_MAC, outcome: 'success', security: true, before: { modules: ['marketing', 'crm', 'loyalty', 'campaigns'] }, after: { modules: ['marketing', 'crm', 'loyalty', 'campaigns', 'analytics'] }, }, { id: 'a-1013', at: '2026-08-05T09:30:00Z', actor: 'Abel Operations', actorRole: 'operations', action: 'migration.commit', category: 'migrations', target: 'mig-1046', targetLabel: 'riders-august.csv', ip: '196.189.44.31', userAgent: CHROME_WIN, outcome: 'success', before: { rowsPending: 12 }, after: { rowsImported: 12, rowsFailed: 0 }, }, { id: 'a-1012', at: '2026-08-04T16:55:00Z', actor: 'Hana Pharmacist', actorRole: 'pharmacist', action: 'rx.query', category: 'prescriptions', target: 'rx-1002', targetLabel: 'Marta Hailu · Metformin 850mg', ip: '196.189.44.12', userAgent: SAFARI_IOS, outcome: 'success', before: { status: 'submitted' }, after: { status: 'queried', query: 'Prescriber signature is illegible — resubmit a clearer scan.' }, }, { id: 'a-1011', at: '2026-08-04T15:22:00Z', actor: 'Yonas Finance', actorRole: 'finance', action: 'finance.gateway_toggle', category: 'finance', target: 'gw-mpesa', targetLabel: 'M-Pesa gateway', ip: '196.189.44.77', userAgent: CHROME_WIN, outcome: 'success', security: true, before: { enabled: false }, after: { enabled: true, feePercent: 2 }, }, { id: 'a-1010', at: '2026-08-04T14:03:00Z', actor: 'Dawit Stock', actorRole: 'stock_manager', action: 'stock.adjust', category: 'stock', target: 'MTF-850', targetLabel: 'Metformin 850mg · CMC Branch', ip: '196.189.44.19', userAgent: FIREFOX_LINUX, outcome: 'success', before: { qty: 14 }, after: { qty: 8, reason: 'Damaged in transit — six blisters written off' }, }, { id: 'a-1009', at: '2026-08-04T12:40:00Z', actor: 'Super Admin', actorRole: 'super_admin', action: 'auth.login_failed', category: 'security', target: 'tm-admin', targetLabel: 'admin@gishen.et', ip: '102.213.9.88', userAgent: CHROME_WIN, outcome: 'failed', security: true, note: 'Login attempt from an IP outside Ethiopia — blocked by geo policy.', }, { id: 'a-1008', at: '2026-08-04T11:15:00Z', actor: 'Lidya Marketing', actorRole: 'marketing_manager', action: 'campaign.schedule', category: 'marketing', target: 'cmp-204', targetLabel: 'Gold tier perks → High-value households', ip: '196.189.44.58', userAgent: CHROME_MAC, outcome: 'success', before: { status: 'draft' }, after: { status: 'scheduled', scheduledFor: '2026-08-07T06:00:00Z' }, }, { id: 'a-1007', at: '2026-08-04T09:28:00Z', actor: 'Abel Operations', actorRole: 'operations', action: 'order.refund', category: 'orders', target: 'ord-4988', targetLabel: 'Selam G. · Piassa Branch', ip: '196.189.44.31', userAgent: CHROME_WIN, outcome: 'success', before: { paid: true, refundedEtb: 0 }, after: { paid: true, refundedEtb: 640, reason: 'Item out of stock at pickup' }, }, { id: 'a-1006', at: '2026-08-03T16:10:00Z', actor: 'Sara Procurement', actorRole: 'procurement', action: 'procurement.receive', category: 'procurement', target: 'pr-303', targetLabel: 'Amoxicillin 500mg × 300', ip: '196.189.44.63', userAgent: CHROME_WIN, outcome: 'success', before: { status: 'ordered' }, after: { status: 'received', po: 'PO-884', batch: 'B2411' }, }, { id: 'a-1005', at: '2026-08-03T13:47:00Z', actor: 'Super Admin', actorRole: 'super_admin', action: 'settings.branch_update', category: 'settings', target: 'br-piassa', targetLabel: 'Piassa Branch', ip: '196.189.44.2', userAgent: CHROME_MAC, outcome: 'success', before: { openingHours: '08:00–20:00' }, after: { openingHours: '07:30–21:00' }, }, { id: 'a-1004', at: '2026-08-03T10:05:00Z', actor: 'Dawit Stock', actorRole: 'stock_manager', action: 'migration.validate_failed', category: 'migrations', target: 'mig-1044', targetLabel: 'catalog-q3.csv', ip: '196.189.44.19', userAgent: FIREFOX_LINUX, outcome: 'failed', before: { rowsTotal: 1840 }, after: { rowsImported: 0, rowsFailed: 1840, reason: 'Required column "sku" was not mapped' }, }, { id: 'a-1003', at: '2026-08-02T15:33:00Z', actor: 'Yonas Finance', actorRole: 'finance', action: 'finance.invoice_issue', category: 'finance', target: 'org-1', targetLabel: 'Horizon Bank · inv-881', ip: '196.189.44.77', userAgent: CHROME_WIN, outcome: 'success', before: null, after: { invoice: 'inv-881', amountEtb: 32000, period: 'Aug 2026' }, }, { id: 'a-1002', at: '2026-08-02T11:12:00Z', actor: 'Hana Pharmacist', actorRole: 'pharmacist', action: 'rx.reject', category: 'prescriptions', target: 'rx-0994', targetLabel: 'Guest · Diazepam 5mg', ip: '196.189.44.12', userAgent: CHROME_MAC, outcome: 'success', security: true, before: { status: 'under_review' }, after: { status: 'rejected', reason: 'Prescriber not on the national register' }, }, { id: 'a-1001', at: '2026-08-01T14:20:00Z', actor: 'Super Admin', actorRole: 'super_admin', action: 'auth.user_create', category: 'security', target: 'tm-marketing', targetLabel: 'Lidya Marketing', ip: '196.189.44.2', userAgent: CHROME_MAC, outcome: 'success', security: true, before: null, after: { role: 'marketing_manager', email: 'marketing@gishen.et' }, }, { id: 'a-1000', at: '2026-08-01T09:02:00Z', actor: 'Abel Operations', actorRole: 'operations', action: 'dispatch.rider_link', category: 'orders', target: 'rd-3', targetLabel: 'Samuel Rider · @sam_gishen', ip: '196.189.44.31', userAgent: CHROME_WIN, outcome: 'success', before: { telegramLinked: false }, after: { telegramLinked: true, telegramId: '@sam_gishen' }, }, ] export const importTemplates = [ { id: 'stock', label: 'Stock / inventory', fields: ['sku', 'barcode', 'branchId', 'qty', 'batch', 'expiry', 'cost', 'sell'] }, { id: 'catalog', label: 'Catalog products', fields: ['sku', 'name', 'strength', 'form', 'category', 'rxFlag', 'manufacturer'] }, { id: 'hr', label: 'HR / org members', fields: ['orgId', 'phone', 'name', 'department', 'plan', 'memberId'] }, { id: 'branches', label: 'Branches', fields: ['id', 'name', 'zone', 'phone', 'lat', 'lng'] }, { id: 'riders', label: 'Delivery people', fields: ['name', 'phone', 'telegramId', 'branchId', 'zone'] }, ] export type OrderLine = { sku: string; name: string; qty: number; unitEtb: number } export const orderLines: Record = { 'ord-5001': [ { sku: 'AMX-500', name: 'Amoxicillin 500mg', qty: 21, unitEtb: 18 }, { sku: 'PCM-500', name: 'Paracetamol 500mg', qty: 10, unitEtb: 8 }, { sku: 'VIT-C', name: 'Vitamin C 1000mg', qty: 2, unitEtb: 120 }, ], 'ord-5002': [ { sku: 'PCM-500', name: 'Paracetamol 500mg', qty: 20, unitEtb: 8 }, { sku: 'VIT-C', name: 'Vitamin C 1000mg', qty: 1, unitEtb: 120 }, ], 'ord-5003': [ { sku: 'MTF-850', name: 'Metformin 850mg', qty: 120, unitEtb: 22 }, { sku: 'AMX-500', name: 'Amoxicillin 500mg', qty: 40, unitEtb: 18 }, ], 'ord-5004': [ { sku: 'PCM-500', name: 'Paracetamol 500mg', qty: 30, unitEtb: 8 }, { sku: 'AMX-500', name: 'Amoxicillin 500mg', qty: 14, unitEtb: 18 }, ], } export const orderTimeline: Record = { 'ord-5001': [ { id: '1', at: '2026-08-06T08:30:00Z', title: 'Order placed', detail: 'Web · paid via Chapa', tone: 'success' }, { id: '2', at: '2026-08-06T08:32:00Z', title: 'Rx linked', detail: 'rx-1001 approved', tone: 'success' }, { id: '3', at: '2026-08-06T08:40:00Z', title: 'Branch confirmed', detail: 'Bole pharmacist', tone: 'default' }, { id: '4', at: '2026-08-06T09:05:00Z', title: 'Awaiting rider', detail: 'Delivery zone Bole', tone: 'warning' }, ], 'ord-5002': [ { id: '1', at: '2026-08-06T09:00:00Z', title: 'Order placed', detail: 'Telegram guest', tone: 'success' }, { id: '2', at: '2026-08-06T09:10:00Z', title: 'Picking started', detail: 'Bole counter', tone: 'default' }, ], 'ord-5003': [ { id: '1', at: '2026-08-06T10:00:00Z', title: 'B2B order placed', detail: 'Horizon Bank credit', tone: 'success' }, { id: '2', at: '2026-08-06T10:05:00Z', title: 'Finance approved credit draw', tone: 'success' }, { id: '3', at: '2026-08-06T10:15:00Z', title: 'Confirmed for delivery', tone: 'default' }, ], 'ord-5004': [ { id: '1', at: '2026-08-06T07:15:00Z', title: 'Order placed', detail: 'Mobile app', tone: 'success' }, { id: '2', at: '2026-08-06T07:40:00Z', title: 'Rider assigned', detail: 'Kidus Rider', tone: 'default' }, { id: '3', at: '2026-08-06T08:00:00Z', title: 'Out for delivery', detail: 'Telegram bot notified', tone: 'warning' }, ], } export const stockMovements: Record = { 'AMX-500': [ { id: 'm1', at: '2026-08-06T08:00:00Z', type: 'sale', qty: -21, note: 'ord-5001' }, { id: 'm2', at: '2026-08-05T14:00:00Z', type: 'receive', qty: 100, note: 'PO-884' }, { id: 'm3', at: '2026-08-04T11:00:00Z', type: 'adjust', qty: -2, note: 'ERP sync discrepancy' }, ], 'PCM-500': [ { id: 'm1', at: '2026-08-06T09:00:00Z', type: 'sale', qty: -20, note: 'ord-5002' }, { id: 'm2', at: '2026-08-03T10:00:00Z', type: 'receive', qty: 200, note: 'PO-871' }, ], 'MTF-850': [ { id: 'm1', at: '2026-08-06T10:00:00Z', type: 'reserve', qty: -120, note: 'ord-5003 B2B' }, { id: 'm2', at: '2026-07-28T09:00:00Z', type: 'receive', qty: 80, note: 'PO-840' }, ], 'VIT-C': [ { id: 'm1', at: '2026-08-01T12:00:00Z', type: 'receive', qty: 200, note: 'PO-860' }, { id: 'm2', at: '2026-08-06T08:30:00Z', type: 'sale', qty: -2, note: 'ord-5001' }, ], 'AMD-5': [ { id: 'm1', at: '2026-08-05T13:10:00Z', type: 'sale', qty: -60, note: 'ord-5009' }, { id: 'm2', at: '2026-07-30T10:20:00Z', type: 'sale', qty: -120, note: 'ord-5019 B2B' }, { id: 'm3', at: '2026-07-25T09:00:00Z', type: 'receive', qty: 300, note: 'PO-878' }, ], 'CTZ-10': [ { id: 'm1', at: '2026-08-06T06:40:00Z', type: 'sale', qty: -20, note: 'ord-5005' }, { id: 'm2', at: '2026-08-02T10:15:00Z', type: 'sale', qty: -20, note: 'ord-5014' }, { id: 'm3', at: '2026-07-24T08:00:00Z', type: 'receive', qty: 240, note: 'PO-869' }, { id: 'm4', at: '2026-07-21T11:00:00Z', type: 'adjust', qty: -2, note: 'ERP sync discrepancy' }, ], 'IBU-400': [ { id: 'm1', at: '2026-08-06T07:50:00Z', type: 'sale', qty: -20, note: 'ord-5006' }, { id: 'm2', at: '2026-08-01T16:20:00Z', type: 'sale', qty: -20, note: 'ord-5015' }, { id: 'm3', at: '2026-07-19T09:00:00Z', type: 'receive', qty: 300, note: 'PO-862' }, ], 'ORS-20': [ { id: 'm1', at: '2026-08-06T10:40:00Z', type: 'reserve', qty: -4, note: 'ord-5007' }, { id: 'm2', at: '2026-07-23T11:15:00Z', type: 'sale', qty: -40, note: 'ord-5029 B2B' }, { id: 'm3', at: '2026-07-15T08:00:00Z', type: 'receive', qty: 120, note: 'PO-856' }, ], 'HYC-1': [ { id: 'm1', at: '2026-08-04T15:25:00Z', type: 'sale', qty: -2, note: 'ord-5010' }, { id: 'm2', at: '2026-07-17T17:40:00Z', type: 'sale', qty: -2, note: 'ord-5037' }, { id: 'm3', at: '2026-07-12T10:00:00Z', type: 'receive', qty: 80, note: 'PO-851' }, ], 'AZI-250': [ { id: 'm1', at: '2026-08-04T11:05:00Z', type: 'sale', qty: -6, note: 'ord-5011' }, { id: 'm2', at: '2026-07-29T15:40:00Z', type: 'sale', qty: -6, note: 'ord-5020' }, { id: 'm3', at: '2026-07-18T09:00:00Z', type: 'receive', qty: 60, note: 'PO-860' }, ], 'SLB-INH': [ { id: 'm1', at: '2026-08-06T10:40:00Z', type: 'reserve', qty: -1, note: 'ord-5007' }, { id: 'm2', at: '2026-07-24T18:30:00Z', type: 'sale', qty: -2, note: 'ord-5028' }, { id: 'm3', at: '2026-07-16T08:00:00Z', type: 'receive', qty: 24, note: 'PO-857' }, ], 'FEF-200': [ { id: 'm1', at: '2026-08-03T09:35:00Z', type: 'sale', qty: -30, note: 'ord-5012' }, { id: 'm2', at: '2026-07-26T09:25:00Z', type: 'sale', qty: -30, note: 'ord-5025' }, { id: 'm3', at: '2026-07-13T09:00:00Z', type: 'receive', qty: 300, note: 'PO-853' }, ], 'TRM-50': [ { id: 'm1', at: '2026-08-05T10:20:00Z', type: 'sale', qty: -6, note: 'ord-5013 · rx-1015 stamp' }, { id: 'm2', at: '2026-07-20T08:00:00Z', type: 'receive', qty: 30, note: 'PO-866 · controlled register signed' }, ], 'DZP-5': [ { id: 'm1', at: '2026-08-02T14:45:00Z', type: 'sale', qty: -4, note: 'ord-5018 · rx-1008 stamp' }, { id: 'm2', at: '2026-07-20T08:00:00Z', type: 'receive', qty: 30, note: 'PO-866 · controlled register signed' }, ], } export let procurementRequests = [ { id: 'pr-301', sku: 'MTF-850', name: 'Metformin 850mg', qty: 500, branchId: 'br-cmc', status: 'open' as const, priority: 'high' as const, requestedBy: 'Sara Procurement', createdAt: '2026-08-06T07:00:00Z', notes: 'CMC below reorder point', }, { id: 'pr-302', sku: 'PCM-500', name: 'Paracetamol 500mg', qty: 1000, branchId: 'br-bole', status: 'ordered' as const, priority: 'medium' as const, requestedBy: 'Dawit Stock', createdAt: '2026-08-05T11:00:00Z', notes: 'Weekend demand buffer', }, { id: 'pr-303', sku: 'AMX-500', name: 'Amoxicillin 500mg', qty: 300, branchId: 'br-bole', status: 'received' as const, priority: 'low' as const, requestedBy: 'Sara Procurement', createdAt: '2026-08-01T09:00:00Z', notes: 'PO-884 received', }, ] export const orgMembers: Record = { 'org-1': [ { id: 'om-1', name: 'Hiwot Bekele', dept: 'Retail banking', plan: 'Staff+', avatarUrl: portraitFor('Hiwot Bekele') }, { id: 'om-2', name: 'Daniel Tadesse', dept: 'IT', plan: 'Staff', avatarUrl: portraitFor('Daniel Tadesse') }, { id: 'om-3', name: 'Sara Worku', dept: 'HR', plan: 'Dependent', avatarUrl: portraitFor('Sara Worku') }, ], // Placeholder seat on an org still pending KYC — no real person behind it, so no photo. 'org-2': [{ id: 'om-4', name: 'Pending admin', dept: 'Operations', plan: 'Staff' }], } export const orgInvoices: Record = { 'org-1': [ { id: 'inv-880', period: 'Jul 2026', amountEtb: 48000, status: 'paid' }, { id: 'inv-881', period: 'Aug 2026 (MTD)', amountEtb: 32000, status: 'open' }, ], 'org-2': [], } export const customerOrders: Record = { c1: ['ord-5001'], c2: ['ord-5002'], c3: [], c4: ['ord-5004'], c5: ['ord-5003'], c6: [], } export const settlementsDetailed = [ { id: 'st-chapa-0805', channel: 'Chapa', date: '2026-08-05', volumeEtb: 84200, status: 'reconciled', txnCount: 186, feesEtb: 1684 }, { id: 'st-tele-0805', channel: 'Telebirr', date: '2026-08-05', volumeEtb: 52100, status: 'reconciled', txnCount: 142, feesEtb: 1042 }, { id: 'st-cod-0805', channel: 'COD', date: '2026-08-05', volumeEtb: 18300, status: 'pending', txnCount: 28, feesEtb: 0 }, { id: 'st-mpesa-0805', channel: 'M-Pesa', date: '2026-08-05', volumeEtb: 9400, status: 'reconciled', txnCount: 19, feesEtb: 188 }, ] export type LoyaltyLedgerEntry = { id: string customerId: string customerName: string activity: string points: number orderId?: string branch: string at: string } export const loyaltyActivityLabels: Record = { purchase: 'Purchase spend', refill: 'Prescription refill', app_order: 'Mobile app order', referral: 'Friend referral', review: 'Product review', birthday: 'Birthday bonus', health_check: 'Health-check attendance', org_signup: 'B2B member signup', redemption: 'Points redemption', } export const loyaltyLedger: LoyaltyLedgerEntry[] = [ { id: 'lp-1001', customerId: 'c1', customerName: 'Abebe Kebede', activity: 'purchase', points: 125, orderId: 'ord-5001', branch: 'Bole', at: '2026-08-06T08:35:00Z' }, { id: 'lp-1002', customerId: 'c1', customerName: 'Abebe Kebede', activity: 'review', points: 50, branch: 'Bole', at: '2026-08-04T16:20:00Z' }, { id: 'lp-1003', customerId: 'c1', customerName: 'Abebe Kebede', activity: 'refill', points: 75, branch: 'Bole', at: '2026-07-29T10:05:00Z' }, { id: 'lp-1004', customerId: 'c1', customerName: 'Abebe Kebede', activity: 'redemption', points: -200, branch: 'Bole', at: '2026-07-22T12:00:00Z' }, { id: 'lp-1005', customerId: 'c1', customerName: 'Abebe Kebede', activity: 'purchase', points: 180, branch: 'Bole', at: '2026-07-14T09:30:00Z' }, { id: 'lp-1006', customerId: 'c1', customerName: 'Abebe Kebede', activity: 'referral', points: 150, branch: 'Bole', at: '2026-07-02T11:10:00Z' }, { id: 'lp-1007', customerId: 'c1', customerName: 'Abebe Kebede', activity: 'birthday', points: 250, branch: 'Bole', at: '2026-06-18T07:00:00Z' }, { id: 'lp-1008', customerId: 'c2', customerName: 'Marta Hailu', activity: 'purchase', points: 42, orderId: 'ord-5002', branch: 'Bole', at: '2026-08-06T09:05:00Z' }, { id: 'lp-1009', customerId: 'c2', customerName: 'Marta Hailu', activity: 'refill', points: 75, branch: 'Bole', at: '2026-08-03T14:40:00Z' }, { id: 'lp-1010', customerId: 'c2', customerName: 'Marta Hailu', activity: 'app_order', points: 100, branch: 'Bole', at: '2026-07-27T18:15:00Z' }, { id: 'lp-1011', customerId: 'c2', customerName: 'Marta Hailu', activity: 'redemption', points: -150, branch: 'Bole', at: '2026-07-19T13:25:00Z' }, { id: 'lp-1012', customerId: 'c2', customerName: 'Marta Hailu', activity: 'purchase', points: 96, branch: 'Bole', at: '2026-07-08T10:50:00Z' }, { id: 'lp-1013', customerId: 'c2', customerName: 'Marta Hailu', activity: 'health_check', points: 120, branch: 'Bole', at: '2026-06-25T09:15:00Z' }, { id: 'lp-1014', customerId: 'c3', customerName: 'Tigist Alemu', activity: 'purchase', points: 55, branch: 'CMC', at: '2026-08-05T15:30:00Z' }, { id: 'lp-1015', customerId: 'c3', customerName: 'Tigist Alemu', activity: 'review', points: 50, branch: 'CMC', at: '2026-07-31T17:05:00Z' }, { id: 'lp-1016', customerId: 'c3', customerName: 'Tigist Alemu', activity: 'refill', points: 75, branch: 'CMC', at: '2026-07-21T11:45:00Z' }, { id: 'lp-1017', customerId: 'c3', customerName: 'Tigist Alemu', activity: 'redemption', points: -100, branch: 'CMC', at: '2026-07-05T16:00:00Z' }, { id: 'lp-1018', customerId: 'c3', customerName: 'Tigist Alemu', activity: 'purchase', points: 64, branch: 'CMC', at: '2026-06-28T12:20:00Z' }, { id: 'lp-1019', customerId: 'c4', customerName: 'Netsanet Desta', activity: 'purchase', points: 89, orderId: 'ord-5004', branch: 'Bole', at: '2026-08-06T07:20:00Z' }, { id: 'lp-1020', customerId: 'c4', customerName: 'Netsanet Desta', activity: 'app_order', points: 100, branch: 'Bole', at: '2026-08-02T20:10:00Z' }, { id: 'lp-1021', customerId: 'c4', customerName: 'Netsanet Desta', activity: 'referral', points: 150, branch: 'Bole', at: '2026-07-25T08:35:00Z' }, { id: 'lp-1022', customerId: 'c4', customerName: 'Netsanet Desta', activity: 'redemption', points: -300, branch: 'Bole', at: '2026-07-11T15:50:00Z' }, { id: 'lp-1023', customerId: 'c4', customerName: 'Netsanet Desta', activity: 'purchase', points: 210, branch: 'Bole', at: '2026-06-30T13:05:00Z' }, { id: 'lp-1024', customerId: 'c4', customerName: 'Netsanet Desta', activity: 'refill', points: 75, branch: 'Bole', at: '2026-06-21T10:25:00Z' }, { id: 'lp-1025', customerId: 'c5', customerName: 'Hiwot Bekele', activity: 'purchase', points: 340, orderId: 'ord-5003', branch: 'CMC', at: '2026-08-06T10:10:00Z' }, { id: 'lp-1026', customerId: 'c5', customerName: 'Hiwot Bekele', activity: 'org_signup', points: 200, branch: 'CMC', at: '2026-07-30T09:00:00Z' }, { id: 'lp-1027', customerId: 'c5', customerName: 'Hiwot Bekele', activity: 'redemption', points: -250, branch: 'CMC', at: '2026-07-16T14:15:00Z' }, { id: 'lp-1028', customerId: 'c5', customerName: 'Hiwot Bekele', activity: 'health_check', points: 120, branch: 'CMC', at: '2026-06-24T08:45:00Z' }, { id: 'lp-1029', customerId: 'c6', customerName: 'Dawit Lemma', activity: 'purchase', points: 72, branch: 'Piassa', at: '2026-08-01T11:30:00Z' }, { id: 'lp-1030', customerId: 'c6', customerName: 'Dawit Lemma', activity: 'birthday', points: 250, branch: 'Piassa', at: '2026-07-23T07:00:00Z' }, { id: 'lp-1031', customerId: 'c6', customerName: 'Dawit Lemma', activity: 'review', points: 50, branch: 'Piassa', at: '2026-07-09T19:40:00Z' }, { id: 'lp-1032', customerId: 'c6', customerName: 'Dawit Lemma', activity: 'redemption', points: -120, branch: 'Piassa', at: '2026-06-26T16:30:00Z' }, ] export type LoyaltyActivity = { id: string name: string description: string points: number unit: string tierMultiplier?: string active: boolean } export const loyaltyActivities: LoyaltyActivity[] = [ { id: 'purchase', name: 'Purchase spend', description: 'Every paid order accrues points on the net basket value, all channels.', points: 1, unit: 'per 100 ETB', tierMultiplier: 'Gold ×1.5 · Silver ×1.2', active: true, }, { id: 'refill', name: 'Prescription refill on time', description: 'Chronic patients who refill inside the reminder window keep their streak.', points: 75, unit: 'flat per refill', active: true, }, { id: 'app_order', name: 'First mobile app order', description: 'One-off bonus to move Telegram and walk-in customers onto the app.', points: 100, unit: 'flat, once per customer', active: true, }, { id: 'referral', name: 'Friend referral', description: 'Awarded once the referred customer completes their first paid order.', points: 150, unit: 'flat per referral', tierMultiplier: 'Gold ×2', active: true, }, { id: 'review', name: 'Product review left', description: 'Verified purchase reviews only, capped at three rewarded reviews a month.', points: 50, unit: 'flat per review', active: true, }, { id: 'birthday', name: 'Birthday bonus', description: 'Issued on the customer birthday, expires after 30 days if unused.', points: 250, unit: 'flat per year', tierMultiplier: 'Gold ×1.5', active: true, }, { id: 'health_check', name: 'Health-check attendance', description: 'In-branch blood pressure and glucose screening campaigns.', points: 120, unit: 'flat per visit', active: true, }, { id: 'org_signup', name: 'B2B member signup', description: 'Organisation staff activating their pharmacy benefit for the first time.', points: 200, unit: 'flat per member', tierMultiplier: 'None', active: false, }, ] /* ------------------------------------------------------------------ * * Marketing — segments * ------------------------------------------------------------------ */ export type MarketingChannel = 'SMS' | 'Telegram' | 'Push' | 'Email' export type MarketingSegment = { id: string name: string criteria: string size: number channel: MarketingChannel status: 'active' | 'paused' | 'draft' /** Share of members who opened or clicked the last campaign, as a percentage. */ engagementRate: number lastUsedAt: string | null owner: string createdAt: string refresh: 'live' | 'daily' | 'manual' } export const marketingSegments: MarketingSegment[] = [ { id: 'seg-chronic', name: 'Chronic refill', criteria: 'Has ≥2 repeat prescriptions in 180d AND refill due within 14d', size: 1280, channel: 'SMS', status: 'active', engagementRate: 58, lastUsedAt: '2026-08-06T06:00:00Z', owner: 'Lidya Marketing', createdAt: '2026-03-02T09:00:00Z', refresh: 'live', }, { id: 'seg-bole', name: 'Bole catchment', criteria: 'Delivery zone = Bole OR preferred branch = Bole Branch', size: 940, channel: 'Telegram', status: 'active', engagementRate: 45, lastUsedAt: '2026-08-04T07:30:00Z', owner: 'Lidya Marketing', createdAt: '2026-03-18T09:00:00Z', refresh: 'daily', }, { id: 'seg-lapsed', name: 'Lapsed buyers', criteria: 'No paid order in 120d AND lifetime orders ≥1', size: 640, channel: 'SMS', status: 'active', engagementRate: 19, lastUsedAt: '2026-07-29T06:00:00Z', owner: 'Lidya Marketing', createdAt: '2026-04-05T09:00:00Z', refresh: 'daily', }, { id: 'seg-new30', name: 'New customers (30d)', criteria: 'First paid order within the last 30 days', size: 428, channel: 'Push', status: 'active', engagementRate: 54, lastUsedAt: '2026-08-02T10:00:00Z', owner: 'Lidya Marketing', createdAt: '2026-05-11T09:00:00Z', refresh: 'live', }, { id: 'seg-rxfollow', name: 'Rx follow-up', criteria: 'Prescription approved but not collected within 48h', size: 315, channel: 'Telegram', status: 'active', engagementRate: 66, lastUsedAt: '2026-08-05T12:00:00Z', owner: 'Hana Pharmacist', createdAt: '2026-06-01T09:00:00Z', refresh: 'live', }, { id: 'seg-highvalue', name: 'High-value households', criteria: 'Lifetime spend ≥ 25,000 ETB OR loyalty tier = Gold', size: 210, channel: 'Email', status: 'active', engagementRate: 70, lastUsedAt: '2026-07-21T08:00:00Z', owner: 'Lidya Marketing', createdAt: '2026-03-02T09:00:00Z', refresh: 'daily', }, { id: 'seg-institutional', name: 'Institutional accounts', criteria: 'Linked to an active organisation with credit ≥ 100,000 ETB', size: 18, channel: 'Email', status: 'paused', engagementRate: 78, lastUsedAt: null, owner: 'Yonas Finance', createdAt: '2026-04-22T09:00:00Z', refresh: 'manual', }, ] export type SegmentMember = { id: string name: string phone: string tier: string /** Present only for members who exist in the CRM customer table. */ customerId?: string joinedAt: string orders: number spendEtb: number lastOrderAt: string | null } export const segmentMembers: Record = { 'seg-chronic': [ { id: 'sm-1', name: 'Abebe Kebede', phone: '+251911223344', tier: 'Gold', customerId: 'c1', joinedAt: '2026-03-04T00:00:00Z', orders: 14, spendEtb: 28400, lastOrderAt: '2026-08-06T08:30:00Z' }, { id: 'sm-2', name: 'Marta Hailu', phone: '+251922334455', tier: 'Silver', customerId: 'c2', joinedAt: '2026-03-12T00:00:00Z', orders: 9, spendEtb: 11200, lastOrderAt: '2026-08-06T09:00:00Z' }, { id: 'sm-3', name: 'Tigist Alemu', phone: '+251933445566', tier: 'Bronze', customerId: 'c3', joinedAt: '2026-05-02T00:00:00Z', orders: 4, spendEtb: 3600, lastOrderAt: '2026-07-30T00:00:00Z' }, { id: 'sm-4', name: 'Bekele Tesfaye', phone: '+251944556677', tier: 'Silver', joinedAt: '2026-03-22T00:00:00Z', orders: 11, spendEtb: 15800, lastOrderAt: '2026-08-04T00:00:00Z' }, { id: 'sm-5', name: 'Almaz Girma', phone: '+251955667788', tier: 'Gold', joinedAt: '2026-04-01T00:00:00Z', orders: 18, spendEtb: 34200, lastOrderAt: '2026-08-05T00:00:00Z' }, { id: 'sm-6', name: 'Yohannes Desta', phone: '+251966778899', tier: 'Bronze', joinedAt: '2026-05-18T00:00:00Z', orders: 3, spendEtb: 2400, lastOrderAt: '2026-07-19T00:00:00Z' }, { id: 'sm-7', name: 'Hiwot Bekele', phone: '+251977889900', tier: 'Silver', joinedAt: '2026-04-14T00:00:00Z', orders: 8, spendEtb: 9600, lastOrderAt: '2026-08-01T00:00:00Z' }, { id: 'sm-8', name: 'Getachew Mola', phone: '+251988990011', tier: 'Bronze', joinedAt: '2026-06-08T00:00:00Z', orders: 2, spendEtb: 1800, lastOrderAt: '2026-07-11T00:00:00Z' }, ], 'seg-bole': [ { id: 'sm-9', name: 'Abebe Kebede', phone: '+251911223344', tier: 'Gold', customerId: 'c1', joinedAt: '2026-03-19T00:00:00Z', orders: 14, spendEtb: 28400, lastOrderAt: '2026-08-06T08:30:00Z' }, { id: 'sm-10', name: 'Marta Hailu', phone: '+251922334455', tier: 'Silver', customerId: 'c2', joinedAt: '2026-03-19T00:00:00Z', orders: 9, spendEtb: 11200, lastOrderAt: '2026-08-06T09:00:00Z' }, { id: 'sm-11', name: 'Netsanet Demeke', phone: '+251911445566', tier: 'Silver', joinedAt: '2026-04-02T00:00:00Z', orders: 7, spendEtb: 8900, lastOrderAt: '2026-08-06T07:15:00Z' }, { id: 'sm-12', name: 'Sofia Ahmed', phone: '+251912334455', tier: 'Bronze', joinedAt: '2026-05-27T00:00:00Z', orders: 3, spendEtb: 2100, lastOrderAt: '2026-07-25T00:00:00Z' }, { id: 'sm-13', name: 'Dawit Alemayehu', phone: '+251913445566', tier: 'Gold', joinedAt: '2026-03-30T00:00:00Z', orders: 21, spendEtb: 41000, lastOrderAt: '2026-08-05T00:00:00Z' }, { id: 'sm-14', name: 'Rahel Tadesse', phone: '+251914556677', tier: 'Silver', joinedAt: '2026-06-14T00:00:00Z', orders: 6, spendEtb: 7200, lastOrderAt: '2026-08-03T00:00:00Z' }, ], 'seg-lapsed': [ { id: 'sm-15', name: 'Tigist Alemu', phone: '+251933445566', tier: 'Bronze', customerId: 'c3', joinedAt: '2026-04-08T00:00:00Z', orders: 4, spendEtb: 3600, lastOrderAt: '2026-04-02T00:00:00Z' }, { id: 'sm-16', name: 'Solomon Wolde', phone: '+251915667788', tier: 'Bronze', joinedAt: '2026-04-08T00:00:00Z', orders: 1, spendEtb: 420, lastOrderAt: '2026-03-28T00:00:00Z' }, { id: 'sm-17', name: 'Meseret Kassa', phone: '+251916778899', tier: 'Silver', joinedAt: '2026-04-08T00:00:00Z', orders: 5, spendEtb: 6100, lastOrderAt: '2026-04-01T00:00:00Z' }, { id: 'sm-18', name: 'Fikru Assefa', phone: '+251917889900', tier: 'Bronze', joinedAt: '2026-05-01T00:00:00Z', orders: 2, spendEtb: 980, lastOrderAt: '2026-03-30T00:00:00Z' }, { id: 'sm-19', name: 'Zenebech Haile', phone: '+251918990011', tier: 'Bronze', joinedAt: '2026-05-01T00:00:00Z', orders: 1, spendEtb: 310, lastOrderAt: '2026-04-04T00:00:00Z' }, ], 'seg-new30': [ { id: 'sm-20', name: 'Kalkidan Berhe', phone: '+251919001122', tier: 'Bronze', joinedAt: '2026-07-18T00:00:00Z', orders: 1, spendEtb: 640, lastOrderAt: '2026-07-18T00:00:00Z' }, { id: 'sm-21', name: 'Eyob Mekonnen', phone: '+251920112233', tier: 'Bronze', joinedAt: '2026-07-24T00:00:00Z', orders: 2, spendEtb: 1420, lastOrderAt: '2026-08-02T00:00:00Z' }, { id: 'sm-22', name: 'Selam Getahun', phone: '+251921223344', tier: 'Bronze', joinedAt: '2026-08-01T00:00:00Z', orders: 1, spendEtb: 890, lastOrderAt: '2026-08-01T00:00:00Z' }, { id: 'sm-23', name: 'Mulugeta Fikadu', phone: '+251923334455', tier: 'Bronze', joinedAt: '2026-08-03T00:00:00Z', orders: 1, spendEtb: 1240, lastOrderAt: '2026-08-03T00:00:00Z' }, ], 'seg-rxfollow': [ { id: 'sm-24', name: 'Marta Hailu', phone: '+251922334455', tier: 'Silver', customerId: 'c2', joinedAt: '2026-08-04T00:00:00Z', orders: 9, spendEtb: 11200, lastOrderAt: '2026-08-06T09:00:00Z' }, { id: 'sm-25', name: 'Tigist Alemu', phone: '+251933445566', tier: 'Bronze', customerId: 'c3', joinedAt: '2026-08-05T00:00:00Z', orders: 4, spendEtb: 3600, lastOrderAt: '2026-07-30T00:00:00Z' }, { id: 'sm-26', name: 'Yeshi Lemma', phone: '+251924445566', tier: 'Silver', joinedAt: '2026-08-05T00:00:00Z', orders: 6, spendEtb: 7800, lastOrderAt: '2026-08-04T00:00:00Z' }, { id: 'sm-27', name: 'Tesfaye Negash', phone: '+251925556677', tier: 'Bronze', joinedAt: '2026-08-06T00:00:00Z', orders: 3, spendEtb: 2900, lastOrderAt: '2026-08-05T00:00:00Z' }, ], 'seg-highvalue': [ { id: 'sm-28', name: 'Abebe Kebede', phone: '+251911223344', tier: 'Gold', customerId: 'c1', joinedAt: '2026-03-02T00:00:00Z', orders: 14, spendEtb: 28400, lastOrderAt: '2026-08-06T08:30:00Z' }, { id: 'sm-29', name: 'Almaz Girma', phone: '+251955667788', tier: 'Gold', joinedAt: '2026-03-02T00:00:00Z', orders: 18, spendEtb: 34200, lastOrderAt: '2026-08-05T00:00:00Z' }, { id: 'sm-30', name: 'Dawit Alemayehu', phone: '+251913445566', tier: 'Gold', joinedAt: '2026-03-30T00:00:00Z', orders: 21, spendEtb: 41000, lastOrderAt: '2026-08-05T00:00:00Z' }, { id: 'sm-31', name: 'Genet Abera', phone: '+251926667788', tier: 'Gold', joinedAt: '2026-04-11T00:00:00Z', orders: 16, spendEtb: 26800, lastOrderAt: '2026-08-02T00:00:00Z' }, ], 'seg-institutional': [ { id: 'sm-32', name: 'Hiwot Bekele (Horizon Bank)', phone: '+251977889900', tier: 'Staff+', joinedAt: '2026-05-01T00:00:00Z', orders: 12, spendEtb: 48000, lastOrderAt: '2026-08-06T10:00:00Z' }, { id: 'sm-33', name: 'Daniel Tadesse (Horizon Bank)', phone: '+251927778899', tier: 'Staff', joinedAt: '2026-05-01T00:00:00Z', orders: 7, spendEtb: 21400, lastOrderAt: '2026-08-01T00:00:00Z' }, { id: 'sm-34', name: 'Sara Worku (Horizon Bank)', phone: '+251928889900', tier: 'Dependent', joinedAt: '2026-05-14T00:00:00Z', orders: 4, spendEtb: 9200, lastOrderAt: '2026-07-28T00:00:00Z' }, ], } /* ------------------------------------------------------------------ * * Marketing — campaigns * ------------------------------------------------------------------ */ export type Campaign = { id: string name: string channel: MarketingChannel segmentId: string segmentName: string status: 'draft' | 'scheduled' | 'sending' | 'sent' recipients: number delivered: number opened: number clicked: number redeemed: number bounced: number message: string createdBy: string createdAt: string sentAt: string | null scheduledFor: string | null budgetEtb: number } export const campaigns: Campaign[] = [ { id: 'cmp-201', name: 'Aug refill nudge', channel: 'SMS', segmentId: 'seg-chronic', segmentName: 'Chronic refill', status: 'sending', recipients: 1280, delivered: 1204, opened: 698, clicked: 241, redeemed: 88, bounced: 76, message: 'Your refill is due soon. Tap to reorder from Gishen — free delivery over 500 ETB.', createdBy: 'Lidya Marketing', createdAt: '2026-08-05T14:00:00Z', sentAt: '2026-08-06T06:00:00Z', scheduledFor: null, budgetEtb: 6400, }, { id: 'cmp-202', name: 'Bole weekend offer', channel: 'Telegram', segmentId: 'seg-bole', segmentName: 'Bole catchment', status: 'sent', recipients: 940, delivered: 902, opened: 388, clicked: 132, redeemed: 51, bounced: 38, message: '15% off vitamins and supplements at Bole Branch this weekend only.', createdBy: 'Lidya Marketing', createdAt: '2026-08-03T10:00:00Z', sentAt: '2026-08-04T07:30:00Z', scheduledFor: null, budgetEtb: 4200, }, { id: 'cmp-203', name: 'Win-back 20%', channel: 'SMS', segmentId: 'seg-lapsed', segmentName: 'Lapsed buyers', status: 'sent', recipients: 640, delivered: 618, opened: 141, clicked: 44, redeemed: 12, bounced: 22, message: 'We miss you. Here is 20% off your next Gishen order — valid for 14 days.', createdBy: 'Lidya Marketing', createdAt: '2026-07-28T09:00:00Z', sentAt: '2026-07-29T06:00:00Z', scheduledFor: null, budgetEtb: 3200, }, { id: 'cmp-204', name: 'Gold tier perks', channel: 'Email', segmentId: 'seg-highvalue', segmentName: 'High-value households', status: 'scheduled', recipients: 210, delivered: 0, opened: 0, clicked: 0, redeemed: 0, bounced: 0, message: 'Your Gold benefits for August: priority dispatch, 2× points, free consultations.', createdBy: 'Lidya Marketing', createdAt: '2026-08-04T11:15:00Z', sentAt: null, scheduledFor: '2026-08-07T06:00:00Z', budgetEtb: 1800, }, { id: 'cmp-205', name: 'Rx pickup reminder', channel: 'Telegram', segmentId: 'seg-rxfollow', segmentName: 'Rx follow-up', status: 'sent', recipients: 315, delivered: 311, opened: 214, clicked: 96, redeemed: 74, bounced: 4, message: 'Your approved prescription is ready for collection. We hold it for 72 hours.', createdBy: 'Hana Pharmacist', createdAt: '2026-08-05T09:00:00Z', sentAt: '2026-08-05T12:00:00Z', scheduledFor: null, budgetEtb: 900, }, { id: 'cmp-206', name: 'Ramadan wellness', channel: 'SMS', segmentId: 'seg-bole', segmentName: 'Bole catchment', status: 'sent', recipients: 1480, delivered: 1420, opened: 604, clicked: 188, redeemed: 63, bounced: 60, message: 'Ramadan wellness bundle: hydration salts, dates and multivitamins from 380 ETB.', createdBy: 'Lidya Marketing', createdAt: '2026-07-10T08:00:00Z', sentAt: '2026-07-12T05:30:00Z', scheduledFor: null, budgetEtb: 7400, }, { id: 'cmp-207', name: 'Corporate renewal', channel: 'Email', segmentId: 'seg-institutional', segmentName: 'Institutional accounts', status: 'sent', recipients: 18, delivered: 18, opened: 14, clicked: 9, redeemed: 6, bounced: 0, message: 'Your Gishen corporate agreement renews on 1 September. Review your usage report.', createdBy: 'Yonas Finance', createdAt: '2026-07-20T10:00:00Z', sentAt: '2026-07-21T08:00:00Z', scheduledFor: null, budgetEtb: 200, }, { id: 'cmp-208', name: 'Flu season prep', channel: 'Push', segmentId: 'seg-new30', segmentName: 'New customers (30d)', status: 'sent', recipients: 902, delivered: 874, opened: 402, clicked: 143, redeemed: 39, bounced: 28, message: 'Flu season is here. Stock up on paracetamol, zinc and vitamin C at Gishen.', createdBy: 'Lidya Marketing', createdAt: '2026-08-01T09:00:00Z', sentAt: '2026-08-02T10:00:00Z', scheduledFor: null, budgetEtb: 0, }, { id: 'cmp-209', name: 'Diabetes screening drive', channel: 'Telegram', segmentId: 'seg-chronic', segmentName: 'Chronic refill', status: 'draft', recipients: 1280, delivered: 0, opened: 0, clicked: 0, redeemed: 0, bounced: 0, message: 'Free glucose screening at CMC Branch every Saturday in August. Walk in any time.', createdBy: 'Lidya Marketing', createdAt: '2026-08-06T08:20:00Z', sentAt: null, scheduledFor: null, budgetEtb: 2600, }, { id: 'cmp-210', name: 'App download push', channel: 'Push', segmentId: 'seg-new30', segmentName: 'New customers (30d)', status: 'scheduled', recipients: 428, delivered: 0, opened: 0, clicked: 0, redeemed: 0, bounced: 0, message: 'Get 100 bonus points on your first order in the Gishen app.', createdBy: 'Lidya Marketing', createdAt: '2026-08-06T09:10:00Z', sentAt: null, scheduledFor: '2026-08-08T06:00:00Z', budgetEtb: 0, }, ] export type CampaignRecipient = { id: string name: string phone: string customerId?: string status: 'delivered' | 'opened' | 'clicked' | 'redeemed' | 'bounced' sentAt: string } export const campaignRecipients: Record = { 'cmp-201': [ { id: 'cr-1', name: 'Abebe Kebede', phone: '+251911223344', customerId: 'c1', status: 'redeemed', sentAt: '2026-08-06T06:00:00Z' }, { id: 'cr-2', name: 'Marta Hailu', phone: '+251922334455', customerId: 'c2', status: 'clicked', sentAt: '2026-08-06T06:00:00Z' }, { id: 'cr-3', name: 'Tigist Alemu', phone: '+251933445566', customerId: 'c3', status: 'opened', sentAt: '2026-08-06T06:01:00Z' }, { id: 'cr-4', name: 'Bekele Tesfaye', phone: '+251944556677', status: 'opened', sentAt: '2026-08-06T06:01:00Z' }, { id: 'cr-5', name: 'Almaz Girma', phone: '+251955667788', status: 'redeemed', sentAt: '2026-08-06T06:02:00Z' }, { id: 'cr-6', name: 'Yohannes Desta', phone: '+251966778899', status: 'delivered', sentAt: '2026-08-06T06:02:00Z' }, { id: 'cr-7', name: 'Hiwot Bekele', phone: '+251977889900', status: 'clicked', sentAt: '2026-08-06T06:03:00Z' }, { id: 'cr-8', name: 'Getachew Mola', phone: '+251988990011', status: 'bounced', sentAt: '2026-08-06T06:03:00Z' }, ], 'cmp-202': [ { id: 'cr-9', name: 'Abebe Kebede', phone: '+251911223344', customerId: 'c1', status: 'clicked', sentAt: '2026-08-04T07:30:00Z' }, { id: 'cr-10', name: 'Netsanet Demeke', phone: '+251911445566', status: 'redeemed', sentAt: '2026-08-04T07:30:00Z' }, { id: 'cr-11', name: 'Sofia Ahmed', phone: '+251912334455', status: 'delivered', sentAt: '2026-08-04T07:31:00Z' }, { id: 'cr-12', name: 'Dawit Alemayehu', phone: '+251913445566', status: 'opened', sentAt: '2026-08-04T07:31:00Z' }, { id: 'cr-13', name: 'Rahel Tadesse', phone: '+251914556677', status: 'opened', sentAt: '2026-08-04T07:32:00Z' }, { id: 'cr-14', name: 'Marta Hailu', phone: '+251922334455', customerId: 'c2', status: 'bounced', sentAt: '2026-08-04T07:32:00Z' }, ], 'cmp-203': [ { id: 'cr-15', name: 'Tigist Alemu', phone: '+251933445566', customerId: 'c3', status: 'opened', sentAt: '2026-07-29T06:00:00Z' }, { id: 'cr-16', name: 'Solomon Wolde', phone: '+251915667788', status: 'delivered', sentAt: '2026-07-29T06:00:00Z' }, { id: 'cr-17', name: 'Meseret Kassa', phone: '+251916778899', status: 'redeemed', sentAt: '2026-07-29T06:01:00Z' }, { id: 'cr-18', name: 'Fikru Assefa', phone: '+251917889900', status: 'bounced', sentAt: '2026-07-29T06:01:00Z' }, { id: 'cr-19', name: 'Zenebech Haile', phone: '+251918990011', status: 'delivered', sentAt: '2026-07-29T06:02:00Z' }, ], 'cmp-205': [ { id: 'cr-20', name: 'Marta Hailu', phone: '+251922334455', customerId: 'c2', status: 'redeemed', sentAt: '2026-08-05T12:00:00Z' }, { id: 'cr-21', name: 'Tigist Alemu', phone: '+251933445566', customerId: 'c3', status: 'clicked', sentAt: '2026-08-05T12:00:00Z' }, { id: 'cr-22', name: 'Yeshi Lemma', phone: '+251924445566', status: 'redeemed', sentAt: '2026-08-05T12:01:00Z' }, { id: 'cr-23', name: 'Tesfaye Negash', phone: '+251925556677', status: 'opened', sentAt: '2026-08-05T12:01:00Z' }, ], 'cmp-207': [ { id: 'cr-24', name: 'Hiwot Bekele (Horizon Bank)', phone: '+251977889900', status: 'redeemed', sentAt: '2026-07-21T08:00:00Z' }, { id: 'cr-25', name: 'Daniel Tadesse (Horizon Bank)', phone: '+251927778899', status: 'clicked', sentAt: '2026-07-21T08:00:00Z' }, { id: 'cr-26', name: 'Sara Worku (Horizon Bank)', phone: '+251928889900', status: 'opened', sentAt: '2026-07-21T08:01:00Z' }, ], } /* ------------------------------------------------------------------ * * Migrations — job history * ------------------------------------------------------------------ */ export type MigrationJob = { id: string templateId: string templateLabel: string filename: string rowsTotal: number rowsImported: number rowsFailed: number status: 'completed' | 'partial' | 'failed' | 'running' runBy: string runAt: string durationSec: number mapping: Record } export const migrationJobs: MigrationJob[] = [ { id: 'mig-1048', templateId: 'stock', templateLabel: 'Stock / inventory', filename: 'stock-cmc-aug.csv', rowsTotal: 412, rowsImported: 404, rowsFailed: 8, status: 'partial', runBy: 'Dawit Stock', runAt: '2026-08-06T09:05:00Z', durationSec: 34, mapping: { sku: 'item_code', branchId: 'branch', qty: 'qty_on_hand', batch: 'batch_no', expiry: 'exp_date', cost: 'unit_cost', sell: 'unit_price' }, }, { id: 'mig-1047', templateId: 'catalog', templateLabel: 'Catalog products', filename: 'catalog-additions.csv', rowsTotal: 186, rowsImported: 186, rowsFailed: 0, status: 'completed', runBy: 'Dawit Stock', runAt: '2026-08-05T14:22:00Z', durationSec: 19, mapping: { sku: 'sku', name: 'product_name', strength: 'strength', form: 'dosage_form', category: 'category', rxFlag: 'is_rx', manufacturer: 'maker' }, }, { id: 'mig-1046', templateId: 'riders', templateLabel: 'Delivery people', filename: 'riders-august.csv', rowsTotal: 12, rowsImported: 12, rowsFailed: 0, status: 'completed', runBy: 'Abel Operations', runAt: '2026-08-05T09:30:00Z', durationSec: 3, mapping: { name: 'full_name', phone: 'msisdn', telegramId: 'telegram', branchId: 'branch', zone: 'zone' }, }, { id: 'mig-1045', templateId: 'hr', templateLabel: 'HR / org members', filename: 'horizon-bank-staff.xlsx', rowsTotal: 248, rowsImported: 231, rowsFailed: 17, status: 'partial', runBy: 'Yonas Finance', runAt: '2026-08-04T13:10:00Z', durationSec: 41, mapping: { orgId: 'org', phone: 'mobile', name: 'employee_name', department: 'dept', plan: 'benefit_plan', memberId: 'staff_no' }, }, { id: 'mig-1044', templateId: 'catalog', templateLabel: 'Catalog products', filename: 'catalog-q3.csv', rowsTotal: 1840, rowsImported: 0, rowsFailed: 1840, status: 'failed', runBy: 'Dawit Stock', runAt: '2026-08-03T10:05:00Z', durationSec: 8, mapping: { name: 'title', strength: 'dose', form: 'form', category: 'group' }, }, { id: 'mig-1043', templateId: 'stock', templateLabel: 'Stock / inventory', filename: 'stock-bole-opening.csv', rowsTotal: 604, rowsImported: 604, rowsFailed: 0, status: 'completed', runBy: 'Dawit Stock', runAt: '2026-08-02T08:40:00Z', durationSec: 52, mapping: { sku: 'sku', branchId: 'branch_id', qty: 'quantity', batch: 'lot', expiry: 'expiry', cost: 'cost', sell: 'price' }, }, { id: 'mig-1042', templateId: 'branches', templateLabel: 'Branches', filename: 'branches-geo.csv', rowsTotal: 3, rowsImported: 3, rowsFailed: 0, status: 'completed', runBy: 'Super Admin', runAt: '2026-08-01T16:12:00Z', durationSec: 2, mapping: { id: 'branch_id', name: 'branch_name', zone: 'zone', phone: 'phone', lat: 'latitude', lng: 'longitude' }, }, { id: 'mig-1041', templateId: 'hr', templateLabel: 'HR / org members', filename: 'green-ngo-pilot.csv', rowsTotal: 64, rowsImported: 58, rowsFailed: 6, status: 'partial', runBy: 'Yonas Finance', runAt: '2026-07-30T11:25:00Z', durationSec: 12, mapping: { orgId: 'organisation', phone: 'phone_number', name: 'name', department: 'unit', plan: 'plan' }, }, ] export type MigrationRowError = { id: string row: number column: string value: string reason: string } export const migrationJobErrors: Record = { 'mig-1048': [ { id: 'er-1', row: 14, column: 'qty_on_hand', value: 'n/a', reason: 'Quantity must be a whole number' }, { id: 'er-2', row: 37, column: 'exp_date', value: '2025-02', reason: 'Expiry date is already in the past' }, { id: 'er-3', row: 88, column: 'item_code', value: 'AMX500', reason: 'No catalog product matches this SKU' }, { id: 'er-4', row: 112, column: 'branch', value: 'br-mexico', reason: 'Unknown branch id' }, { id: 'er-5', row: 190, column: 'unit_cost', value: '-40', reason: 'Cost cannot be negative' }, { id: 'er-6', row: 244, column: 'batch_no', value: '', reason: 'Batch number is required for tracked items' }, { id: 'er-7', row: 301, column: 'qty_on_hand', value: '1.5', reason: 'Quantity must be a whole number' }, { id: 'er-8', row: 388, column: 'item_code', value: 'PCM-500', reason: 'Duplicate row — SKU already imported in this file' }, ], 'mig-1045': [ { id: 'er-9', row: 8, column: 'mobile', value: '09112233', reason: 'Phone number is too short' }, { id: 'er-10', row: 22, column: 'benefit_plan', value: 'Platinum', reason: 'Plan is not configured for this organisation' }, { id: 'er-11', row: 41, column: 'staff_no', value: 'HB-0091', reason: 'Member id already exists' }, { id: 'er-12', row: 77, column: 'mobile', value: '', reason: 'Phone number is required' }, { id: 'er-13', row: 103, column: 'dept', value: '', reason: 'Department is required' }, ], 'mig-1044': [ { id: 'er-14', row: 1, column: 'sku', value: '', reason: 'Required column "sku" was not mapped — the whole file was rejected' }, ], 'mig-1041': [ { id: 'er-15', row: 5, column: 'organisation', value: 'org-3', reason: 'Unknown organisation id' }, { id: 'er-16', row: 19, column: 'phone_number', value: '+251911223344', reason: 'Phone already linked to another member' }, { id: 'er-17', row: 33, column: 'plan', value: '', reason: 'Plan is required' }, ], } /* ------------------------------------------------------------------ * * Dispatch — rider profiles and trip history * ------------------------------------------------------------------ */ export type RiderTrip = { id: string orderId: string customerName: string zone: string assignedAt: string completedAt: string | null status: 'delivered' | 'failed' | 'en_route' | 'returned' minutes: number onTime: boolean codEtb: number } export const riderStats: Record< string, { status: 'on_shift' | 'off_shift' | 'on_break' shift: string vehicle: string plate: string joinedAt: string tripsToday: number tripsTotal: number onTimeRate: number avgMinutes: number rating: number codHeldEtb: number telegramLinked: boolean lastSeenAt: string } > = { 'rd-1': { status: 'on_shift', shift: '07:00 – 16:00', vehicle: 'Motorbike', plate: 'AA-3-91422', joinedAt: '2026-02-11T00:00:00Z', tripsToday: 7, tripsTotal: 486, onTimeRate: 94, avgMinutes: 28, rating: 4.8, codHeldEtb: 3240, telegramLinked: true, lastSeenAt: '2026-08-06T10:22:00Z', }, 'rd-2': { status: 'on_break', shift: '09:00 – 18:00', vehicle: 'Motorbike', plate: 'AA-3-88107', joinedAt: '2026-03-04T00:00:00Z', tripsToday: 5, tripsTotal: 372, onTimeRate: 87, avgMinutes: 34, rating: 4.5, codHeldEtb: 1180, telegramLinked: true, lastSeenAt: '2026-08-06T09:58:00Z', }, 'rd-3': { status: 'off_shift', shift: '13:00 – 22:00', vehicle: 'Bicycle', plate: '—', joinedAt: '2026-05-20T00:00:00Z', tripsToday: 0, tripsTotal: 141, onTimeRate: 96, avgMinutes: 24, rating: 4.9, codHeldEtb: 0, telegramLinked: true, lastSeenAt: '2026-08-05T21:40:00Z', }, } export const riderTrips: Record = { 'rd-1': [ { id: 'tr-101', orderId: 'ord-5004', customerName: 'Netsanet D.', zone: 'Bole', assignedAt: '2026-08-06T07:40:00Z', completedAt: null, status: 'en_route', minutes: 0, onTime: true, codEtb: 0 }, { id: 'tr-102', orderId: 'ord-5001', customerName: 'Abebe Kebede', zone: 'Bole', assignedAt: '2026-08-06T06:20:00Z', completedAt: '2026-08-06T06:52:00Z', status: 'delivered', minutes: 32, onTime: true, codEtb: 0 }, { id: 'tr-103', orderId: 'ord-4998', customerName: 'Sofia Ahmed', zone: 'Bole', assignedAt: '2026-08-05T17:10:00Z', completedAt: '2026-08-05T17:38:00Z', status: 'delivered', minutes: 28, onTime: true, codEtb: 1240 }, { id: 'tr-104', orderId: 'ord-4995', customerName: 'Dawit Alemayehu', zone: 'Bole', assignedAt: '2026-08-05T14:05:00Z', completedAt: '2026-08-05T14:29:00Z', status: 'delivered', minutes: 24, onTime: true, codEtb: 0 }, { id: 'tr-105', orderId: 'ord-4991', customerName: 'Rahel Tadesse', zone: 'Bole', assignedAt: '2026-08-05T11:20:00Z', completedAt: '2026-08-05T12:14:00Z', status: 'delivered', minutes: 54, onTime: false, codEtb: 890 }, { id: 'tr-106', orderId: 'ord-4987', customerName: 'Almaz Girma', zone: 'Bole', assignedAt: '2026-08-04T16:40:00Z', completedAt: '2026-08-04T17:05:00Z', status: 'delivered', minutes: 25, onTime: true, codEtb: 0 }, { id: 'tr-107', orderId: 'ord-4982', customerName: 'Getachew Mola', zone: 'Bole', assignedAt: '2026-08-04T12:00:00Z', completedAt: '2026-08-04T12:31:00Z', status: 'delivered', minutes: 31, onTime: true, codEtb: 420 }, { id: 'tr-108', orderId: 'ord-4979', customerName: 'Yohannes Desta', zone: 'Bole', assignedAt: '2026-08-03T15:15:00Z', completedAt: '2026-08-03T15:41:00Z', status: 'delivered', minutes: 26, onTime: true, codEtb: 0 }, { id: 'tr-109', orderId: 'ord-4974', customerName: 'Kalkidan Berhe', zone: 'Bole', assignedAt: '2026-08-03T10:30:00Z', completedAt: null, status: 'failed', minutes: 41, onTime: false, codEtb: 0 }, { id: 'tr-110', orderId: 'ord-4970', customerName: 'Bekele Tesfaye', zone: 'Bole', assignedAt: '2026-08-02T18:05:00Z', completedAt: '2026-08-02T18:28:00Z', status: 'delivered', minutes: 23, onTime: true, codEtb: 1600 }, { id: 'tr-111', orderId: 'ord-4966', customerName: 'Hiwot Bekele', zone: 'Bole', assignedAt: '2026-08-02T13:40:00Z', completedAt: '2026-08-02T14:09:00Z', status: 'delivered', minutes: 29, onTime: true, codEtb: 0 }, { id: 'tr-112', orderId: 'ord-4961', customerName: 'Eyob Mekonnen', zone: 'Bole', assignedAt: '2026-08-01T16:20:00Z', completedAt: '2026-08-01T16:44:00Z', status: 'delivered', minutes: 24, onTime: true, codEtb: 640 }, { id: 'tr-113', orderId: 'ord-4957', customerName: 'Selam Getahun', zone: 'Bole', assignedAt: '2026-08-01T11:10:00Z', completedAt: '2026-08-01T11:52:00Z', status: 'returned', minutes: 42, onTime: false, codEtb: 0 }, ], 'rd-2': [ { id: 'tr-201', orderId: 'ord-5003', customerName: 'Bank staff · Hiwot', zone: 'CMC', assignedAt: '2026-08-06T10:20:00Z', completedAt: null, status: 'en_route', minutes: 0, onTime: true, codEtb: 0 }, { id: 'tr-202', orderId: 'ord-4999', customerName: 'Meseret Kassa', zone: 'CMC', assignedAt: '2026-08-06T08:15:00Z', completedAt: '2026-08-06T08:53:00Z', status: 'delivered', minutes: 38, onTime: true, codEtb: 980 }, { id: 'tr-203', orderId: 'ord-4994', customerName: 'Solomon Wolde', zone: 'CMC', assignedAt: '2026-08-05T15:30:00Z', completedAt: '2026-08-05T16:22:00Z', status: 'delivered', minutes: 52, onTime: false, codEtb: 0 }, { id: 'tr-204', orderId: 'ord-4990', customerName: 'Genet Abera', zone: 'CMC', assignedAt: '2026-08-05T12:40:00Z', completedAt: '2026-08-05T13:11:00Z', status: 'delivered', minutes: 31, onTime: true, codEtb: 2400 }, { id: 'tr-205', orderId: 'ord-4986', customerName: 'Fikru Assefa', zone: 'CMC', assignedAt: '2026-08-04T17:05:00Z', completedAt: '2026-08-04T17:39:00Z', status: 'delivered', minutes: 34, onTime: true, codEtb: 0 }, { id: 'tr-206', orderId: 'ord-4981', customerName: 'Zenebech Haile', zone: 'CMC', assignedAt: '2026-08-04T11:50:00Z', completedAt: null, status: 'failed', minutes: 48, onTime: false, codEtb: 0 }, { id: 'tr-207', orderId: 'ord-4977', customerName: 'Tesfaye Negash', zone: 'CMC', assignedAt: '2026-08-03T16:20:00Z', completedAt: '2026-08-03T16:55:00Z', status: 'delivered', minutes: 35, onTime: true, codEtb: 1120 }, { id: 'tr-208', orderId: 'ord-4972', customerName: 'Yeshi Lemma', zone: 'CMC', assignedAt: '2026-08-03T09:40:00Z', completedAt: '2026-08-03T10:18:00Z', status: 'delivered', minutes: 38, onTime: true, codEtb: 0 }, { id: 'tr-209', orderId: 'ord-4968', customerName: 'Mulugeta Fikadu', zone: 'CMC', assignedAt: '2026-08-02T14:15:00Z', completedAt: '2026-08-02T15:02:00Z', status: 'delivered', minutes: 47, onTime: false, codEtb: 760 }, { id: 'tr-210', orderId: 'ord-4963', customerName: 'Daniel Tadesse', zone: 'CMC', assignedAt: '2026-08-02T10:00:00Z', completedAt: '2026-08-02T10:33:00Z', status: 'delivered', minutes: 33, onTime: true, codEtb: 0 }, { id: 'tr-211', orderId: 'ord-4959', customerName: 'Sara Worku', zone: 'CMC', assignedAt: '2026-08-01T15:45:00Z', completedAt: '2026-08-01T16:19:00Z', status: 'delivered', minutes: 34, onTime: true, codEtb: 1450 }, { id: 'tr-212', orderId: 'ord-4954', customerName: 'Tigist Alemu', zone: 'CMC', assignedAt: '2026-08-01T09:20:00Z', completedAt: '2026-08-01T09:58:00Z', status: 'delivered', minutes: 38, onTime: true, codEtb: 0 }, ], 'rd-3': [ { id: 'tr-301', orderId: 'ord-4997', customerName: 'Netsanet Demeke', zone: 'Bole', assignedAt: '2026-08-05T18:30:00Z', completedAt: '2026-08-05T18:52:00Z', status: 'delivered', minutes: 22, onTime: true, codEtb: 0 }, { id: 'tr-302', orderId: 'ord-4993', customerName: 'Marta Hailu', zone: 'Bole', assignedAt: '2026-08-05T16:10:00Z', completedAt: '2026-08-05T16:33:00Z', status: 'delivered', minutes: 23, onTime: true, codEtb: 420 }, { id: 'tr-303', orderId: 'ord-4989', customerName: 'Abebe Kebede', zone: 'Bole', assignedAt: '2026-08-04T19:05:00Z', completedAt: '2026-08-04T19:26:00Z', status: 'delivered', minutes: 21, onTime: true, codEtb: 0 }, { id: 'tr-304', orderId: 'ord-4984', customerName: 'Sofia Ahmed', zone: 'Bole', assignedAt: '2026-08-04T15:20:00Z', completedAt: '2026-08-04T15:47:00Z', status: 'delivered', minutes: 27, onTime: true, codEtb: 890 }, { id: 'tr-305', orderId: 'ord-4980', customerName: 'Rahel Tadesse', zone: 'Bole', assignedAt: '2026-08-03T20:00:00Z', completedAt: '2026-08-03T20:24:00Z', status: 'delivered', minutes: 24, onTime: true, codEtb: 0 }, { id: 'tr-306', orderId: 'ord-4976', customerName: 'Dawit Alemayehu', zone: 'Bole', assignedAt: '2026-08-03T17:15:00Z', completedAt: '2026-08-03T17:41:00Z', status: 'delivered', minutes: 26, onTime: true, codEtb: 1300 }, { id: 'tr-307', orderId: 'ord-4971', customerName: 'Almaz Girma', zone: 'Bole', assignedAt: '2026-08-02T19:40:00Z', completedAt: '2026-08-02T20:11:00Z', status: 'delivered', minutes: 31, onTime: false, codEtb: 0 }, { id: 'tr-308', orderId: 'ord-4967', customerName: 'Kalkidan Berhe', zone: 'Bole', assignedAt: '2026-08-02T16:00:00Z', completedAt: '2026-08-02T16:23:00Z', status: 'delivered', minutes: 23, onTime: true, codEtb: 640 }, { id: 'tr-309', orderId: 'ord-4962', customerName: 'Bekele Tesfaye', zone: 'Bole', assignedAt: '2026-08-01T18:25:00Z', completedAt: '2026-08-01T18:47:00Z', status: 'delivered', minutes: 22, onTime: true, codEtb: 0 }, { id: 'tr-310', orderId: 'ord-4958', customerName: 'Selam Getahun', zone: 'Bole', assignedAt: '2026-08-01T14:10:00Z', completedAt: '2026-08-01T14:35:00Z', status: 'delivered', minutes: 25, onTime: true, codEtb: 380 }, ], } /* ------------------------------------------------------------------ * * Settings — branch profiles, staff and configuration history * ------------------------------------------------------------------ */ export const branchProfiles: Record< string, { manager: string address: string openingHours: string email: string staffCount: number ordersToday: number revenueTodayEtb: number skuCount: number lowStockCount: number expiringSoonCount: number openedAt: string status: 'open' | 'closed' | 'maintenance' } > = { 'br-bole': { manager: 'Hana Pharmacist', address: 'Bole Atlas, Cameroon St, Addis Ababa', openingHours: '07:00 – 22:00', email: 'bole@gishen.et', staffCount: 9, ordersToday: 34, revenueTodayEtb: 41000, skuCount: 1284, lowStockCount: 12, expiringSoonCount: 6, openedAt: '2024-06-01T00:00:00Z', status: 'open', }, 'br-cmc': { manager: 'Dawit Stock', address: 'CMC Road, near St Michael Church, Addis Ababa', openingHours: '08:00 – 21:00', email: 'cmc@gishen.et', staffCount: 6, ordersToday: 21, revenueTodayEtb: 27000, skuCount: 1041, lowStockCount: 19, expiringSoonCount: 4, openedAt: '2025-02-14T00:00:00Z', status: 'open', }, 'br-piassa': { manager: 'Abel Operations', address: 'Piassa, Adwa Ave, Addis Ababa', openingHours: '07:30 – 21:00', email: 'piassa@gishen.et', staffCount: 4, ordersToday: 14, revenueTodayEtb: 17000, skuCount: 826, lowStockCount: 8, expiringSoonCount: 11, openedAt: '2025-11-03T00:00:00Z', status: 'maintenance', }, } export const branchStaff: Record = { 'br-bole': [ { id: 'bs-1', name: 'Hana Pharmacist', role: 'Branch manager · Pharmacist', shift: '07:00 – 15:00', phone: '+251930099850', avatarUrl: portraitFor('Hana Pharmacist') }, { id: 'bs-2', name: 'Selamawit Girma', role: 'Pharmacist', shift: '14:00 – 22:00', phone: '+251930099853', avatarUrl: portraitFor('Selamawit Girma') }, { id: 'bs-3', name: 'Biruk Haile', role: 'Pharmacy technician', shift: '07:00 – 15:00', phone: '+251930099854', avatarUrl: portraitFor('Biruk Haile') }, { id: 'bs-4', name: 'Kidus Rider', role: 'Delivery', shift: '07:00 – 16:00', phone: '+251911000111', avatarUrl: portraitFor('Kidus Rider') }, // Matches riders['rd-3'] — Samuel has no photo on file. { id: 'bs-5', name: 'Samuel Rider', role: 'Delivery', shift: '13:00 – 22:00', phone: '+251911000333' }, ], 'br-cmc': [ { id: 'bs-6', name: 'Dawit Stock', role: 'Branch manager · Stock', shift: '08:00 – 17:00', phone: '+251930099851', avatarUrl: portraitFor('Dawit Stock') }, { id: 'bs-7', name: 'Rediet Tesfa', role: 'Pharmacist', shift: '08:00 – 16:00', phone: '+251930099855', avatarUrl: portraitFor('Rediet Tesfa') }, { id: 'bs-8', name: 'Meron Rider', role: 'Delivery', shift: '09:00 – 18:00', phone: '+251911000222', avatarUrl: portraitFor('Meron Rider') }, ], 'br-piassa': [ { id: 'bs-9', name: 'Abel Operations', role: 'Branch manager', shift: '07:30 – 16:30', phone: '+251930099852', avatarUrl: portraitFor('Abel Operations') }, { id: 'bs-10', name: 'Nardos Alemu', role: 'Pharmacist', shift: '12:00 – 21:00', phone: '+251930099856', avatarUrl: portraitFor('Nardos Alemu') }, ], } /** Staff invited to the console who have not accepted yet — drives the Team pending-invite KPI. */ export type StaffInvitation = { id: string email: string name: string role: StaffRole branchId?: string invitedBy: string invitedAt: string expiresAt: string /** * Deliberately unset on every pending invite — nobody has uploaded a photo * before accepting, so invite rows carry initials. */ avatarUrl?: string } export const staffInvitations: StaffInvitation[] = [ { id: 'inv-1', email: 'nardos.alemu@gishen.et', name: 'Nardos Alemu', role: 'pharmacist', branchId: 'br-piassa', invitedBy: 'Super Admin', invitedAt: '2026-08-06T08:20:00Z', expiresAt: '2026-08-13T08:20:00Z', }, { id: 'inv-2', email: 'biruk.haile@gishen.et', name: 'Biruk Haile', role: 'stock_manager', branchId: 'br-bole', invitedBy: 'Dawit Stock', invitedAt: '2026-08-04T14:05:00Z', expiresAt: '2026-08-11T14:05:00Z', }, { id: 'inv-3', email: 'rediet.tesfa@gishen.et', name: 'Rediet Tesfa', role: 'pharmacist', branchId: 'br-cmc', invitedBy: 'Hana Pharmacist', invitedAt: '2026-08-02T09:30:00Z', expiresAt: '2026-08-09T09:30:00Z', }, ] export type SettingsChange = { id: string at: string actor: string area: 'Platform' | 'Branches' | 'Delivery' | 'Security' | 'Integrations' | 'Notifications' change: string detail: string } export const settingsActivity: SettingsChange[] = [ { id: 'sc-1', at: '2026-08-06T07:02:00Z', actor: 'Super Admin', area: 'Platform', change: 'Data retention raised to 365 days', detail: 'Was 180 days · required by the pharmacy regulator' }, { id: 'sc-2', at: '2026-08-05T18:40:00Z', actor: 'Abel Operations', area: 'Delivery', change: 'Bole delivery radius extended to 9 km', detail: 'Adds Gerji and Summit to the Bole catchment' }, { id: 'sc-3', at: '2026-08-05T11:05:00Z', actor: 'Super Admin', area: 'Security', change: 'Two-factor authentication enforced for admins', detail: 'Applies to super_admin and finance roles' }, { id: 'sc-4', at: '2026-08-04T15:22:00Z', actor: 'Yonas Finance', area: 'Integrations', change: 'M-Pesa gateway enabled', detail: 'Fee 2% · settlement T+1' }, { id: 'sc-5', at: '2026-08-03T13:47:00Z', actor: 'Super Admin', area: 'Branches', change: 'Piassa opening hours changed', detail: '07:30 – 21:00, was 08:00 – 20:00' }, { id: 'sc-6', at: '2026-08-02T09:30:00Z', actor: 'Lidya Marketing', area: 'Notifications', change: 'SMS sender id updated to GISHEN', detail: 'Approved by the telecom regulator on 1 Aug' }, { id: 'sc-7', at: '2026-08-01T16:12:00Z', actor: 'Super Admin', area: 'Branches', change: 'Branch geo-coordinates imported', detail: 'Job mig-1042 · 3 rows' }, { id: 'sc-8', at: '2026-07-31T10:18:00Z', actor: 'Dawit Stock', area: 'Platform', change: 'Low-stock threshold set to 20 units', detail: 'Applies to all branches and drives reorder alerts' }, { id: 'sc-9', at: '2026-07-30T14:55:00Z', actor: 'Super Admin', area: 'Security', change: 'Session timeout reduced to 30 minutes', detail: 'Idle sessions now expire faster on shared terminals' }, { id: 'sc-10', at: '2026-07-28T08:20:00Z', actor: 'Abel Operations', area: 'Delivery', change: 'Telegram dispatch bot token rotated', detail: 'Routine 90-day rotation' }, ] export const configIssues = [ { id: 'ci-1', severity: 'warning' as const, area: 'Branches', message: 'Piassa Branch has no backup pharmacist assigned' }, { id: 'ci-2', severity: 'warning' as const, area: 'Integrations', message: 'ERP sync credentials expire in 12 days' }, { id: 'ci-3', severity: 'info' as const, area: 'Delivery', message: 'Samuel Rider has no vehicle plate on file' }, ] /* ------------------------------------------------------------------ * * Profile — the signed-in admin's own account, sessions and activity * Keyed by the demoAccounts id (tm-*) so /team/:id and /profile agree. * ------------------------------------------------------------------ */ export type StaffProfile = { id: string jobTitle: string phone: string status: 'active' | 'invited' | 'suspended' joinedAt: string lastSignInAt: string lastSignInFrom: string passwordUpdatedAt: string twoFactorEnabled: boolean preferredLocale: 'en' | 'am' notifications: { orderAlerts: boolean lowStockAlerts: boolean weeklyDigest: boolean securityAlerts: boolean } } export const staffProfiles: Record = { 'tm-pharmacist': { id: 'tm-pharmacist', jobTitle: 'Branch manager · Pharmacist', phone: '+251 930 099 850', status: 'active', joinedAt: '2024-06-01T00:00:00Z', lastSignInAt: '2026-08-06T06:40:00Z', lastSignInFrom: 'Addis Ababa, Ethiopia · 196.189.44.12', passwordUpdatedAt: '2026-05-18T09:12:00Z', twoFactorEnabled: true, preferredLocale: 'am', notifications: { orderAlerts: true, lowStockAlerts: true, weeklyDigest: false, securityAlerts: true }, }, 'tm-stock': { id: 'tm-stock', jobTitle: 'Stock manager', phone: '+251 930 099 851', status: 'active', joinedAt: '2024-09-12T00:00:00Z', lastSignInAt: '2026-08-06T05:55:00Z', lastSignInFrom: 'Addis Ababa, Ethiopia · 196.189.44.20', passwordUpdatedAt: '2026-06-02T14:30:00Z', twoFactorEnabled: false, preferredLocale: 'en', notifications: { orderAlerts: false, lowStockAlerts: true, weeklyDigest: true, securityAlerts: true }, }, 'tm-procurement': { id: 'tm-procurement', jobTitle: 'Procurement lead', phone: '+251 930 099 857', status: 'active', joinedAt: '2025-01-20T00:00:00Z', lastSignInAt: '2026-08-05T15:10:00Z', lastSignInFrom: 'Addis Ababa, Ethiopia · 196.189.44.44', passwordUpdatedAt: '2026-04-11T08:05:00Z', twoFactorEnabled: false, preferredLocale: 'en', notifications: { orderAlerts: false, lowStockAlerts: true, weeklyDigest: true, securityAlerts: true }, }, 'tm-finance': { id: 'tm-finance', jobTitle: 'Finance manager', phone: '+251 930 099 858', status: 'active', joinedAt: '2024-07-08T00:00:00Z', lastSignInAt: '2026-08-06T07:25:00Z', lastSignInFrom: 'Addis Ababa, Ethiopia · 196.189.44.51', passwordUpdatedAt: '2026-07-01T11:45:00Z', twoFactorEnabled: true, preferredLocale: 'en', notifications: { orderAlerts: false, lowStockAlerts: false, weeklyDigest: true, securityAlerts: true }, }, 'tm-marketing': { id: 'tm-marketing', jobTitle: 'Marketing manager', phone: '+251 930 099 859', status: 'active', joinedAt: '2025-03-03T00:00:00Z', lastSignInAt: '2026-08-05T18:02:00Z', lastSignInFrom: 'Addis Ababa, Ethiopia · 196.189.44.62', passwordUpdatedAt: '2026-02-19T16:20:00Z', twoFactorEnabled: false, preferredLocale: 'am', notifications: { orderAlerts: false, lowStockAlerts: false, weeklyDigest: true, securityAlerts: false }, }, 'tm-ops': { id: 'tm-ops', jobTitle: 'Operations manager', phone: '+251 930 099 852', status: 'active', joinedAt: '2024-11-15T00:00:00Z', lastSignInAt: '2026-08-06T04:30:00Z', lastSignInFrom: 'Addis Ababa, Ethiopia · 196.189.44.31', passwordUpdatedAt: '2026-06-27T07:40:00Z', twoFactorEnabled: true, preferredLocale: 'en', notifications: { orderAlerts: true, lowStockAlerts: false, weeklyDigest: false, securityAlerts: true }, }, 'tm-admin': { id: 'tm-admin', jobTitle: 'Platform super admin', phone: '+251 930 099 800', status: 'active', joinedAt: '2024-05-01T00:00:00Z', lastSignInAt: '2026-08-06T09:48:00Z', lastSignInFrom: 'Addis Ababa, Ethiopia · 196.189.44.10', passwordUpdatedAt: '2026-07-22T10:00:00Z', twoFactorEnabled: true, preferredLocale: 'en', notifications: { orderAlerts: true, lowStockAlerts: true, weeklyDigest: true, securityAlerts: true }, }, } export type StaffSession = { id: string device: string browser: string location: string ip: string lastActiveAt: string current: boolean } export const staffSessions: Record = { 'tm-pharmacist': [ { id: 'ss-101', device: 'MacBook Pro', browser: 'Chrome 141', location: 'Bole Branch, Addis Ababa', ip: '196.189.44.12', lastActiveAt: '2026-08-06T10:12:00Z', current: true }, { id: 'ss-102', device: 'iPhone 15', browser: 'Safari 18', location: 'Addis Ababa', ip: '10.42.7.19', lastActiveAt: '2026-08-05T19:40:00Z', current: false }, ], 'tm-stock': [ { id: 'ss-201', device: 'Dell Latitude', browser: 'Chrome 140', location: 'CMC Branch, Addis Ababa', ip: '196.189.44.20', lastActiveAt: '2026-08-06T09:30:00Z', current: true }, { id: 'ss-202', device: 'Warehouse terminal', browser: 'Firefox 135', location: 'Central warehouse', ip: '10.42.9.4', lastActiveAt: '2026-08-04T13:05:00Z', current: false }, ], 'tm-procurement': [ { id: 'ss-301', device: 'ThinkPad X1', browser: 'Chrome 141', location: 'Head office, Addis Ababa', ip: '196.189.44.44', lastActiveAt: '2026-08-05T15:55:00Z', current: true }, ], 'tm-finance': [ { id: 'ss-401', device: 'MacBook Air', browser: 'Chrome 141', location: 'Head office, Addis Ababa', ip: '196.189.44.51', lastActiveAt: '2026-08-06T08:15:00Z', current: true }, { id: 'ss-402', device: 'iPad Pro', browser: 'Safari 18', location: 'Addis Ababa', ip: '10.42.3.61', lastActiveAt: '2026-08-03T11:20:00Z', current: false }, ], 'tm-marketing': [ { id: 'ss-501', device: 'MacBook Pro', browser: 'Chrome 140', location: 'Head office, Addis Ababa', ip: '196.189.44.62', lastActiveAt: '2026-08-05T18:44:00Z', current: true }, { id: 'ss-502', device: 'Pixel 8', browser: 'Chrome mobile', location: 'Addis Ababa', ip: '10.42.5.22', lastActiveAt: '2026-08-02T20:10:00Z', current: false }, ], 'tm-ops': [ { id: 'ss-601', device: 'HP EliteBook', browser: 'Chrome 141', location: 'Dispatch hub, Addis Ababa', ip: '196.189.44.31', lastActiveAt: '2026-08-06T07:05:00Z', current: true }, { id: 'ss-602', device: 'Samsung Galaxy A54', browser: 'Chrome mobile', location: 'Piassa, Addis Ababa', ip: '10.42.8.77', lastActiveAt: '2026-08-05T22:35:00Z', current: false }, ], 'tm-admin': [ { id: 'ss-701', device: 'MacBook Pro', browser: 'Chrome 141', location: 'Head office, Addis Ababa', ip: '196.189.44.10', lastActiveAt: '2026-08-06T10:20:00Z', current: true }, { id: 'ss-702', device: 'Windows workstation', browser: 'Edge 140', location: 'Head office, Addis Ababa', ip: '196.189.44.11', lastActiveAt: '2026-08-05T17:12:00Z', current: false }, { id: 'ss-703', device: 'iPhone 16 Pro', browser: 'Safari 18', location: 'Addis Ababa', ip: '10.42.1.5', lastActiveAt: '2026-08-04T21:48:00Z', current: false }, ], } export type StaffActivityEntry = { id: string at: string action: string summary: string module: ModuleKey target?: string targetPath?: string outcome: 'success' | 'failed' } export const staffActivity: Record = { 'tm-pharmacist': [ { id: 'pa-101', at: '2026-08-06T10:12:00Z', action: 'rx.approve', summary: 'Approved a prescription for Abebe Kebede', module: 'prescriptions', target: 'rx-1001', targetPath: '/prescriptions/rx-1001', outcome: 'success' }, { id: 'pa-102', at: '2026-08-06T09:35:00Z', action: 'order.confirm', summary: 'Confirmed order for pickup at Bole', module: 'orders', target: 'ord-5002', targetPath: '/orders/ord-5002', outcome: 'success' }, { id: 'pa-103', at: '2026-08-06T08:50:00Z', action: 'stock.adjust', summary: 'Adjusted Amoxicillin 500mg after a dispensing count', module: 'stock', outcome: 'success' }, { id: 'pa-104', at: '2026-08-05T17:20:00Z', action: 'rx.query', summary: 'Queried a prescription with the prescriber', module: 'prescriptions', target: 'rx-1003', targetPath: '/prescriptions/rx-1003', outcome: 'success' }, { id: 'pa-105', at: '2026-08-05T07:02:00Z', action: 'auth.login', summary: 'Signed in from Bole Branch', module: 'dashboard', outcome: 'success' }, { id: 'pa-106', at: '2026-08-04T16:44:00Z', action: 'faq.view', summary: 'Read the controlled substances guidance article', module: 'faq', outcome: 'success' }, ], 'tm-stock': [ { id: 'pa-201', at: '2026-08-06T09:28:00Z', action: 'stock.receive', summary: 'Received a delivery into the CMC store room', module: 'stock', outcome: 'success' }, { id: 'pa-202', at: '2026-08-06T08:05:00Z', action: 'migration.run', summary: 'Ran the supplier catalogue import', module: 'migrations', outcome: 'success' }, { id: 'pa-203', at: '2026-08-05T15:12:00Z', action: 'procurement.request', summary: 'Raised a reorder request for low-stock lines', module: 'procurement', outcome: 'success' }, { id: 'pa-204', at: '2026-08-05T11:40:00Z', action: 'stock.export', summary: 'Exported the near-expiry report', module: 'stock', outcome: 'success' }, { id: 'pa-205', at: '2026-08-04T18:30:00Z', action: 'migration.run', summary: 'Retried a failed ERP sync job', module: 'migrations', outcome: 'failed' }, { id: 'pa-206', at: '2026-08-04T07:15:00Z', action: 'auth.login', summary: 'Signed in from the warehouse terminal', module: 'dashboard', outcome: 'success' }, ], 'tm-procurement': [ { id: 'pa-301', at: '2026-08-05T15:48:00Z', action: 'procurement.order', summary: 'Placed a purchase order with a supplier', module: 'procurement', outcome: 'success' }, { id: 'pa-302', at: '2026-08-05T13:22:00Z', action: 'procurement.compare', summary: 'Compared supplier quotes for antibiotics', module: 'procurement', outcome: 'success' }, { id: 'pa-303', at: '2026-08-04T16:10:00Z', action: 'stock.view', summary: 'Reviewed low-stock coverage across branches', module: 'stock', outcome: 'success' }, { id: 'pa-304', at: '2026-08-04T09:05:00Z', action: 'procurement.receive', summary: 'Marked a request as received', module: 'procurement', outcome: 'success' }, { id: 'pa-305', at: '2026-08-03T08:30:00Z', action: 'auth.login', summary: 'Signed in from the head office', module: 'dashboard', outcome: 'success' }, ], 'tm-finance': [ { id: 'pa-401', at: '2026-08-06T08:15:00Z', action: 'settlement.reconcile', summary: 'Reconciled the Telebirr settlement batch', module: 'finance', outcome: 'success' }, { id: 'pa-402', at: '2026-08-05T16:40:00Z', action: 'organisation.approve', summary: 'Approved a B2B credit limit increase', module: 'organisations', outcome: 'success' }, { id: 'pa-403', at: '2026-08-05T10:05:00Z', action: 'analytics.export', summary: 'Exported the monthly revenue breakdown', module: 'analytics', outcome: 'success' }, { id: 'pa-404', at: '2026-08-04T14:25:00Z', action: 'invoice.issue', summary: 'Issued invoices for corporate accounts', module: 'finance', outcome: 'success' }, { id: 'pa-405', at: '2026-08-03T09:50:00Z', action: 'migration.run', summary: 'Imported the bank statement file', module: 'migrations', outcome: 'failed' }, { id: 'pa-406', at: '2026-08-03T07:30:00Z', action: 'auth.login', summary: 'Signed in from the head office', module: 'dashboard', outcome: 'success' }, ], 'tm-marketing': [ { id: 'pa-501', at: '2026-08-05T18:44:00Z', action: 'campaign.launch', summary: 'Launched the rainy season wellness campaign', module: 'campaigns', outcome: 'success' }, { id: 'pa-502', at: '2026-08-05T14:10:00Z', action: 'segment.update', summary: 'Refreshed the lapsed customers segment', module: 'marketing', outcome: 'success' }, { id: 'pa-503', at: '2026-08-04T11:35:00Z', action: 'loyalty.adjust', summary: 'Granted bonus points to gold tier members', module: 'loyalty', outcome: 'success' }, { id: 'pa-504', at: '2026-08-04T09:20:00Z', action: 'faq.publish', summary: 'Published a new customer help article', module: 'faq_write', outcome: 'success' }, { id: 'pa-505', at: '2026-08-03T15:05:00Z', action: 'crm.view', summary: 'Reviewed the top spending customers', module: 'crm', outcome: 'success' }, { id: 'pa-506', at: '2026-08-02T08:40:00Z', action: 'auth.login', summary: 'Signed in from the head office', module: 'dashboard', outcome: 'success' }, ], 'tm-ops': [ { id: 'pa-601', at: '2026-08-06T07:05:00Z', action: 'dispatch.assign_rider', summary: 'Assigned a rider to a Gerji delivery', module: 'dispatch', target: 'ord-5004', targetPath: '/orders/ord-5004', outcome: 'success' }, { id: 'pa-602', at: '2026-08-05T20:15:00Z', action: 'order.reroute', summary: 'Rerouted a delayed delivery to a second rider', module: 'orders', target: 'ord-5003', targetPath: '/orders/ord-5003', outcome: 'success' }, { id: 'pa-603', at: '2026-08-05T12:30:00Z', action: 'dispatch.shift', summary: 'Updated the evening rider shift plan', module: 'dispatch', outcome: 'success' }, { id: 'pa-604', at: '2026-08-04T19:10:00Z', action: 'migration.run', summary: 'Imported delivery zone boundaries', module: 'migrations', outcome: 'success' }, { id: 'pa-605', at: '2026-08-04T06:50:00Z', action: 'auth.login', summary: 'Signed in from the dispatch hub', module: 'dashboard', outcome: 'success' }, ], 'tm-admin': [ { id: 'pa-701', at: '2026-08-06T09:48:00Z', action: 'settings.update', summary: 'Raised data retention to 365 days', module: 'settings', outcome: 'success' }, { id: 'pa-702', at: '2026-08-06T08:20:00Z', action: 'team.invite', summary: 'Invited a new pharmacist to the Piassa branch', module: 'team', outcome: 'success' }, { id: 'pa-703', at: '2026-08-05T17:12:00Z', action: 'audit.export', summary: 'Exported the security review queue', module: 'audit', outcome: 'success' }, { id: 'pa-704', at: '2026-08-05T11:05:00Z', action: 'settings.security', summary: 'Enforced two-factor authentication for admins', module: 'settings', outcome: 'success' }, { id: 'pa-705', at: '2026-08-04T15:30:00Z', action: 'organisation.approve', summary: 'Approved a new corporate account', module: 'organisations', outcome: 'success' }, { id: 'pa-706', at: '2026-08-04T10:02:00Z', action: 'auth.login', summary: 'Signed in from the head office', module: 'dashboard', outcome: 'success' }, { id: 'pa-707', at: '2026-08-03T14:18:00Z', action: 'migration.run', summary: 'Imported branch geo-coordinates', module: 'migrations', outcome: 'success' }, ], } /* ── Scan to purchase (counter POS) ───────────────────────────────────────── * Fixtures for the QR scan-to-purchase tool, the assisted invoice reader and * the inbound purchase-ticket integration. IDs deliberately reuse the existing * customer (`c1`–`c6`), branch, SKU and procurement keys so every association * resolves against real records. */ /** * How a SKU is allowed to leave the counter. `rx` needs an approved * prescription naming the medicine (a pharmacist may record an emergency * override); `controlled` can never be overridden. */ export type DispenseClass = 'otc' | 'rx' | 'controlled' export const dispenseClassBySku: Record = { 'AMX-500': 'rx', 'PCM-500': 'otc', 'MTF-850': 'rx', 'VIT-C': 'otc', 'AMD-5': 'rx', 'CTZ-10': 'otc', 'IBU-400': 'otc', 'ORS-20': 'otc', 'HYC-1': 'otc', 'AZI-250': 'rx', 'SLB-INH': 'rx', 'FEF-200': 'otc', 'TRM-50': 'controlled', 'DZP-5': 'controlled', } /** Loyalty earn rule, mirroring the `purchase` row in `loyaltyActivities`. */ export const posEarnRule = { pointsPer: 1, perEtb: 100 } /** Tier multipliers stacked on the base purchase award. */ export const posTierMultipliers: Record = { Gold: 1.5, Silver: 1.2, Bronze: 1 } /** Namespace stamped into every customer QR code the console prints. */ export const customerQrPrefix = 'GISHEN-CUST' export type ScanSource = 'camera' | 'manual' | 'simulated' | 'ticket' export type CounterScan = { id: string at: string /** Raw payload as it arrived from the camera, keypad or ticket feed. */ code: string customerId?: string customerName: string source: ScanSource outcome: 'resolved' | 'unknown' branchId: string staff: string orderId?: string totalEtb?: number pointsAwarded?: number } export const counterScanHistory: CounterScan[] = [ { id: 'scn-4001', at: '2026-08-06T10:42:00Z', code: 'GISHEN-CUST:c1', customerId: 'c1', customerName: 'Abebe Kebede', source: 'camera', outcome: 'resolved', branchId: 'br-bole', staff: 'Hana Pharmacist', orderId: 'ord-5041', totalEtb: 336, pointsAwarded: 5 }, { id: 'scn-4002', at: '2026-08-06T10:18:00Z', code: 'GISHEN-CUST:c4', customerId: 'c4', customerName: 'Netsanet Desta', source: 'camera', outcome: 'resolved', branchId: 'br-bole', staff: 'Hana Pharmacist', orderId: 'ord-5040', totalEtb: 188, pointsAwarded: 3 }, { id: 'scn-4003', at: '2026-08-06T09:55:00Z', code: '+251933445566', customerId: 'c3', customerName: 'Tigist Alemu', source: 'manual', outcome: 'resolved', branchId: 'br-cmc', staff: 'Rediet Tesfa', orderId: 'ord-5039', totalEtb: 75, pointsAwarded: 1 }, { id: 'scn-4004', at: '2026-08-06T09:31:00Z', code: 'GISHEN-CUST:c9x', customerName: 'Not recognised', source: 'camera', outcome: 'unknown', branchId: 'br-bole', staff: 'Hana Pharmacist' }, { id: 'scn-4005', at: '2026-08-06T09:04:00Z', code: 'GISHEN-CUST:c2', customerId: 'c2', customerName: 'Marta Hailu', source: 'camera', outcome: 'resolved', branchId: 'br-bole', staff: 'Hana Pharmacist', orderId: 'ord-5038', totalEtb: 260, pointsAwarded: 3 }, { id: 'scn-4006', at: '2026-08-06T08:47:00Z', code: 'TCK-88399', customerId: 'c6', customerName: 'Dawit Lemma', source: 'ticket', outcome: 'resolved', branchId: 'br-piassa', staff: 'Abel Operations', orderId: 'ord-5037', totalEtb: 440, pointsAwarded: 4 }, { id: 'scn-4007', at: '2026-08-05T17:12:00Z', code: 'GISHEN-CUST:c5', customerId: 'c5', customerName: 'Hiwot Bekele', source: 'simulated', outcome: 'resolved', branchId: 'br-cmc', staff: 'Rediet Tesfa', orderId: 'ord-5036', totalEtb: 190, pointsAwarded: 2 }, { id: 'scn-4008', at: '2026-08-05T16:35:00Z', code: 'unreadable', customerName: 'Not recognised', source: 'camera', outcome: 'unknown', branchId: 'br-cmc', staff: 'Rediet Tesfa' }, ] export type PurchaseTicketLine = { sku: string; name: string; qty: number; unitEtb: number } /** * An inbound purchase raised outside the console — the tickets app, the counter * POS bridge or the Telegram bot. The operator accepts it into an order. */ export type PurchaseTicket = { id: string sourceId: string externalRef: string customerId?: string customerName: string phone?: string branchId: string lines: PurchaseTicketLine[] totalEtb: number raisedAt: string status: 'pending' | 'accepted' | 'rejected' note?: string acceptedOrderId?: string } export const inboundPurchaseTickets: PurchaseTicket[] = [ { id: 'tkt-7001', sourceId: 'int-tickets', externalRef: 'TCK-88421', customerId: 'c1', customerName: 'Abebe Kebede', phone: '+251911223344', branchId: 'br-bole', lines: [ { sku: 'PCM-500', name: 'Paracetamol 500mg', qty: 10, unitEtb: 8 }, { sku: 'VIT-C', name: 'Vitamin C 1000mg', qty: 1, unitEtb: 120 }, ], totalEtb: 200, raisedAt: '2026-08-06T10:52:00Z', status: 'pending', note: 'Loyalty QR matched at the ticket counter.', }, { id: 'tkt-7002', sourceId: 'int-tickets', externalRef: 'TCK-88437', customerId: 'c3', customerName: 'Tigist Alemu', phone: '+251933445566', branchId: 'br-cmc', lines: [ { sku: 'MTF-850', name: 'Metformin 850mg', qty: 12, unitEtb: 22 }, { sku: 'ORS-20', name: 'Oral rehydration salts', qty: 2, unitEtb: 25 }, ], totalEtb: 314, raisedAt: '2026-08-06T10:35:00Z', status: 'pending', note: 'Quantity exceeds CMC shelf count — check before accepting.', }, { id: 'tkt-7003', sourceId: 'int-tickets', externalRef: 'TCK-88452', customerName: 'Guest · 0921334455', phone: '+251921334455', branchId: 'br-bole', lines: [ { sku: 'IBU-400', name: 'Ibuprofen 400mg', qty: 6, unitEtb: 11 }, { sku: 'CTZ-10', name: 'Cetirizine 10mg', qty: 4, unitEtb: 9 }, ], totalEtb: 102, raisedAt: '2026-08-06T10:11:00Z', status: 'pending', note: 'No loyalty id on the ticket — resolve the customer before confirming.', }, { id: 'tkt-7004', sourceId: 'int-pos-bridge', externalRef: 'POS-1194', customerId: 'c2', customerName: 'Marta Hailu', phone: '+251922334455', branchId: 'br-bole', lines: [ { sku: 'TRM-50', name: 'Tramadol 50mg', qty: 10, unitEtb: 34 }, { sku: 'PCM-500', name: 'Paracetamol 500mg', qty: 5, unitEtb: 8 }, ], totalEtb: 380, raisedAt: '2026-08-06T09:48:00Z', status: 'pending', note: 'Contains a controlled line — pharmacist must clear it against rx-1023.', }, { id: 'tkt-7005', sourceId: 'int-tickets', externalRef: 'TCK-88399', customerId: 'c6', customerName: 'Dawit Lemma', phone: '+251966778899', branchId: 'br-piassa', lines: [ { sku: 'SLB-INH', name: 'Salbutamol inhaler', qty: 1, unitEtb: 320 }, { sku: 'FEF-200', name: 'Ferrous sulphate 200mg', qty: 10, unitEtb: 12 }, ], totalEtb: 440, raisedAt: '2026-08-06T08:40:00Z', status: 'accepted', acceptedOrderId: 'ord-5037', }, ] /** A pluggable inbound purchase source. Manual counter entry never depends on one. */ export type PurchaseSource = { id: string name: string vendor: string status: 'connected' | 'degraded' | 'disconnected' lastSyncAt?: string webhookUrl: string note: string } export const purchaseSources: PurchaseSource[] = [ { id: 'int-tickets', name: 'Tickets app', vendor: 'Yaltopia Tickets', status: 'connected', lastSyncAt: '2026-08-06T10:52:00Z', webhookUrl: 'https://api.gishen.et/hooks/tickets', note: 'Counter tickets stream in as pending purchases for an operator to accept.', }, { id: 'int-pos-bridge', name: 'Counter POS bridge', vendor: 'Legacy till (CSV bridge)', status: 'degraded', lastSyncAt: '2026-08-06T09:48:00Z', webhookUrl: 'https://api.gishen.et/hooks/pos-bridge', note: 'Polling every 15 minutes; the till clock drifts, so timestamps need a sanity check.', }, { id: 'int-telegram', name: 'Telegram order bot', vendor: 'Gishen Bot', status: 'disconnected', lastSyncAt: '2026-08-04T18:20:00Z', webhookUrl: 'https://api.gishen.et/hooks/telegram', note: 'Token expired. Manual counter entry is unaffected.', }, ] /** Suppliers that issue the paper invoices the reader ingests. */ export type InvoiceSupplier = { id: string; name: string; tin: string; contact: string } export const invoiceSuppliers: InvoiceSupplier[] = [ { id: 'sup-1', name: 'Ethio Pharma Import', tin: '0012345678', contact: 'sales@ethiopharma.et' }, { id: 'sup-2', name: 'Addis Pharma Dist.', tin: '0087654321', contact: 'orders@addispharma.et' }, { id: 'sup-3', name: 'Rift Valley Medical', tin: '0055667788', contact: 'accounts@riftvalleymed.et' }, ] /** * One line the assisted reader pulled off an invoice. `confidence` is the * extractor's own score — anything under 0.8 is highlighted for the operator, * and `matchedSku` is false when the printed code matched no catalogue entry. */ export type ScannedInvoiceLine = { id: string sku: string name: string qty: number unitEtb: number confidence: number matchedSku: boolean } export type ScannedInvoice = { id: string supplierId: string supplierName: string invoiceNo: string invoiceDate: string branchId: string subtotalEtb: number vatEtb: number totalEtb: number lines: ScannedInvoiceLine[] /** What the extractor thinks this paperwork belongs to; the operator confirms. */ suggestedLink?: { kind: PurchaseAssociationKind; id: string; label: string } } export const scannedInvoiceFixtures: ScannedInvoice[] = [ { id: 'sinv-2001', supplierId: 'sup-1', supplierName: 'Ethio Pharma Import', invoiceNo: 'EPI-2026-4471', invoiceDate: '2026-08-05', branchId: 'br-cmc', subtotalEtb: 11800, vatEtb: 1770, totalEtb: 13570, lines: [ { id: 'sl-1', sku: 'MTF-850', name: 'Metformin 850mg', qty: 500, unitEtb: 20, confidence: 0.97, matchedSku: true }, { id: 'sl-2', sku: 'ORS-20', name: 'Oral rehydration salts', qty: 40, unitEtb: 22, confidence: 0.91, matchedSku: true }, { id: 'sl-3', sku: 'AZI-250', name: 'Azithromycin 250mg', qty: 20, unitEtb: 44, confidence: 0.62, matchedSku: true }, ], suggestedLink: { kind: 'procurement', id: 'pr-301', label: 'pr-301 · Metformin 850mg × 500' }, }, { id: 'sinv-2002', supplierId: 'sup-2', supplierName: 'Addis Pharma Dist.', invoiceNo: 'APD-8890', invoiceDate: '2026-08-04', branchId: 'br-bole', subtotalEtb: 8600, vatEtb: 1290, totalEtb: 9890, lines: [ { id: 'sl-1', sku: 'PCM-500', name: 'Paracetamol 500mg', qty: 1000, unitEtb: 7, confidence: 0.95, matchedSku: true }, { id: 'sl-2', sku: 'IBU-400', name: 'Ibuprofen 400mg', qty: 100, unitEtb: 10, confidence: 0.88, matchedSku: true }, { id: 'sl-3', sku: '', name: 'Zinc sulphate 20mg (not in catalogue)', qty: 60, unitEtb: 10, confidence: 0.54, matchedSku: false }, ], suggestedLink: { kind: 'procurement', id: 'pr-302', label: 'pr-302 · Paracetamol 500mg × 1000' }, }, { id: 'sinv-2003', supplierId: 'sup-3', supplierName: 'Rift Valley Medical', invoiceNo: 'RVM-0771', invoiceDate: '2026-08-03', branchId: 'br-piassa', subtotalEtb: 6720, vatEtb: 1008, totalEtb: 7728, lines: [ { id: 'sl-1', sku: 'SLB-INH', name: 'Salbutamol inhaler', qty: 20, unitEtb: 296, confidence: 0.93, matchedSku: true }, { id: 'sl-2', sku: 'FEF-200', name: 'Ferrous sulphate 200mg', qty: 70, unitEtb: 11, confidence: 0.79, matchedSku: true }, ], suggestedLink: { kind: 'branch', id: 'br-piassa', label: 'Piassa Branch · stock arrival' }, }, ] /** What a scanned invoice can be attached to. */ export type PurchaseAssociationKind = 'procurement' | 'supplier' | 'branch' export type PurchaseAssociation = { id: string invoiceId: string invoiceNo: string supplierName: string linkKind: PurchaseAssociationKind linkId: string linkLabel: string totalEtb: number lineCount: number createdAt: string createdBy: string /** `review` when the operator confirmed the link but flagged a line for finance. */ status: 'linked' | 'review' } export let purchaseAssociations: PurchaseAssociation[] = [ { id: 'pas-9001', invoiceId: 'sinv-2004', invoiceNo: 'EPI-2026-4402', supplierName: 'Ethio Pharma Import', linkKind: 'procurement', linkId: 'pr-303', linkLabel: 'pr-303 · Amoxicillin 500mg × 300', totalEtb: 5980, lineCount: 2, createdAt: '2026-08-05T12:20:00Z', createdBy: 'Dawit Stock', status: 'linked', }, { id: 'pas-9002', invoiceId: 'sinv-2005', invoiceNo: 'APD-8871', supplierName: 'Addis Pharma Dist.', linkKind: 'supplier', linkId: 'sup-2', linkLabel: 'Addis Pharma Dist. · August statement', totalEtb: 3240, lineCount: 3, createdAt: '2026-08-04T16:05:00Z', createdBy: 'Sara Procurement', status: 'review', }, ] export { catalogItems, itemChangeHistory, therapeuticClasses, priceLists, productTypes, dosageForms, adminRoutes, controlledSchedules, efdaStatuses, ageRestrictions, mergeStatuses, merchandisingBadges, normalizeMatchKey, scoreMatchKey, nextItemSku, slugifyItem, findCatalogItem, itemVariants, skuToItemId, } from './catalog' export type { MedicationItem, ItemChangeEvent, PriceList, ProductType, DosageForm, AdminRoute, ControlledSchedule, EfdaStatus, AgeRestriction, MergeStatus, OwnershipSource, MerchandisingBadge, ActiveIngredient, CatalogBarcode, DraftPackVariant, FieldGroupOwnership, } from './catalog' import { skuToItemId as _skuToItemId } from './catalog' /** Additive link: attach catalog `itemId` + a mock reserved qty without renaming stock fields. */ for (const row of stockRows) { const linked = _skuToItemId[row.sku] if (linked) row.itemId = linked if (row.reserved === undefined) row.reserved = Math.max(0, Math.floor(row.qty * 0.08)) }