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
-1
View File
@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
@@ -19,6 +19,6 @@ class ExampleInstrumentedTest {
fun useAppContext() { fun useAppContext() {
// Context of the app under test. // Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.approagency.drug", appContext.packageName) assertEquals("com.approagency.pharmacy", appContext.packageName)
} }
} }
@@ -14,64 +14,78 @@ object PharmacyHtmlParser {
val doc = Jsoup.parse(html) val doc = Jsoup.parse(html)
val table = doc.select("table#TBL_patientReferral").first() ?: return emptyList() 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") val rows = table.select("tbody tr")
for (row in rows) { for (row in rows) {
val cells = row.select("td") val cells = row.select("td")
if (cells.size < 3) continue if (cells.isEmpty()) continue
// ایندکس ستون‌ها بر اساس وجود ستون تضمین // ستون داروخانه (ضروری) — اگر از سربرگ پیدا نشد، سلولی که لینک ph- دارد
val brandIndex = if (hasGuaranteeColumn) 1 else 0 val pIdx = if (pharmacyIndex in cells.indices) {
val pharmacyIndex = if (hasGuaranteeColumn) 2 else 1 pharmacyIndex
val locationIndex = if (hasGuaranteeColumn) 3 else 2 } else {
cells.indexOfFirst { cell ->
cell.select("a").any { it.attr("href").contains("ph-") }
}
}
if (pIdx < 0) continue
// ستون برند val pharmacyCell = cells[pIdx]
val brandCell = cells[brandIndex] // پیدا کردن لینک داروخانه (لینکی که "اطلاعات تماس" نباشد)
val brandLink = brandCell.select("a").first() 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 brandName = brandLink?.text()?.trim() ?: ""
val brandUrl = normalizeUrl(brandLink?.attr("href")?.trim() ?: "") val brandUrl = normalizeUrl(brandLink?.attr("href")?.trim() ?: "")
val brandId = extractIdFromUrl(brandUrl, "/B-(\\d+)/?") val brandId = extractIdFromUrl(brandUrl, "/B-(\\d+)/?")
// ستون داروخانه // ستون موقعیت — اگر از سربرگ پیدا نشد، سلولی که «استان» دارد
val pharmacyCell = cells[pharmacyIndex] val locIdx = if (locationIndex in cells.indices) {
val pharmacyLinks = pharmacyCell.select("a") locationIndex
} else {
// پیدا کردن لینک داروخانه (لینکی که "اطلاعات تماس" نباشد) cells.indexOfFirst { it.text().contains("استان") }
val pharmacyLink = pharmacyLinks.firstOrNull {
!it.text().contains("اطلاعات تماس")
} }
val pharmacyName = pharmacyLink?.text()?.trim() ?: "" val locationText = locIdx.takeIf { it in cells.indices }?.let { cells[it].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 province = extractProvince(locationText) val province = extractProvince(locationText)
val city = extractCity(locationText) val city = extractCity(locationText)
if (brandName.isNotBlank() && pharmacyName.isNotBlank()) { items.add(
items.add( PharmacyItem(
PharmacyItem( brandName = brandName,
brandName = brandName, brandUrl = brandUrl,
brandUrl = brandUrl, brandId = brandId,
brandId = brandId, pharmacyName = pharmacyName,
pharmacyName = pharmacyName, pharmacyUrl = pharmacyUrl,
pharmacyUrl = pharmacyUrl, pharmacyId = pharmacyId,
pharmacyId = pharmacyId, province = province,
province = province, city = city
city = city
)
) )
} )
} }
return items 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 { private fun extractProvince(locationText: String): String {
// الگو: "استان تهران" یا "استان اصفهان" // الگو: "استان تهران" یا "استان اصفهان"
val provincePattern = "استان\\s*(\\S+)".toRegex() val provincePattern = "استان\\s*(\\S+)".toRegex()
@@ -94,12 +94,13 @@ class DrugRepositoryImpl(
override suspend fun searchPharmacies( override suspend fun searchPharmacies(
genericDrugId: String, genericDrugId: String,
brandIrc: String,
provId: String provId: String
): List<PharmacyItem> { ): List<PharmacyItem> {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
try { try {
val html = darooyabApiService.getPharmacies( val html = darooyabApiService.getPharmacies(
brandIrc = "0", brandIrc = brandIrc,
genericDrugId = genericDrugId, genericDrugId = genericDrugId,
provId = provId, provId = provId,
cityId = "0" cityId = "0"
@@ -16,7 +16,7 @@ interface DrugRepository {
suspend fun getGorohDaroei(): Result<DarmanModel> suspend fun getGorohDaroei(): Result<DarmanModel>
suspend fun searchDrugs(params:DaroYabParams):Result<DaroYabSearchResult> suspend fun searchDrugs(params:DaroYabParams):Result<DaroYabSearchResult>
suspend fun getDrugDetailFromYab(detailUrl: String):Result<DrugDetail> 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 suspend fun getPharmacyDetail(pharmacyUrl: String): PharmacyDetail
@@ -8,8 +8,9 @@ class GetPharmaciesUseCase(
) { ) {
suspend operator fun invoke( suspend operator fun invoke(
genericDrugId: String, genericDrugId: String,
brandIrc: String,
provinceId: String provinceId: String
): List<PharmacyItem> { ): 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) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun PharmacyBottomSheet( fun PharmacyBottomSheet(
viewModel: PharmacyViewModel, genericDrugId: String, isOpen: Boolean, onDismiss: () -> Unit viewModel: PharmacyViewModel,
genericDrugId: String,
brandIrc: String,
isOpen: Boolean,
onDismiss: () -> Unit
) { ) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val sheetState = rememberModalBottomSheetState( val sheetState = rememberModalBottomSheetState(
@@ -45,11 +49,10 @@ fun PharmacyBottomSheet(
val pharmacyState by viewModel.state.collectAsState() val pharmacyState by viewModel.state.collectAsState()
// جستجوی اولیه با استان تهران // جستجوی اولیه با استان تهران — با تغییر دارو دوباره اجرا می‌شود
LaunchedEffect(Unit) { // (در صورت خالی بودن شناسه، ViewModel وضعیت قبلی را پاک و خطا نمایش می‌دهد)
if (genericDrugId.isNotBlank()) { LaunchedEffect(genericDrugId, brandIrc) {
viewModel.search(genericDrugId, "40") viewModel.search(genericDrugId, brandIrc, "40")
}
} }
if (isOpen) { if (isOpen) {
@@ -98,7 +101,7 @@ fun PharmacyBottomSheet(
selectedProvinceId = province.id selectedProvinceId = province.id
showProvinceDropdown = false 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)) Spacer(modifier = Modifier.height(MaterialTheme.dime.md))
if (showPharmacyBottomSheet && selectedDrugForPharmacy != null) { if (showPharmacyBottomSheet && selectedDrugForPharmacy != null) {
// استخراج genericDrugId از URL // درخواست بر اساس نوع صفحه فرق می‌کند:
// مثال: /G-799/Fexofenadine -> 799 // - صفحات برند (/B-...): شناسه باید به‌عنوان brandIrc ارسال شود
val genericId = extractGenericIdFromUrl(selectedDrugForPharmacy?.detailPageUrl ?: "") // - صفحات ژنریک (/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( PharmacyBottomSheet(
viewModel = pharmacyViewModel, viewModel = pharmacyViewModel,
genericDrugId = genericId, genericDrugId = genericId,
brandIrc = brandIrc,
isOpen = showPharmacyBottomSheet, isOpen = showPharmacyBottomSheet,
onDismiss = { onDismiss = {
showPharmacyBottomSheet = false 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() val pharmacyDetailStates: StateFlow<Map<String, PharmacyDetailState>> = _pharmacyDetailStates.asStateFlow()
private var currentGenericDrugId: String = "" private var currentGenericDrugId: String = ""
private var currentBrandIrc: String = "0"
private var currentProvinceId: String = "0" private var currentProvinceId: String = "0"
fun loadPharmacyDetail(pharmacyUrl: String) { fun loadPharmacyDetail(pharmacyUrl: String) {
@@ -67,16 +68,26 @@ class PharmacyViewModel(
} }
fun search(genericDrugId: String, provinceId: String) { fun search(genericDrugId: String, brandIrc: String, provinceId: String) {
if (genericDrugId.isBlank()) return
currentGenericDrugId = genericDrugId currentGenericDrugId = genericDrugId
currentBrandIrc = brandIrc
currentProvinceId = provinceId 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 { viewModelScope.launch {
_state.value = PharmacyState.Loading _state.value = PharmacyState.Loading
try { try {
val result = getPharmaciesUseCase(genericDrugId, provinceId) val result = getPharmaciesUseCase(genericDrugId, brandIrc, provinceId)
_state.value = PharmacyState.Success(result) _state.value = PharmacyState.Success(result)
} catch (e: Exception) { } catch (e: Exception) {
_state.value = PharmacyState.Error(e.message ?: "خطا در دریافت اطلاعات") _state.value = PharmacyState.Error(e.message ?: "خطا در دریافت اطلاعات")
@@ -85,8 +96,6 @@ class PharmacyViewModel(
} }
fun retry() { fun retry() {
if (currentGenericDrugId.isNotBlank()) { search(currentGenericDrugId, currentBrandIrc, currentProvinceId)
search(currentGenericDrugId, currentProvinceId)
}
} }
} }