fix: brand id and genetic id are fixed

This commit is contained in:
2026-06-07 21:03:22 +03:30
parent cbe77fbb9e
commit 670dbf8ce2
9 changed files with 97 additions and 82 deletions
@@ -14,64 +14,78 @@ object PharmacyHtmlParser {
val doc = Jsoup.parse(html)
val table = doc.select("table#TBL_patientReferral").first() ?: return emptyList()
// تشخیص ساختار بر اساس سربرگ جدول (یک بار برای کل جدول)
val hasGuaranteeColumn = hasGuaranteeColumnInHeader(table)
// ساختار جدول متغیر است:
// - صفحات ژنریک: [نام برند, نام داروخانه, شهرستان] (گاهی با ستون «تضمین موجودی»)
// - صفحات برند: [نام داروخانه, شهرستان] (بدون ستون برند)
// بنابراین ایندکس ستون‌ها را از روی متن سربرگ تشخیص می‌دهیم نه موقعیت ثابت
val headers = table.select("thead th")
var brandIndex = -1
var pharmacyIndex = -1
var locationIndex = -1
headers.forEachIndexed { index, th ->
val text = th.text().trim()
when {
brandIndex == -1 && text.contains("برند") -> brandIndex = index
pharmacyIndex == -1 && text.contains("داروخانه") -> pharmacyIndex = index
locationIndex == -1 && (text.contains("شهرستان") || text.contains("استان")) -> locationIndex = index
}
}
val rows = table.select("tbody tr")
for (row in rows) {
val cells = row.select("td")
if (cells.size < 3) continue
if (cells.isEmpty()) continue
// ایندکس ستون‌ها بر اساس وجود ستون تضمین
val brandIndex = if (hasGuaranteeColumn) 1 else 0
val pharmacyIndex = if (hasGuaranteeColumn) 2 else 1
val locationIndex = if (hasGuaranteeColumn) 3 else 2
// ستون داروخانه (ضروری) — اگر از سربرگ پیدا نشد، سلولی که لینک ph- دارد
val pIdx = if (pharmacyIndex in cells.indices) {
pharmacyIndex
} else {
cells.indexOfFirst { cell ->
cell.select("a").any { it.attr("href").contains("ph-") }
}
}
if (pIdx < 0) continue
// ستون برند
val brandCell = cells[brandIndex]
val brandLink = brandCell.select("a").first()
val pharmacyCell = cells[pIdx]
// پیدا کردن لینک داروخانه (لینکی که "اطلاعات تماس" نباشد)
val pharmacyLink = pharmacyCell.select("a").firstOrNull {
!it.text().contains("اطلاعات تماس")
}
val pharmacyName = pharmacyLink?.text()?.trim() ?: ""
val pharmacyUrl = normalizeUrl(pharmacyLink?.attr("href")?.trim() ?: "")
val pharmacyId = extractIdFromUrl(pharmacyUrl, "/ph-(\\d+)/?")
if (pharmacyName.isBlank()) continue
// ستون برند (در صفحات برند وجود ندارد)
val brandCell = brandIndex.takeIf { it in cells.indices }?.let { cells[it] }
val brandLink = brandCell?.select("a")?.first()
val brandName = brandLink?.text()?.trim() ?: ""
val brandUrl = normalizeUrl(brandLink?.attr("href")?.trim() ?: "")
val brandId = extractIdFromUrl(brandUrl, "/B-(\\d+)/?")
// ستون داروخانه
val pharmacyCell = cells[pharmacyIndex]
val pharmacyLinks = pharmacyCell.select("a")
// پیدا کردن لینک داروخانه (لینکی که "اطلاعات تماس" نباشد)
val pharmacyLink = pharmacyLinks.firstOrNull {
!it.text().contains("اطلاعات تماس")
// ستون موقعیت — اگر از سربرگ پیدا نشد، سلولی که «استان» دارد
val locIdx = if (locationIndex in cells.indices) {
locationIndex
} else {
cells.indexOfFirst { it.text().contains("استان") }
}
val pharmacyName = pharmacyLink?.text()?.trim() ?: ""
var pharmacyUrl = pharmacyLink?.attr("href")?.trim() ?: ""
// نرمال کردن URL داروخانه
pharmacyUrl = normalizeUrl(pharmacyUrl)
val pharmacyId = extractIdFromUrl(pharmacyUrl, "/ph-(\\d+)/?")
// ستون موقعیت
val locationCell = cells[locationIndex]
val locationText = locationCell.text().trim()
// استخراج استان و شهر
val locationText = locIdx.takeIf { it in cells.indices }?.let { cells[it].text().trim() } ?: ""
val province = extractProvince(locationText)
val city = extractCity(locationText)
if (brandName.isNotBlank() && pharmacyName.isNotBlank()) {
items.add(
PharmacyItem(
brandName = brandName,
brandUrl = brandUrl,
brandId = brandId,
pharmacyName = pharmacyName,
pharmacyUrl = pharmacyUrl,
pharmacyId = pharmacyId,
province = province,
city = city
)
items.add(
PharmacyItem(
brandName = brandName,
brandUrl = brandUrl,
brandId = brandId,
pharmacyName = pharmacyName,
pharmacyUrl = pharmacyUrl,
pharmacyId = pharmacyId,
province = province,
city = city
)
}
)
}
return items
@@ -108,18 +122,6 @@ object PharmacyHtmlParser {
}
}
/**
* تشخیص وجود ستون تضمین موجودی با بررسی سربرگ جدول
*/
private fun hasGuaranteeColumnInHeader(table: org.jsoup.nodes.Element): Boolean {
val headers = table.select("thead th")
if (headers.isEmpty()) return false
// بررسی می‌کنیم که آیا اولین ستون حاوی متن "تضمین" است
val firstHeaderText = headers.firstOrNull()?.text()?.trim() ?: ""
return firstHeaderText.contains("تضمین") || firstHeaderText.contains("موجودی")
}
private fun extractProvince(locationText: String): String {
// الگو: "استان تهران" یا "استان اصفهان"
val provincePattern = "استان\\s*(\\S+)".toRegex()
@@ -94,12 +94,13 @@ class DrugRepositoryImpl(
override suspend fun searchPharmacies(
genericDrugId: String,
brandIrc: String,
provId: String
): List<PharmacyItem> {
return withContext(Dispatchers.IO) {
try {
val html = darooyabApiService.getPharmacies(
brandIrc = "0",
brandIrc = brandIrc,
genericDrugId = genericDrugId,
provId = provId,
cityId = "0"
@@ -16,7 +16,7 @@ interface DrugRepository {
suspend fun getGorohDaroei(): Result<DarmanModel>
suspend fun searchDrugs(params:DaroYabParams):Result<DaroYabSearchResult>
suspend fun getDrugDetailFromYab(detailUrl: String):Result<DrugDetail>
suspend fun searchPharmacies(genericDrugId: String,provId: String): List<PharmacyItem>
suspend fun searchPharmacies(genericDrugId: String, brandIrc: String, provId: String): List<PharmacyItem>
suspend fun getPharmacyDetail(pharmacyUrl: String): PharmacyDetail
@@ -8,8 +8,9 @@ class GetPharmaciesUseCase(
) {
suspend operator fun invoke(
genericDrugId: String,
brandIrc: String,
provinceId: String
): List<PharmacyItem> {
return repository.searchPharmacies(genericDrugId, provinceId)
return repository.searchPharmacies(genericDrugId, brandIrc, provinceId)
}
}
@@ -33,7 +33,11 @@ import com.vada.caller.ui.theme.dime
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PharmacyBottomSheet(
viewModel: PharmacyViewModel, genericDrugId: String, isOpen: Boolean, onDismiss: () -> Unit
viewModel: PharmacyViewModel,
genericDrugId: String,
brandIrc: String,
isOpen: Boolean,
onDismiss: () -> Unit
) {
val scope = rememberCoroutineScope()
val sheetState = rememberModalBottomSheetState(
@@ -45,11 +49,10 @@ fun PharmacyBottomSheet(
val pharmacyState by viewModel.state.collectAsState()
// جستجوی اولیه با استان تهران
LaunchedEffect(Unit) {
if (genericDrugId.isNotBlank()) {
viewModel.search(genericDrugId, "40")
}
// جستجوی اولیه با استان تهران — با تغییر دارو دوباره اجرا می‌شود
// (در صورت خالی بودن شناسه، ViewModel وضعیت قبلی را پاک و خطا نمایش می‌دهد)
LaunchedEffect(genericDrugId, brandIrc) {
viewModel.search(genericDrugId, brandIrc, "40")
}
if (isOpen) {
@@ -98,7 +101,7 @@ fun PharmacyBottomSheet(
selectedProvinceId = province.id
showProvinceDropdown = false
// جستجو با استان جدید
viewModel.search(genericDrugId, province.id)
viewModel.search(genericDrugId, brandIrc, province.id)
})
}
}
@@ -128,13 +128,19 @@ fun SearchScreen(
Spacer(modifier = Modifier.height(MaterialTheme.dime.md))
if (showPharmacyBottomSheet && selectedDrugForPharmacy != null) {
// استخراج genericDrugId از URL
// مثال: /G-799/Fexofenadine -> 799
val genericId = extractGenericIdFromUrl(selectedDrugForPharmacy?.detailPageUrl ?: "")
// درخواست بر اساس نوع صفحه فرق می‌کند:
// - صفحات برند (/B-...): شناسه باید به‌عنوان brandIrc ارسال شود
// - صفحات ژنریک (/G-...): شناسه به‌عنوان genericDrugId ارسال می‌شود
// (genericId از قبل پارس شده و در هر دو حالت همان عدد را نگه می‌دارد)
val drug = selectedDrugForPharmacy!!
val isBrand = drug.detailPageUrl.contains("/B-")
val genericId = if (isBrand) "0" else drug.genericId
val brandIrc = if (isBrand) drug.genericId else "0"
PharmacyBottomSheet(
viewModel = pharmacyViewModel,
genericDrugId = genericId,
brandIrc = brandIrc,
isOpen = showPharmacyBottomSheet,
onDismiss = {
showPharmacyBottomSheet = false
@@ -235,10 +241,4 @@ fun SearchScreen(
}
}
}
}
// تابع کمکی برای استخراج ID از URL
private fun extractGenericIdFromUrl(url: String): String {
val pattern = "/G-(\\d+)/".toRegex()
return pattern.find(url)?.groupValues?.get(1) ?: ""
}
@@ -38,6 +38,7 @@ class PharmacyViewModel(
val pharmacyDetailStates: StateFlow<Map<String, PharmacyDetailState>> = _pharmacyDetailStates.asStateFlow()
private var currentGenericDrugId: String = ""
private var currentBrandIrc: String = "0"
private var currentProvinceId: String = "0"
fun loadPharmacyDetail(pharmacyUrl: String) {
@@ -67,16 +68,26 @@ class PharmacyViewModel(
}
fun search(genericDrugId: String, provinceId: String) {
if (genericDrugId.isBlank()) return
fun search(genericDrugId: String, brandIrc: String, provinceId: String) {
currentGenericDrugId = genericDrugId
currentBrandIrc = brandIrc
currentProvinceId = provinceId
// پاک کردن نتایج جستجوی قبلی تا داده‌ی قبلی نمایش داده نشود
_pharmacyDetailStates.value = emptyMap()
// حداقل یکی از شناسه‌ها (ژنریک یا برند) باید معتبر باشد
val hasGeneric = genericDrugId.isNotBlank() && genericDrugId != "0"
val hasBrand = brandIrc.isNotBlank() && brandIrc != "0"
if (!hasGeneric && !hasBrand) {
_state.value = PharmacyState.Error("شناسه دارو یافت نشد")
return
}
viewModelScope.launch {
_state.value = PharmacyState.Loading
try {
val result = getPharmaciesUseCase(genericDrugId, provinceId)
val result = getPharmaciesUseCase(genericDrugId, brandIrc, provinceId)
_state.value = PharmacyState.Success(result)
} catch (e: Exception) {
_state.value = PharmacyState.Error(e.message ?: "خطا در دریافت اطلاعات")
@@ -85,8 +96,6 @@ class PharmacyViewModel(
}
fun retry() {
if (currentGenericDrugId.isNotBlank()) {
search(currentGenericDrugId, currentProvinceId)
}
search(currentGenericDrugId, currentBrandIrc, currentProvinceId)
}
}