fix: brand id and genetic id are fixed
This commit is contained in:
@@ -19,6 +19,6 @@ class ExampleInstrumentedTest {
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
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 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)
|
||||
}
|
||||
}
|
||||
+10
-7
@@ -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) ?: ""
|
||||
}
|
||||
+16
-7
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user