diff --git a/ecs/jskult-webapp/src/controller/ultmarc.py b/ecs/jskult-webapp/src/controller/ultmarc.py index 2f1bd5eb..ed966685 100644 --- a/ecs/jskult-webapp/src/controller/ultmarc.py +++ b/ecs/jskult-webapp/src/controller/ultmarc.py @@ -11,6 +11,7 @@ from src.model.view.bio_view_model import BioViewModel from src.router.session_router import AuthenticatedRoute from src.services.batch_status_service import BatchStatusService from src.services.bio_view_service import BioViewService +from src.services.ultmarc_view_service import UltmarcViewService from src.services.session_service import set_session from src.system_var import constants from src.templates import templates @@ -27,14 +28,14 @@ router.route_class = AuthenticatedRoute def ultmarc_view( request: Request, # batch_status_service:BatchStatusService=Depends(get_service(BatchStatusService)), - # bio_service: BioViewService=Depends(get_service(BioViewService)) + ultmarc_service: UltmarcViewService = Depends(get_service(UltmarcViewService)) ): session: UserSession = request.session # バッチ処理中の場合、機能を利用させない # if batch_status_service.is_batch_processing(): # raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=constants.LOGOUT_REASON_BATCH_PROCESSING) # # 検索項目の取得 - # bio = bio_service.prepare_bio_view(session) + ultmarc = ultmarc_service.prepare_ultmarc_doctor_view(session) # セッション書き換え session.update( actions=[ @@ -46,7 +47,7 @@ def ultmarc_view( templates_response = templates.TemplateResponse( 'docSearch.html', { 'request': request, - # 'bio': bio, + 'ultmarc': ultmarc, }, headers={'session_key': session_key} ) diff --git a/ecs/jskult-webapp/src/model/db/prefc_master.py b/ecs/jskult-webapp/src/model/db/prefc_master.py new file mode 100644 index 00000000..5010149e --- /dev/null +++ b/ecs/jskult-webapp/src/model/db/prefc_master.py @@ -0,0 +1,8 @@ +from typing import Optional + +from src.model.db.base_db_model import BaseDBModel + + +class PrefcMasterModel(BaseDBModel): + prefc_cd: Optional[str] + prefc_name: Optional[str] diff --git a/ecs/jskult-webapp/src/model/view/ultmarc_doctor_view_model.py b/ecs/jskult-webapp/src/model/view/ultmarc_doctor_view_model.py new file mode 100644 index 00000000..1e3cf236 --- /dev/null +++ b/ecs/jskult-webapp/src/model/view/ultmarc_doctor_view_model.py @@ -0,0 +1,113 @@ +import json +from collections import OrderedDict +from datetime import datetime +from typing import Optional + +from pydantic import BaseModel + +from src.model.db.prefc_master import PrefcMasterModel +from src.model.db.wholesaler_master import WholesalerMasterModel +from src.model.request.bio import BioModel +from src.model.view.bio_disp_model import BisDisplayModel +from src.system_var import environment + + +class UltmarcDoctorViewModel(BaseModel): + subtitle: str = '医師検索一覧' + batch_status: Optional[str] + prefc_models: list[PrefcMasterModel] + doctor_data: Optional[list[BisDisplayModel]] = [] + form_data: Optional[BioModel] + + def display_wholesaler_names(self): + display_names = [ + f'{whs_model.rec_whs_cd}-{whs_model.rec_whs_sub_cd}:{whs_model.nm}' + for whs_model in self.whs_models + ] + return display_names + + def ultmarc_data_json_str(self): + def date_handler(obj): + return obj.isoformat() if hasattr(obj, 'isoformat') else obj + return json.dumps([model.dict() for model in self.doctor_data], ensure_ascii=False, default=date_handler) + + # def is_selected_whs_name(self, selected_wholesaler): + # if not self.is_form_submitted(): + # return '' + + # form_wholesaler_full_name = f'{self.form_data.wholesaler_code}-{self.form_data.wholesaler_sub_code}:{self.form_data.wholesaler_name}' + # return self._selected_value(form_wholesaler_full_name, selected_wholesaler) + + # def is_selected_org_kbn(self, selected_org_kbn): + # if not self.is_form_submitted(): + # return '' + # return self._selected_value(self.form_data.org_kbn, selected_org_kbn) + + # def is_input_rec_ymd_from(self): + # if not self.is_form_submitted(): + # return '' + + # return self._format_date_string(self.form_data.rec_ymd_from) + + # def is_input_rec_ymd_to(self): + # if not self.is_form_submitted(): + # return '' + + # return self._format_date_string(self.form_data.rec_ymd_to) + + # def is_input_lot_num(self): + # if not self.is_form_submitted(): + # return '' + + # return self.form_data.rec_lot_num or '' + + # def is_selected_data_kbn(self, selected_data_kbn): + # if not self.is_form_submitted(): + # return '' + + # return self._selected_value(self.form_data.data_kbn, selected_data_kbn) + + def is_selected_prefc_cd(self, selected_prefc_cd): + if not self.is_form_submitted(): + return '' + return '' + # return self._selected_value(self.form_data.maker_cd, selected_prefc_cd) + + # def is_input_rev_hsdnymd_srk_from(self): + # if not self.is_form_submitted(): + # return '' + + # return self._format_date_string(self.form_data.rev_hsdnymd_srk_from) + + # def is_input_rev_hsdnymd_srk_to(self): + # if not self.is_form_submitted(): + # return '' + + # return self._format_date_string(self.form_data.rev_hsdnymd_srk_to) + + # def is_checked_iko_flg(self): + # if not self.is_form_submitted(): + # return '' + + # return 'checked' if self.form_data.ikoFlg else '' + + def disabled_button(self): + return 'disabled' if self.is_data_empty() or self.is_data_overflow_max_length() else '' + + def is_form_submitted(self): + return self.form_data is not None + + def is_data_empty(self): + return len(self.doctor_data) == 0 + + def is_data_overflow_max_length(self): + return len(self.doctor_data) >= environment.BIO_SEARCH_RESULT_MAX_COUNT + + def _format_date_string(self, date_string): + if date_string is None: + return '' + date = datetime.strptime(date_string, '%Y%m%d') + return date.strftime('%Y/%m/%d') + + def _selected_value(self, form_value: str, current_value: str): + return 'selected' if form_value == current_value else '' diff --git a/ecs/jskult-webapp/src/repositories/prefc_master_repository.py b/ecs/jskult-webapp/src/repositories/prefc_master_repository.py new file mode 100644 index 00000000..a8cf288f --- /dev/null +++ b/ecs/jskult-webapp/src/repositories/prefc_master_repository.py @@ -0,0 +1,31 @@ +from src.model.db.prefc_master import PrefcMasterModel +from src.repositories.base_repository import BaseRepository + + +class PrefcMasterRepository(BaseRepository): + + FETCH_SQL = """\ + SELECT DISTINCT + com_inst.prefc_cd AS prefc_cd, + mst_prefc.prefc_name AS prefc_name + FROM + src05.com_inst + JOIN src05.mst_prefc ON com_inst.prefc_cd = mst_prefc.prefc_cd + ORDER BY + mst_prefc.prefc_cd + """ + + def fetch_all(self) -> list[PrefcMasterModel]: + try: + self._database.connect() + result = self._database.execute_select(self.FETCH_SQL) + result_data = [res for res in result] + models = [PrefcMasterModel(**r) for r in result_data] + return models + except Exception as e: + # TODO: ファイルへの書き出しはloggerでやる + print(f"[ERROR] getOroshiData DB Error. ") + print(f"[ERROR] ErrorMessage: {e.args}") + raise e + finally: + self._database.disconnect() diff --git a/ecs/jskult-webapp/src/services/ultmarc_view_service.py b/ecs/jskult-webapp/src/services/ultmarc_view_service.py new file mode 100644 index 00000000..6d296f74 --- /dev/null +++ b/ecs/jskult-webapp/src/services/ultmarc_view_service.py @@ -0,0 +1,111 @@ +import os.path as path +import shutil +from datetime import datetime + +import pandas as pd + +from src.aws.aws_api_client import AWSAPIClient +from src.aws.s3 import S3Client +from src.model.internal.session import UserSession +from src.model.request.bio import BioModel +from src.model.view.bio_disp_model import BisDisplayModel +from src.model.view.ultmarc_doctor_view_model import UltmarcDoctorViewModel +from src.repositories.base_repository import BaseRepository +from src.repositories.bio_sales_view_repository import BioSalesViewRepository +from src.repositories.prefc_master_repository import PrefcMasterRepository +from src.repositories.pharmacy_product_master_repository import \ + PharmacyProductMasterRepository +from src.repositories.wholesaler_master_repository import \ + WholesalerMasterRepository +from src.services.base_service import BaseService +from src.system_var import constants, environment + + +class UltmarcViewService(BaseService): + REPOSITORIES = { + 'ultmarc_doctor_repository': WholesalerMasterRepository, + 'prefc_repository': PrefcMasterRepository + } + + ultmarc_doctor_repository: WholesalerMasterRepository + prefc_repository: PrefcMasterRepository + + def __init__(self, repositories: dict[str, BaseRepository], clients: dict[str, AWSAPIClient]) -> None: + super().__init__(repositories, clients) + self.ultmarc_doctor_repository = repositories['ultmarc_doctor_repository'] + self.prefc_repository = repositories['prefc_repository'] + + def prepare_ultmarc_doctor_view( + self, + session: UserSession + ) -> UltmarcDoctorViewModel: + # # 都道府県リストを取得 + prefcs = self.prefc_repository.fetch_all() + + ultmarc = UltmarcDoctorViewModel( + prefc_models=prefcs + ) + return ultmarc + + def search_bio_data(self, search_params: BioModel): + # 生物由来データを検索 + bio_sales_view_data = self.bio_sales_repository.fetch_many(parameter=search_params) + # 画面表示用に加工 + display_bio_data: list[BisDisplayModel] = [BisDisplayModel(data) for data in bio_sales_view_data] + + return display_bio_data + + def search_download_bio_data(self, search_params: BioModel): + # 生物由来データをダウンロードするために、DBから検索した結果をデータフレームに変換 + bio_sales_data_frame = self.bio_sales_repository.fetch_as_data_frame(parameter=search_params) + return bio_sales_data_frame + + def write_excel_file(self, data_frame: pd.DataFrame, user_id: str, timestamp: datetime): + # Excelに書き込み + output_file_path = path.join(constants.BIO_TEMPORARY_FILE_DIR_PATH, + f'Result_{user_id}_{timestamp:%Y%m%d%H%M%S%f}.xlsx') + + # テンプレートファイルをコピーして出力ファイルの枠だけを作る + shutil.copyfile( + src=constants.BIO_EXCEL_TEMPLATE_FILE_PATH, + dst=output_file_path + ) + # ExcelWriterの追記モード(`mode`='a')でファイルを開く + # `engine``='openpyxlは、追記モードでExcelを開くためのおまじない(xlsxしか動作しないが、こちらが出すものなので問題ナシ) + # 既存シートへの書き込みは、`if_sheet_exists='overlay'を指定する + with pd.ExcelWriter(output_file_path, engine='openpyxl', mode='a', if_sheet_exists='overlay') as writer: + # `sheet_name`引数を省略した場合は、「Sheet1」に書き込む。 + # DF内のヘッダと連番を書き込みたくない場合、`header`と`index`をFalseに指定する。 + # `startrow`と`startcol`で、Excelの書き込み位置を決定する。省略した場合はA1セルから書く。 + data_frame.to_excel(writer, header=False, index=False, startrow=1, startcol=0) + + return output_file_path + + def write_csv_file(self, data_frame: pd.DataFrame, user_id: str, header: list[str], timestamp: datetime): + # csvに書き込み + output_file_path = path.join(constants.BIO_TEMPORARY_FILE_DIR_PATH, + f'Result_{user_id}_{timestamp:%Y%m%d%H%M%S%f}.csv') + # 横長のDataFrameとするため、ヘッダーの加工処理 + header_data = {} + for df_column, header_column in zip(data_frame.columns, header): + header_data[df_column] = header_column + + header_df = pd.DataFrame([header_data], index=None) + output_df = pd.concat([header_df, data_frame]) + # ヘッダー行としてではなく、1レコードとして出力する + output_df.to_csv(output_file_path, index=False, header=False) + + return output_file_path + + def upload_bio_data_file(self, local_file_path: str) -> None: + bucket_name = environment.BIO_ACCESS_LOG_BUCKET + # TODO: フォルダを変える + file_key = f'bio/{path.basename(local_file_path)}' + self.s3_client.upload_file(local_file_path, bucket_name, file_key) + + def generate_download_file_url(self, local_file_path: str, user_id: str, kind: str) -> str: + bucket_name = environment.BIO_ACCESS_LOG_BUCKET + # TODO: フォルダを変える + file_key = f'bio/{path.basename(local_file_path)}' + download_filename = f'{user_id}_生物由来卸販売データ.{kind}' + return self.s3_client.generate_presigned_url(bucket_name, file_key, download_filename) diff --git a/ecs/jskult-webapp/src/templates/docSearch.html b/ecs/jskult-webapp/src/templates/docSearch.html index 214ea399..45189de3 100644 --- a/ecs/jskult-webapp/src/templates/docSearch.html +++ b/ecs/jskult-webapp/src/templates/docSearch.html @@ -11,7 +11,7 @@ require_once('/home/nds_dwh/webroot/common/function/getDateBatchJSString.php'); - {% with subtitle = '医師検索一覧' %} + {% with subtitle = ultmarc.subtitle %} {% include '_header.html' %} {% endwith %} @@ -231,6 +231,12 @@ if (!isset($isDBSuccess)) {