diff --git a/ecs/jskult-batch-daily/.env.example b/ecs/jskult-batch-daily/.env.example
index 95aef7fe..7463e0d2 100644
--- a/ecs/jskult-batch-daily/.env.example
+++ b/ecs/jskult-batch-daily/.env.example
@@ -11,3 +11,9 @@ ULTMARC_BACKUP_FOLDER=ultmarc
JSKULT_CONFIG_BUCKET=**********************
JSKULT_CONFIG_CALENDAR_FOLDER=jskult/calendar
JSKULT_CONFIG_CALENDAR_HOLIDAY_LIST_FILE_NAME=jskult_holiday_list.txt
+# 連携データ抽出期間
+SALES_LAUNDERING_EXTRACT_DATE_PERIOD=0
+# 洗替対象テーブル名
+SALES_LAUNDERING_TARGET_TABLE_NAME=src05.sales_lau
+# 卸実績洗替で作成するデータの期間(年単位)
+SALES_LAUNDERING_TARGET_YEAR_OFFSET=5
\ No newline at end of file
diff --git a/ecs/jskult-batch-daily/src/batch/dcf_inst_merge/integrate_dcf_inst_merge.py b/ecs/jskult-batch-daily/src/batch/dcf_inst_merge/integrate_dcf_inst_merge.py
new file mode 100644
index 00000000..816a0545
--- /dev/null
+++ b/ecs/jskult-batch-daily/src/batch/dcf_inst_merge/integrate_dcf_inst_merge.py
@@ -0,0 +1,647 @@
+from datetime import datetime, timedelta
+from src.batch.batch_functions import logging_sql
+from src.batch.common.batch_context import BatchContext
+from src.db.database import Database
+from src.error.exceptions import BatchOperationException
+from src.logging.get_logger import get_logger
+from src.time.elapsed_time import ElapsedTime
+
+batch_context = BatchContext.get_instance()
+logger = get_logger('DCF施設統合マスタ日次更新')
+
+
+def exec():
+ db = Database.get_instance()
+ try:
+ db.connect()
+ db.begin()
+ logger.debug('DCF施設統合マスタ日次更新処理開始')
+ # DCF施設統合マスタ移行先コードのセット(無効フラグが『0(有効)』)
+ enabled_dst_inst_merge_records = _set_enabled_dct_inst_merge(db)
+ # DCF施設統合マスタ移行先コードのセット(無効フラグが『1(無効)』)
+ _set_disabled_dct_inst_merge(db)
+ # DCF施設統合マスタに無効フラグが『0(有効)』データが存在する場合
+ if len(enabled_dst_inst_merge_records) > 0:
+ _add_emp_chg_inst(db, enabled_dst_inst_merge_records)
+ _add_ult_ident_presc(db, enabled_dst_inst_merge_records)
+ db.commit()
+ logger.debug('DCF施設統合マスタ日次更新処理終了')
+ except Exception as e:
+ db.rollback()
+ raise BatchOperationException(e)
+ finally:
+ db.disconnect()
+
+
+def _set_enabled_dct_inst_merge(db: Database) -> list[dict]:
+ # データ取得(無効フラグが『0(有効)』)
+ enabled_dst_inst_merge_records = _select_dct_inst_merge(db, 0)
+ # 移行先DCF施設コードの更新(無効フラグが『0(有効)』)
+ if _update_dcf_inst_merge(db, 0) > 0:
+ # DCF施設統合マスタの過去分の洗い替え
+ for row in enabled_dst_inst_merge_records:
+ _update_dcf_inst_cd_new(db, row['dup_opp_cd'], row['dcf_inst_cd'], '')
+
+ return enabled_dst_inst_merge_records
+
+
+def _set_disabled_dct_inst_merge(db: Database):
+ # データ取得(無効フラグが『1(無効)』)
+ disabled_dst_inst_merge_records = _select_dct_inst_merge(db, 1)
+ # 移行先DCF施設コードの更新(無効フラグが『1(無効)』)
+ if _update_dcf_inst_merge(db, 1) > 0:
+ # DCF施設統合マスタの過去分の洗い替え
+ for row in disabled_dst_inst_merge_records:
+ _update_dcf_inst_cd_new(db, row['dcf_inst_cd'], row['dup_opp_cd'], '戻し')
+
+
+def _select_ult_ident_presc_dcf_inst_cd(db: Database, dcf_inst_cd: str) -> list[dict]:
+ # 納入先処方元マスタから、DCF施設コードに対応したレコードの取得
+ try:
+ sql = """
+ SELECT
+ ta_cd,
+ ult_ident_cd,
+ ratio
+ FROM
+ src05.ult_ident_presc
+ WHERE
+ presc_cd = :dcf_inst_cd
+ AND (SELECT ht.syor_date FROM src05.hdke_tbl AS ht) < end_date
+ """
+ params = {'dcf_inst_cd': dcf_inst_cd}
+ ult_ident_presc_ta_cd_records = db.execute_select(sql, params)
+ logging_sql(logger, sql)
+ logger.info('納入先処方元マスタからDCF施設コードに対応したレコードの取得に成功')
+ except Exception as e:
+ logger.debug('納入先処方元マスタからDCF施設コードに対応したレコードの取得に失敗')
+ raise e
+
+ return ult_ident_presc_ta_cd_records
+
+
+def _add_ult_ident_presc(db: Database, enabled_dst_inst_merge_records: list[dict]):
+ # 納入先処方元マスタの追加
+ logger.info('納入先処方元マスタの登録 開始')
+ for data_inst_cnt, enabled_merge_record in enumerate(enabled_dst_inst_merge_records, start=1):
+ tekiyo_month_first_day = _get_first_day_of_month(enabled_merge_record['tekiyo_month'])
+ ult_ident_presc_source_records = _select_ult_ident_presc_dcf_inst_cd(db, enabled_merge_record['dcf_inst_cd'])
+ for ult_ident_presc_source_record in ult_ident_presc_source_records:
+ ult_ident_presc_records = _select_ult_ident_presc(db,
+ enabled_merge_record['dcf_inst_cd'],
+ enabled_merge_record['dup_opp_cd'],
+ ult_ident_presc_source_record)
+ for data_cnt, ult_ident_presc_row in enumerate(ult_ident_presc_records, start=1):
+ logger.info(f'{data_inst_cnt}件目の移行施設の{data_cnt}レコード目処理 開始')
+ # 処方元コード=重複時相手先コードが発生した場合
+ if ult_ident_presc_row['opp_count'] > 0:
+ continue
+
+ start_date = _str_to_date_time(ult_ident_presc_row['start_date'])
+ set_start_date = start_date \
+ if start_date > tekiyo_month_first_day else tekiyo_month_first_day
+ set_start_date = _date_time_to_str(set_start_date)
+ is_exists_duplicate_key = False
+ if _count_duplicate_ult_ident_presc(db, set_start_date, ult_ident_presc_row) > 0:
+ _delete_ult_ident_presc(db, set_start_date, ult_ident_presc_row,
+ '納入先処方元マスタの重複予定データの削除')
+ is_exists_duplicate_key = True
+ else:
+ logger.info('納入先処方元マスタの重複予定データなし')
+ _insert_ult_ident_presc(db, set_start_date, enabled_merge_record['dup_opp_cd'], ult_ident_presc_row)
+
+ # 重複予定データが存在しない、且つ、適用終了日 ≧ 適用開始日の場合
+ if not is_exists_duplicate_key and _str_to_date_time(ult_ident_presc_row['end_date']) >= start_date:
+ last_end_date = tekiyo_month_first_day - timedelta(days=1)
+ # 適用終了日を、DCF施設統合マスタの適用月度の前月末日で更新
+ _update_ult_ident_presc_end_date(db, _date_time_to_str(last_end_date), ult_ident_presc_row)
+
+ logger.info('納入先処方元マスタの登録 終了')
+
+
+def _select_emp_chg_inst_ta_cd(db: Database, dcf_inst_cd: str) -> list[dict]:
+ # 従業員担当施設マスタから、DCF施設コードに対応した領域コードの取得
+ try:
+ sql = """
+ SELECT
+ ta_cd
+ FROM
+ src05.emp_chg_inst
+ WHERE
+ inst_cd = :dcf_inst_cd
+ AND enabled_flg = 'Y'
+ AND (SELECT ht.syor_date FROM src05.hdke_tbl AS ht) < end_date
+ """
+ params = {'dcf_inst_cd': dcf_inst_cd}
+ emp_chg_inst_ta_cd_records = db.execute_select(sql, params)
+ logging_sql(logger, sql)
+ logger.info('従業員担当施設マスタから領域コードの取得に成功')
+ except Exception as e:
+ logger.debug('従業員担当施設マスタから領域コードの取得に失敗')
+ raise e
+
+ return emp_chg_inst_ta_cd_records
+
+
+def _add_emp_chg_inst(db: Database, enabled_dst_inst_merge_records: list[dict]):
+ # 従業員担当施設マスタの登録
+ logger.info('従業員担当施設マスタの登録 開始')
+ for enabled_merge_record in enabled_dst_inst_merge_records:
+ tekiyo_month_first_day = _get_first_day_of_month(enabled_merge_record['tekiyo_month'])
+ emp_chg_inst_ta_cd_records = _select_emp_chg_inst_ta_cd(db, enabled_merge_record['dcf_inst_cd'])
+ for emp_chg_inst_ta_cd_record in emp_chg_inst_ta_cd_records:
+ emp_chg_inst_records = _select_emp_chg_inst(db, enabled_merge_record['dcf_inst_cd'], enabled_merge_record['dup_opp_cd'],
+ emp_chg_inst_ta_cd_record['ta_cd'])
+ for emp_chg_inst_row in emp_chg_inst_records:
+ # 重複時相手先コードが存在したかのチェック
+ if emp_chg_inst_row['opp_count'] > 0:
+ continue
+
+ start_date = _str_to_date_time(emp_chg_inst_row['start_date'])
+ set_start_date = start_date \
+ if start_date > tekiyo_month_first_day else tekiyo_month_first_day
+
+ _insert_emp_chg_inst(db, enabled_merge_record['dup_opp_cd'], _date_time_to_str(set_start_date),
+ emp_chg_inst_row)
+
+ # 適用開始日 < DCF施設統合マスタの適用月度の1日の場合
+ if start_date < tekiyo_month_first_day:
+ # DCF施設統合マスタの適用月度の前月末日で、適用終了日を更新する
+ last_end_date = tekiyo_month_first_day - timedelta(days=1)
+ _update_emp_chg_inst_end_date(db, enabled_merge_record['dcf_inst_cd'], _date_time_to_str(last_end_date),
+ emp_chg_inst_row)
+ continue
+ # 適用開始日 ≧ DCF施設統合マスタの適用月度の1日の場合、N(論理削除レコード)に設定する
+ _update_emp_chg_inst_disabled(db, enabled_merge_record['dcf_inst_cd'], emp_chg_inst_row['ta_cd'],
+ emp_chg_inst_row['start_date'])
+
+ logger.info('従業員担当施設マスタの登録 終了')
+
+
+def _delete_ult_ident_presc(db: Database, start_date: str, ult_ident_presc_row: dict,
+ log_message: str):
+ # ult_ident_prescのDelete
+ try:
+ elapsed_time = ElapsedTime()
+ sql = """
+ DELETE FROM
+ src05.ult_ident_presc
+ WHERE
+ ta_cd = :ta_cd
+ AND ult_ident_cd = :ult_ident_cd
+ AND ratio = :ratio
+ AND start_date = :start_date
+ """
+ params = {
+ 'ta_cd': ult_ident_presc_row['ta_cd'],
+ 'ult_ident_cd': ult_ident_presc_row['ult_ident_cd'],
+ 'ratio': ult_ident_presc_row['ratio'],
+ 'start_date': start_date
+ }
+ res = db.execute(sql, params)
+ logging_sql(logger, sql)
+ logger.info(f'{log_message} 成功, {res.rowcount} 行更新 ({elapsed_time.of})')
+ except Exception as e:
+ logger.debug(f'{log_message} 失敗')
+ raise e
+
+
+def _update_emp_chg_inst_disabled(db: Database, dcf_inst_cd: str, ta_cd: str, start_date: str):
+ # emp_chg_instをUPDATE
+ try:
+ elapsed_time = ElapsedTime()
+ sql = """
+ UPDATE
+ src05.emp_chg_inst
+ SET
+ enabled_flg = 'N',
+ updater = CURRENT_USER(),
+ update_date = SYSDATE()
+ WHERE
+ inst_cd = :dcf_inst_cd
+ AND ta_cd = :ta_cd
+ AND start_date = :start_date
+ """
+ params = {'dcf_inst_cd': dcf_inst_cd, 'ta_cd': ta_cd, 'start_date': start_date}
+ res = db.execute(sql, params)
+ logging_sql(logger, sql)
+ logger.info(f'従業員担当施設マスタのYorNフラグ更新に成功, {res.rowcount} 行更新 ({elapsed_time.of})')
+ except Exception as e:
+ logger.debug('従業員担当施設マスタのYorNフラグ更新に失敗')
+ raise e
+
+
+def _update_emp_chg_inst_end_date(db: Database, dcf_inst_cd: str, last_end_date: str,
+ emp_chg_inst_row: dict):
+ # emp_chg_instをUPDATE
+ try:
+ elapsed_time = ElapsedTime()
+ sql = """
+ UPDATE
+ src05.emp_chg_inst
+ SET end_date = :end_date,
+ updater = CURRENT_USER(),
+ update_date = SYSDATE()
+ WHERE
+ inst_cd = :dcf_inst_cd
+ AND ta_cd = :ta_cd
+ AND emp_cd = :emp_cd
+ AND bu_cd = :bu_cd
+ AND start_date = :start_date
+ """
+ params = {
+ 'end_date': last_end_date,
+ 'dcf_inst_cd': dcf_inst_cd,
+ 'ta_cd': emp_chg_inst_row['ta_cd'],
+ 'emp_cd': emp_chg_inst_row['emp_cd'],
+ 'bu_cd': emp_chg_inst_row['bu_cd'],
+ 'start_date': emp_chg_inst_row['start_date']
+ }
+ res = db.execute(sql, params)
+ logging_sql(logger, sql)
+ logger.info(f'従業員担当施設マスタの適用終了日更新 成功, {res.rowcount} 行更新 ({elapsed_time.of})')
+ except Exception as e:
+ logger.debug('従業員担当施設マスタの適用終了日更新 失敗')
+ raise e
+
+
+def _insert_emp_chg_inst(db: Database, dup_opp_cd: str, set_start_date: str,
+ emp_chg_inst_row: dict):
+ # emp_chg_instにINSERT
+ try:
+ elapsed_time = ElapsedTime()
+ sql = """
+ INSERT INTO
+ src05.emp_chg_inst(
+ inst_cd,
+ ta_cd,
+ emp_cd,
+ bu_cd,
+ start_date,
+ end_date,
+ main_chg_flg,
+ enabled_flg,
+ creater,
+ create_date,
+ updater,
+ update_date
+ )
+ VALUES(
+ :dup_opp_cd,
+ :ta_cd,
+ :emp_cd,
+ :bu_cd,
+ :start_date,
+ :end_date,
+ :main_chg_flg,
+ 'Y',
+ CURRENT_USER(),
+ SYSDATE(),
+ CURRENT_USER(),
+ SYSDATE()
+ )
+ """
+ params = {
+ 'dup_opp_cd': dup_opp_cd,
+ 'ta_cd': emp_chg_inst_row['ta_cd'],
+ 'emp_cd': emp_chg_inst_row['emp_cd'],
+ 'bu_cd': emp_chg_inst_row['bu_cd'],
+ 'start_date': set_start_date,
+ 'end_date': emp_chg_inst_row['end_date'],
+ 'main_chg_flg': None
+ if emp_chg_inst_row['main_chg_flg'] is None else emp_chg_inst_row['main_chg_flg']
+ }
+ res = db.execute(sql, params)
+ logging_sql(logger, sql)
+ logger.info(f'従業員担当施設マスタの追加に成功, {res.rowcount} 行更新 ({elapsed_time.of})')
+ except Exception as e:
+ logger.debug('従業員担当施設マスタの追加に失敗')
+ raise e
+
+
+def _select_dct_inst_merge(db: Database, muko_flg: int) -> list[dict]:
+ # dcf_inst_mergeからSELECT
+ # 無効フラグがOFFのときは、移行先DCF施設コードが設定されてないデータを抽出する。
+ # ONのときは、移行先DCF施設コードが設定されているデータを抽出する。
+ try:
+ sql = """
+ SELECT
+ dim.dcf_inst_cd,
+ dim.dup_opp_cd,
+ dim.tekiyo_month
+ FROM
+ src05.dcf_inst_merge AS dim
+ INNER JOIN
+ src05.hdke_tbl AS ht
+ ON dim.tekiyo_month = DATE_FORMAT(ht.syor_date, '%Y%m')
+ WHERE
+ dim.muko_flg = :muko_flg
+ AND dim.enabled_flg = 'Y'
+ AND dim.dcf_inst_cd_new IS {not_null}NULL
+ """.format(
+ not_null='' if muko_flg == 0 else 'NOT '
+ )
+ params = {
+ 'muko_flg': muko_flg
+ }
+ dst_inst_merge_records = db.execute_select(sql, params)
+ logging_sql(logger, sql)
+ logger.info('DCF施設統合マスタの取得に成功')
+ except Exception as e:
+ logger.debug('DCF施設統合マスタの取得に失敗')
+ raise e
+
+ return dst_inst_merge_records
+
+
+def _update_dcf_inst_merge(db: Database, muko_flg: int) -> int:
+ # dcf_inst_mergeをUPDATE
+ # 無効フラグがOFFのときは、
+ # 移行先DCF施設コードが設定されていないデータを抽出し、移行先DCF施設コードに重複時相手先コードを上書きする
+ # 無効フラグがONのときは、
+ # 移行先DCF施設コードが設定されているデータを抽出し、移行先DCF施設コードにNULLを上書きする。
+ try:
+ elapsed_time = ElapsedTime()
+ log_message = '更新しました' if muko_flg == 0 else '無効データに戻しました'
+ sql = """
+ UPDATE
+ src05.dcf_inst_merge AS updim
+ INNER JOIN(
+ SELECT
+ dim.dcf_inst_cd AS base_dcf_inst_cd,
+ dim.dup_opp_cd AS base_dup_opp_cd,
+ dim.tekiyo_month AS base_tekiyo_month,
+ dim.muko_flg AS base_muko_flg,
+ dim.enabled_flg AS base_enabled_flg
+ FROM
+ src05.dcf_inst_merge AS dim
+ INNER JOIN
+ src05.hdke_tbl AS ht
+ ON dim.tekiyo_month = DATE_FORMAT(ht.syor_date, '%Y%m')
+ WHERE
+ dim.muko_flg = :muko_flg
+ AND dim.enabled_flg ='Y'
+ AND dim.dcf_inst_cd_new IS {not_null}NULL
+ ) AS bf_dim
+ SET
+ updim.dcf_inst_cd_new = {column},
+ updim.updater = CURRENT_USER(),
+ updim.update_date = SYSDATE()
+ WHERE
+ updim.dcf_inst_cd = base_dcf_inst_cd
+ AND updim.dup_opp_cd = base_dup_opp_cd
+ AND updim.tekiyo_month = base_tekiyo_month
+ AND updim.muko_flg = base_muko_flg
+ AND updim.enabled_flg = base_enabled_flg
+ """.format(
+ not_null='' if muko_flg == 0 else 'NOT ',
+ column='base_dup_opp_cd' if muko_flg == 0 else 'NULL'
+ )
+ params = {
+ 'muko_flg': muko_flg
+ }
+ res = db.execute(sql, params)
+ logging_sql(logger, sql)
+ logger.info(f'DCF施設統合マスタの有効データを{log_message} 成功, {res.rowcount} 行更新 ({elapsed_time.of})')
+ except Exception as e:
+ logger.debug(f'DCF施設統合マスタの有効データを{log_message} 失敗')
+ raise e
+
+ return res.rowcount
+
+
+def _update_dcf_inst_cd_new(db: Database, dcf_inst_cd_new_after: str, dcf_inst_cd_new_before: str, log_message: str):
+ # dcf_inst_mergeをUPDATE
+ try:
+ elapsed_time = ElapsedTime()
+ sql = """
+ UPDATE
+ src05.dcf_inst_merge
+ SET
+ dcf_inst_cd_new = :dcf_inst_cd_new_after,
+ updater = CURRENT_USER(),
+ update_date = SYSDATE()
+ WHERE
+ dcf_inst_cd_new = :dcf_inst_cd_new_before
+ AND enabled_flg = 'Y'
+ AND muko_flg = 0
+ """
+ params = {
+ 'dcf_inst_cd_new_after': dcf_inst_cd_new_after,
+ 'dcf_inst_cd_new_before': dcf_inst_cd_new_before
+ }
+ res = db.execute(sql, params)
+ logging_sql(logger, sql)
+ logger.info(f'移行先DCF施設コードの{log_message}更新に成功, {res.rowcount} 行更新 ({elapsed_time.of})')
+ except Exception as e:
+ logger.debug(f'移行先DCF施設コードの{log_message}更新に失敗')
+ raise e
+
+
+def _update_ult_ident_presc_end_date(db: Database, last_end_date: str, ult_ident_presc_row: dict):
+ # ult_ident_presc_endをUPDATE
+ try:
+ elapsed_time = ElapsedTime()
+ sql = """
+ UPDATE
+ src05.ult_ident_presc
+ SET end_date = :end_date,
+ updater = CURRENT_USER(),
+ update_date = SYSDATE()
+ WHERE
+ ta_cd = :ta_cd
+ AND ult_ident_cd = :ult_ident_cd
+ AND ratio = :ratio
+ AND start_date = :start_date
+ """
+ params = {
+ 'end_date': last_end_date,
+ 'ta_cd': ult_ident_presc_row['ta_cd'],
+ 'ult_ident_cd': ult_ident_presc_row['ult_ident_cd'],
+ 'ratio': ult_ident_presc_row['ratio'],
+ 'start_date': ult_ident_presc_row['start_date']
+ }
+ res = db.execute(sql, params)
+ logging_sql(logger, sql)
+ logger.info(f'終了日 > 開始月のため適用終了日を更新 成功, {res.rowcount} 行更新 ({elapsed_time.of})')
+ except Exception as e:
+ logger.debug('終了日 > 開始月のため適用終了日を更新 失敗')
+ raise e
+
+
+def _insert_ult_ident_presc(db: Database, set_Start_Date: str, dup_opp_cd: str,
+ ult_ident_presc_row: dict):
+ # ult_ident_prescにINSERT
+ try:
+ elapsed_time = ElapsedTime()
+ sql = """
+ INSERT INTO
+ src05.ult_ident_presc(
+ ta_cd,
+ ult_ident_cd,
+ ratio,
+ start_date,
+ presc_cd,
+ end_date,
+ creater,
+ create_date,
+ update_date,
+ updater
+ )
+ VALUES(
+ :ta_cd,
+ :ult_ident_cd,
+ :ratio,
+ :start_date,
+ :presc_cd,
+ :end_date,
+ CURRENT_USER(),
+ SYSDATE(),
+ SYSDATE(),
+ CURRENT_USER()
+ )
+ """
+ params = {
+ 'ta_cd': ult_ident_presc_row['ta_cd'],
+ 'ult_ident_cd': ult_ident_presc_row['ult_ident_cd'],
+ 'ratio': ult_ident_presc_row['ratio'],
+ 'start_date': set_Start_Date,
+ 'presc_cd': dup_opp_cd,
+ 'end_date': ult_ident_presc_row['end_date']
+ }
+ res = db.execute(sql, params)
+ logging_sql(logger, sql)
+ logger.info(f'納入先処方元マスタに追加 成功, {res.rowcount} 行更新 ({elapsed_time.of})')
+ except Exception as e:
+ logger.debug('納入先処方元マスタに追加 失敗')
+ raise e
+
+
+def _select_emp_chg_inst(db: Database, dcf_inst_cd: str, dup_opp_cd: str, ta_cd: str) -> list[dict]:
+ # emp_chg_instからSELECT
+ try:
+ sql = """
+ SELECT
+ eci.inst_cd,
+ eci.ta_cd,
+ eci.emp_cd,
+ eci.bu_cd,
+ eci.start_date,
+ eci.end_date,
+ eci.main_chg_flg,
+ eci.enabled_flg,
+ (
+ SELECT
+ COUNT(eciopp.inst_cd)
+ FROM
+ src05.emp_chg_inst AS eciopp
+ WHERE
+ eciopp.inst_cd = :dup_opp_cd
+ AND eciopp.ta_cd = :ta_cd
+ ) AS opp_count
+ FROM
+ src05.emp_chg_inst AS eci
+ WHERE
+ eci.inst_cd = :dcf_inst_cd
+ AND eci.ta_cd = :ta_cd
+ AND eci.enabled_flg = 'Y'
+ AND (SELECT ht.syor_date FROM src05.hdke_tbl AS ht) < eci.end_date
+ """
+ params = {'dcf_inst_cd': dcf_inst_cd, 'dup_opp_cd': dup_opp_cd, 'ta_cd': ta_cd}
+ emp_chg_inst_records = db.execute_select(sql, params)
+ logging_sql(logger, sql)
+ logger.info('従業員担当施設マスタの取得 成功')
+ except Exception as e:
+ logger.debug('従業員担当施設マスタの取得 失敗')
+ raise e
+ return emp_chg_inst_records
+
+
+def _select_ult_ident_presc(db: Database, dcf_inst_cd: str, dup_opp_cd: str,
+ ult_ident_presc_row: dict) -> list[dict]:
+ # ult_ident_prescからSELECT
+ try:
+ sql = """
+ SELECT
+ uip.ta_cd,
+ uip.ult_ident_cd,
+ uip.ratio,
+ uip.start_date,
+ uip.end_date,
+ (
+ SELECT
+ COUNT(uipopp.ta_cd)
+ FROM
+ src05.ult_ident_presc AS uipopp
+ WHERE
+ uipopp.presc_cd = :dup_opp_cd
+ AND uipopp.ta_cd = :ta_cd
+ AND uipopp.ult_ident_cd = :ult_ident_cd
+ AND uipopp.ratio = :ratio
+ ) AS opp_count
+ FROM
+ src05.ult_ident_presc AS uip
+ WHERE
+ uip.presc_cd = :dcf_inst_cd
+ AND uip.ta_cd = :ta_cd
+ AND (SELECT ht.syor_date FROM src05.hdke_tbl AS ht) < uip.end_date
+ """
+ params = {
+ 'dcf_inst_cd': dcf_inst_cd,
+ 'dup_opp_cd': dup_opp_cd,
+ 'ta_cd': ult_ident_presc_row['ta_cd'],
+ 'ult_ident_cd': ult_ident_presc_row['ult_ident_cd'],
+ 'ratio': ult_ident_presc_row['ratio']
+ }
+ ult_ident_presc_records = db.execute_select(sql, params)
+ logging_sql(logger, sql)
+ logger.info('納入先処方元マスタの取得 成功')
+ except Exception as e:
+ logger.debug('納入先処方元マスタの取得 失敗')
+ raise e
+ return ult_ident_presc_records
+
+
+def _count_duplicate_ult_ident_presc(db: Database, set_start_date: str,
+ ult_ident_presc_row: dict) -> int:
+ # ult_ident_prescの重複時相手先コードの件数取得
+ try:
+ sql = """
+ SELECT
+ COUNT(ta_cd) AS cnt
+ FROM
+ src05.ult_ident_presc
+ WHERE
+ ta_cd = :ta_cd
+ AND ult_ident_cd = :ult_ident_cd
+ AND ratio = :ratio
+ AND start_date = :start_date
+ """
+ params = {
+ 'ta_cd': ult_ident_presc_row['ta_cd'],
+ 'ult_ident_cd': ult_ident_presc_row['ult_ident_cd'],
+ 'ratio': ult_ident_presc_row['ratio'],
+ 'start_date': set_start_date
+ }
+ result = db.execute_select(sql, params)
+ logging_sql(logger, sql)
+ logger.info('納入先処方元マスタの重複予定データの存在チェック 成功')
+ except Exception as e:
+ logger.debug('納入先処方元マスタの重複予定データの存在チェック 失敗')
+ raise e
+ return result[0]['cnt']
+
+
+def _get_first_day_of_month(year_month: str) -> datetime:
+ # year_monthの初日の日付を日付型に変換し返却する
+ return datetime.strptime(year_month + '01', '%Y%m%d')
+
+
+def _str_to_date_time(str_date_time: str) -> datetime:
+ # str_date_timeを日付型に変換して返却する
+ return datetime.strptime(str_date_time, '%Y%m%d')
+
+
+def _date_time_to_str(date_time: datetime) -> str:
+ # date_timeをYmd型に変換して返却する
+ return date_time.strftime('%Y%m%d')
diff --git a/ecs/jskult-batch-daily/src/batch/laundering/sales_laundering.py b/ecs/jskult-batch-daily/src/batch/laundering/sales_laundering.py
index f6d682b4..3c086ca5 100644
--- a/ecs/jskult-batch-daily/src/batch/laundering/sales_laundering.py
+++ b/ecs/jskult-batch-daily/src/batch/laundering/sales_laundering.py
@@ -1,5 +1,8 @@
from src.batch.common.batch_context import BatchContext
-from src.batch.laundering import create_inst_merge_for_laundering, emp_chg_inst_laundering, ult_ident_presc_laundering
+from src.batch.laundering import (
+ create_inst_merge_for_laundering, emp_chg_inst_laundering,
+ ult_ident_presc_laundering, sales_results_laundering)
+from src.batch.dcf_inst_merge import integrate_dcf_inst_merge
from src.logging.get_logger import get_logger
batch_context = BatchContext.get_instance()
@@ -16,10 +19,14 @@ def exec():
return
# 洗替用マスタ作成
create_inst_merge_for_laundering.exec()
+ # DCF施設統合マスタ日次更新
+ integrate_dcf_inst_merge.exec()
# 施設担当者洗替
emp_chg_inst_laundering.exec()
# 納入先処方元マスタ洗替
ult_ident_presc_laundering.exec()
+ # 卸販売洗替
+ sales_results_laundering.exec()
# # 並列処理のテスト用コード
# import time
diff --git a/ecs/jskult-batch-daily/src/batch/laundering/sales_results_laundering.py b/ecs/jskult-batch-daily/src/batch/laundering/sales_results_laundering.py
new file mode 100644
index 00000000..7f6d4259
--- /dev/null
+++ b/ecs/jskult-batch-daily/src/batch/laundering/sales_results_laundering.py
@@ -0,0 +1,167 @@
+from src.batch.batch_functions import logging_sql
+from src.db.database import Database
+from src.error.exceptions import BatchOperationException
+from src.logging.get_logger import get_logger
+from src.system_var import environment
+
+logger = get_logger('卸販売洗替')
+
+
+def exec():
+ db = Database.get_instance(autocommit=True)
+ try:
+ db.connect()
+ logger.debug('処理開始')
+ # 卸販売実績テーブル(洗替後)過去5年以前のデータ削除
+ _call_sales_lau_delete(db)
+ # 卸販売実績テーブル(洗替後)作成
+ _call_sales_lau_upsert(db)
+ # 1:卸組織洗替
+ _call_whs_org_laundering(db)
+ # 3:HCO施設コードの洗替
+ _update_sales_lau_from_vop_hco_merge_v(db)
+ # 4:メルク施設コードの洗替
+ _update_mst_inst_laundering(db)
+ logger.debug('処理終了')
+ except Exception as e:
+ raise BatchOperationException(e)
+ finally:
+ db.disconnect()
+
+
+def _call_sales_lau_delete(db: Database):
+ # 卸販売実績テーブル(洗替後)過去5年以前のデータ削除
+ logger.info('sales_lau_delete(プロシージャ―) 開始')
+ db.execute(f"""
+ CALL src05.sales_lau_delete(
+ '{environment.SALES_LAUNDERING_TARGET_TABLE_NAME}',
+ {environment.SALES_LAUNDERING_TARGET_YEAR_OFFSET}
+ )
+ """)
+ logger.info('sales_lau_delete(プロシージャ―) 終了')
+ return
+
+
+def _call_sales_lau_upsert(db: Database):
+ # 卸販売実績テーブル(洗替後)作成
+ logger.info('sales_lau_upsert(プロシージャ―) 開始')
+ db.execute(f"""
+ CALL src05.sales_lau_upsert(
+ '{environment.SALES_LAUNDERING_TARGET_TABLE_NAME}',
+ (src05.get_syor_date() - interval {environment.SALES_LAUNDERING_EXTRACT_DATE_PERIOD} day),
+ src05.get_syor_date()
+ )
+ """)
+ logger.info('sales_lau_upsert(プロシージャ―) 終了')
+ return
+
+
+def _call_whs_org_laundering(db: Database):
+ # 卸組織洗替
+ logger.info('whs_org_laundering(プロシージャ―) 開始')
+ db.execute(f"""
+ CALL src05.whs_org_laundering(
+ '{environment.SALES_LAUNDERING_TARGET_TABLE_NAME}'
+ )
+ """)
+ logger.info('whs_org_laundering(プロシージャ―) 終了')
+ return
+
+
+def _update_sales_lau_from_vop_hco_merge_v(db: Database):
+ # HCO施設コードの洗替
+ if _count_v_inst_merge_t(db) == 0:
+ logger.info('V施設統合マスタ(洗替処理一時テーブル)にデータは存在しません')
+ return
+
+ _call_v_inst_merge_laundering(db)
+ return
+
+
+def _count_v_inst_merge_t(db: Database) -> int:
+ # V施設統合マスタ(洗替処理一時テーブル)のデータ件数の取得
+ try:
+ sql = """
+ SELECT
+ COUNT(v_inst_cd) AS cnt
+ FROM
+ internal05.v_inst_merge_t
+ """
+ result = db.execute_select(sql)
+ logging_sql(logger, sql)
+ logger.info('V施設統合マスタ(洗替処理一時テーブル)のデータ件数の取得 成功')
+ except Exception as e:
+ logger.debug('V施設統合マスタ(洗替処理一時テーブル)のデータ件数の取得 失敗')
+ raise e
+
+ return result[0]['cnt']
+
+
+def _call_v_inst_merge_laundering(db: Database):
+ # HCO施設コードの洗替(プロシージャ―の呼び出し)
+ logger.info('v_inst_merge_laundering(プロシージャ―) 開始')
+ db.execute(f"""
+ CALL src05.v_inst_merge_laundering(
+ '{environment.SALES_LAUNDERING_TARGET_TABLE_NAME}'
+ )
+ """)
+ logger.info('v_inst_merge_laundering(プロシージャ―) 終了')
+ return
+
+
+def _update_mst_inst_laundering(db: Database):
+ # メルク施設コードの洗替
+ _call_hco_to_mdb_laundering(db)
+ _update_sales_lau_from_dcf_inst_merge(db)
+
+
+def _call_hco_to_mdb_laundering(db: Database):
+ # A:医療機関のデータはMDB変換表からHCO⇒DCFへ変換
+ logger.info('hco_to_mdb_laundering(プロシージャ―) 開始')
+ db.execute(f"""
+ CALL src05.hco_to_mdb_laundering(
+ '{environment.SALES_LAUNDERING_TARGET_TABLE_NAME}'
+ )
+ """)
+ logger.info('hco_to_mdb_laundering(プロシージャ―) 終了')
+ return
+
+
+def _update_sales_lau_from_dcf_inst_merge(db: Database):
+ # B:DCF施設統合マスタがある場合は、コードを変換し、住所等をSETする
+ if _count_inst_merge_t(db) == 0:
+ logger.info('アルトマーク施設統合マスタ(洗替処理一時テーブル)にデータは存在しません')
+ return
+ _call_inst_merge_laundering(db)
+ return
+
+
+def _count_inst_merge_t(db: Database) -> int:
+ # アルトマーク施設統合マスタ(洗替処理一時テーブル)のデータ件数の取得
+ try:
+ sql = """
+ SELECT
+ COUNT(dcf_dsf_inst_cd) AS cnt
+ FROM
+ internal05.inst_merge_t
+ """
+ result = db.execute_select(sql)
+ logging_sql(logger, sql)
+ logger.info('アルトマーク施設統合マスタ(洗替処理一時テーブル)のデータ件数の取得 成功')
+ except Exception as e:
+ logger.debug('アルトマーク施設統合マスタ(洗替処理一時テーブル)のデータ件数の取得 失敗')
+ raise e
+
+ return result[0]['cnt']
+
+
+def _call_inst_merge_laundering(db: Database):
+ # B:DCF施設統合マスタがある場合は、コードを変換し、住所等をSETする(プロシージャ―の呼び出し)
+ logger.info('inst_merge_laundering(プロシージャ―) 開始')
+ db.execute(f"""
+ CALL src05.inst_merge_laundering(
+ '{environment.SALES_LAUNDERING_TARGET_TABLE_NAME}'
+ )
+ """)
+ logger.info('inst_merge_laundering(プロシージャ―) 終了')
+ return
diff --git a/ecs/jskult-batch-daily/src/db/database.py b/ecs/jskult-batch-daily/src/db/database.py
index f67a21b9..b9a745be 100644
--- a/ecs/jskult-batch-daily/src/db/database.py
+++ b/ecs/jskult-batch-daily/src/db/database.py
@@ -13,15 +13,17 @@ logger = get_logger(__name__)
class Database:
"""データベース操作クラス"""
__connection: Connection = None
- __engine: Engine = None
+ __transactional_engine: Engine = None
+ __autocommit_engine: Engine = None
__host: str = None
__port: str = None
__username: str = None
__password: str = None
__schema: str = None
+ __autocommit: bool = None
__connection_string: str = None
- def __init__(self, username: str, password: str, host: str, port: int, schema: str) -> None:
+ def __init__(self, username: str, password: str, host: str, port: int, schema: str, autocommit: bool = False) -> None:
"""このクラスの新たなインスタンスを初期化します
Args:
@@ -30,12 +32,14 @@ class Database:
host (str): DBホスト名
port (int): DBポート
schema (str): DBスキーマ名
+ autocommit(bool): 自動コミットモードで接続するかどうか(Trueの場合、トランザクションの有無に限らず即座にコミットされる). Defaults to False.
"""
self.__username = username
self.__password = password
self.__host = host
self.__port = int(port)
self.__schema = schema
+ self.__autocommit = autocommit
self.__connection_string = URL.create(
drivername='mysql+pymysql',
@@ -47,16 +51,20 @@ class Database:
query={"charset": "utf8mb4"}
)
- self.__engine = create_engine(
+ self.__transactional_engine = create_engine(
self.__connection_string,
pool_timeout=5,
poolclass=QueuePool
)
+ self.__autocommit_engine = self.__transactional_engine.execution_options(isolation_level='AUTOCOMMIT')
+
@classmethod
- def get_instance(cls):
+ def get_instance(cls, autocommit=False):
"""インスタンスを取得します
+ Args:
+ autocommit (bool, optional): 自動コミットモードで接続するかどうか(Trueの場合、トランザクションの有無に限らず即座にコミットされる). Defaults to False.
Returns:
Database: DB操作クラスインスタンス
"""
@@ -65,7 +73,8 @@ class Database:
password=environment.DB_PASSWORD,
host=environment.DB_HOST,
port=environment.DB_PORT,
- schema=environment.DB_SCHEMA
+ schema=environment.DB_SCHEMA,
+ autocommit=autocommit
)
@retry(
@@ -77,12 +86,15 @@ class Database:
stop=stop_after_attempt(environment.DB_CONNECTION_MAX_RETRY_ATTEMPT))
def connect(self):
"""
- DBに接続します。接続に失敗した場合、リトライします。
+ DBに接続します。接続に失敗した場合、リトライします。\n
+ インスタンスのautocommitがTrueの場合、自動コミットモードで接続する。(明示的なトランザクションも無視される)
Raises:
DBException: 接続失敗
"""
try:
- self.__connection = self.__engine.connect()
+ self.__connection = (
+ self.__autocommit_engine.connect() if self.__autocommit is True
+ else self.__transactional_engine.connect())
except Exception as e:
raise DBException(e)
diff --git a/ecs/jskult-batch-daily/src/system_var/environment.py b/ecs/jskult-batch-daily/src/system_var/environment.py
index b1730224..a51ab519 100644
--- a/ecs/jskult-batch-daily/src/system_var/environment.py
+++ b/ecs/jskult-batch-daily/src/system_var/environment.py
@@ -22,3 +22,10 @@ DB_CONNECTION_MAX_RETRY_ATTEMPT = int(os.environ.get('DB_CONNECTION_MAX_RETRY_AT
DB_CONNECTION_RETRY_INTERVAL_INIT = int(os.environ.get('DB_CONNECTION_RETRY_INTERVAL', 5))
DB_CONNECTION_RETRY_INTERVAL_MIN_SECONDS = int(os.environ.get('DB_CONNECTION_RETRY_MIN_SECONDS', 5))
DB_CONNECTION_RETRY_INTERVAL_MAX_SECONDS = int(os.environ.get('DB_CONNECTION_RETRY_MAX_SECONDS', 50))
+
+# 連携データ抽出期間
+SALES_LAUNDERING_EXTRACT_DATE_PERIOD = int(os.environ['SALES_LAUNDERING_EXTRACT_DATE_PERIOD'])
+# 洗替対象テーブル名
+SALES_LAUNDERING_TARGET_TABLE_NAME = os.environ['SALES_LAUNDERING_TARGET_TABLE_NAME']
+# 卸実績洗替で作成するデータの期間(年単位)
+SALES_LAUNDERING_TARGET_YEAR_OFFSET = os.environ['SALES_LAUNDERING_TARGET_YEAR_OFFSET']
diff --git a/ecs/jskult-webapp/src/controller/login.py b/ecs/jskult-webapp/src/controller/login.py
index 412ba068..5659c993 100644
--- a/ecs/jskult-webapp/src/controller/login.py
+++ b/ecs/jskult-webapp/src/controller/login.py
@@ -69,16 +69,20 @@ def login(
try:
jwt_token = login_service.login(request.username, request.password)
except NotAuthorizeException as e:
- logger.exception(e)
+ logger.info(f'ログイン失敗:{e}')
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=constants.LOGOUT_REASON_LOGIN_ERROR)
except JWTTokenVerifyException as e:
- logger.exception(e)
+ logger.info(f'ログイン失敗:{e}')
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
verified_token = jwt_token.verify_token()
# 普通の認証だと、`cognito:username`に入る。
user_id = verified_token.user_id
user_record = login_service.logged_in_user(user_id)
+ # ユーザーがマスタに存在しない場合、ログアウトにリダイレクトする
+ if user_record is None:
+ logger.info(f'存在しないユーザー: {user_id}, ユーザーID: {user_id}')
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=constants.LOGOUT_REASON_LOGIN_ERROR)
# ユーザーが有効ではない場合、ログアウトにリダイレクトする
if not user_record.is_enable_user():
logger.info(f'無効なユーザー: {user_id}, 有効フラグ: {user_record.enabled_flg}')
@@ -126,12 +130,17 @@ def sso_authorize(
# トークン検証
verified_token = jwt_token.verify_token()
except JWTTokenVerifyException as e:
- logger.exception(e)
+ logger.exception(f'SSOログイン失敗:{e}')
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
# トークンからユーザーIDを取得
user_id = verified_token.user_id
user_record = login_service.logged_in_user(user_id)
+
+ # ユーザーがマスタに存在しない場合、ログアウトにリダイレクトする
+ if user_record is None:
+ logger.info(f'存在しないユーザー: {user_id}, ユーザーID: {user_id}')
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=constants.LOGOUT_REASON_LOGIN_ERROR)
# ユーザーが有効ではない場合、ログアウトにリダイレクトする
if not user_record.is_enable_user():
logger.info(f'無効なユーザー: {user_id}, 有効フラグ: {user_record.enabled_flg}')
diff --git a/ecs/jskult-webapp/src/controller/master_mainte.py b/ecs/jskult-webapp/src/controller/master_mainte.py
new file mode 100644
index 00000000..ea972ad6
--- /dev/null
+++ b/ecs/jskult-webapp/src/controller/master_mainte.py
@@ -0,0 +1,185 @@
+from fastapi import APIRouter, Depends, HTTPException, Request
+from fastapi.responses import HTMLResponse
+from starlette import status
+
+from src.depends.services import get_service
+from src.model.internal.session import UserSession
+from src.model.view.inst_emp_csv_download_view_model import \
+ InstEmpCsvDownloadViewModel
+from src.model.view.inst_emp_csv_upload_view_model import \
+ InstEmpCsvUploadViewModel
+from src.model.view.master_mainte_menu_view_model import \
+ MasterMainteMenuViewModel
+from src.model.view.table_override_view_model import TableOverrideViewModel
+from src.router.session_router import AuthenticatedRoute
+from src.services.batch_status_service import BatchStatusService
+from src.services.session_service import set_session
+from src.system_var import constants
+from src.templates import templates
+
+router = APIRouter()
+router.route_class = AuthenticatedRoute
+
+#########################
+# Views #
+#########################
+
+
+@router.get('/masterMainteMenu', response_class=HTMLResponse)
+def menu_view(
+ request: Request,
+ batch_status_service: BatchStatusService = Depends(get_service(BatchStatusService))
+):
+ session: UserSession = request.session
+
+ # マスタメンテメニューへのアクセス権がない場合、ログアウトさせる
+ if session.master_mainte_flg != '1':
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
+
+ # バッチ処理中の場合、ログアウトさせる
+ if batch_status_service.is_batch_processing():
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN,
+ detail=constants.LOGOUT_REASON_BATCH_PROCESSING_FOR_MAINTE)
+ # dump処理中の場合、ログアウトさせる
+ if batch_status_service.is_dump_processing():
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=constants.LOGOUT_REASON_BACKUP_PROCESSING)
+
+ # 画面表示用のモデル
+ menu = MasterMainteMenuViewModel()
+ # セッション書き換え
+ session.update(
+ actions=[
+ UserSession.last_access_time.set(UserSession.new_last_access_time()),
+ UserSession.record_expiration_time.set(UserSession.new_record_expiration_time()),
+ ]
+ )
+ set_session(session)
+ templates_response = templates.TemplateResponse(
+ 'masterMainteMenu.html',
+ {
+ 'request': request,
+ 'menu': menu
+ },
+ headers={'session_key': session.session_key}
+ )
+ return templates_response
+
+
+@router.get('/instEmpCsvUL', response_class=HTMLResponse)
+def inst_emp_csv_upload_view(
+ request: Request,
+ batch_status_service: BatchStatusService = Depends(get_service(BatchStatusService))
+):
+ session: UserSession = request.session
+
+ # マスタメンテメニューへのアクセス権がない場合、ログアウトさせる
+ if session.master_mainte_flg != '1':
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
+
+ # バッチ処理中の場合、ログアウトさせる
+ if batch_status_service.is_batch_processing():
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN,
+ detail=constants.LOGOUT_REASON_BATCH_PROCESSING_FOR_MAINTE)
+ # dump処理中の場合、ログアウトさせる
+ if batch_status_service.is_dump_processing():
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=constants.LOGOUT_REASON_BACKUP_PROCESSING)
+
+ # 画面表示用のモデル
+ view_model = InstEmpCsvUploadViewModel()
+ # セッション書き換え
+ session.update(
+ actions=[
+ UserSession.last_access_time.set(UserSession.new_last_access_time()),
+ UserSession.record_expiration_time.set(UserSession.new_record_expiration_time()),
+ ]
+ )
+ set_session(session)
+ templates_response = templates.TemplateResponse(
+ 'instEmpCsvUL.html',
+ {
+ 'request': request,
+ 'view': view_model
+ },
+ headers={'session_key': session.session_key}
+ )
+ return templates_response
+
+
+@router.get('/instEmpCsvDL', response_class=HTMLResponse)
+def inst_emp_csv_download_view(
+ request: Request,
+ batch_status_service: BatchStatusService = Depends(get_service(BatchStatusService))
+):
+ session: UserSession = request.session
+
+ # マスタメンテメニューへのアクセス権がない場合、ログアウトさせる
+ if session.master_mainte_flg != '1':
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
+
+ # バッチ処理中の場合、ログアウトさせる
+ if batch_status_service.is_batch_processing():
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN,
+ detail=constants.LOGOUT_REASON_BATCH_PROCESSING_FOR_MAINTE)
+ # dump処理中の場合、ログアウトさせる
+ if batch_status_service.is_dump_processing():
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=constants.LOGOUT_REASON_BACKUP_PROCESSING)
+
+ # 画面表示用のモデル
+ view_model = InstEmpCsvDownloadViewModel()
+ # セッション書き換え
+ session.update(
+ actions=[
+ UserSession.last_access_time.set(UserSession.new_last_access_time()),
+ UserSession.record_expiration_time.set(UserSession.new_record_expiration_time()),
+ ]
+ )
+ set_session(session)
+ templates_response = templates.TemplateResponse(
+ 'instEmpCsvDL.html',
+ {
+ 'request': request,
+ 'view': view_model
+ },
+ headers={'session_key': session.session_key}
+ )
+ return templates_response
+
+
+@router.get('/tableOverride', response_class=HTMLResponse)
+def table_override_view(
+ request: Request,
+ batch_status_service: BatchStatusService = Depends(get_service(BatchStatusService))
+):
+ session: UserSession = request.session
+
+ # マスタメンテメニューへのアクセス権がない場合、ログアウトさせる
+ if session.master_mainte_flg != '1':
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
+
+ # バッチ処理中の場合、ログアウトさせる
+ if batch_status_service.is_batch_processing():
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN,
+ detail=constants.LOGOUT_REASON_BATCH_PROCESSING_FOR_MAINTE)
+ # dump処理中の場合、ログアウトさせる
+ if batch_status_service.is_dump_processing():
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=constants.LOGOUT_REASON_BACKUP_PROCESSING)
+
+ # 画面表示用のモデル
+ view_model = TableOverrideViewModel()
+ # セッション書き換え
+ session.update(
+ actions=[
+ UserSession.last_access_time.set(UserSession.new_last_access_time()),
+ UserSession.record_expiration_time.set(UserSession.new_record_expiration_time()),
+ ]
+ )
+ set_session(session)
+ templates_response = templates.TemplateResponse(
+ 'tableOverride.html',
+ {
+ 'request': request,
+ 'view': view_model
+ },
+ headers={'session_key': session.session_key}
+ )
+ return templates_response
diff --git a/ecs/jskult-webapp/src/controller/menu.py b/ecs/jskult-webapp/src/controller/menu.py
index 61a1a3a0..96826fce 100644
--- a/ecs/jskult-webapp/src/controller/menu.py
+++ b/ecs/jskult-webapp/src/controller/menu.py
@@ -32,6 +32,7 @@ def menu_view(
hdke_tbl_record = batch_status_service.hdke_table_record
batch_status = hdke_tbl_record.bch_actf
+ dump_status = hdke_tbl_record.dump_sts_kbn
user = UserViewModel(
doc_flg=session.doc_flg,
inst_flg=session.inst_flg,
@@ -40,6 +41,7 @@ def menu_view(
)
menu = MenuViewModel(
batch_status=batch_status,
+ dump_status=dump_status,
user_model=user
)
# セッション書き換え
diff --git a/ecs/jskult-webapp/src/main.py b/ecs/jskult-webapp/src/main.py
index 2aca351c..8717c7cb 100644
--- a/ecs/jskult-webapp/src/main.py
+++ b/ecs/jskult-webapp/src/main.py
@@ -6,7 +6,7 @@ from starlette import status
import src.static as static
from src.controller import (bio, bio_download, healthcheck, login, logout,
- menu, root, ultmarc)
+ master_mainte, menu, root, ultmarc)
from src.controller.sample_send_file import router as sample_router
from src.core import tasks
from src.error.exception_handler import http_exception_handler
@@ -31,6 +31,8 @@ app.include_router(ultmarc.router, prefix='/ultmarc')
# 生物由来のダウンロード用APIルーター。
# クライアントから非同期呼出しされるため、共通ルーターとは異なる扱いとする。
app.include_router(bio_download.router, prefix='/bio')
+# マスタメンテ
+app.include_router(master_mainte.router, prefix='/masterMainte')
# ヘルスチェック用のルーター
app.include_router(healthcheck.router, prefix='/healthcheck')
diff --git a/ecs/jskult-webapp/src/model/db/hdke_tbl.py b/ecs/jskult-webapp/src/model/db/hdke_tbl.py
index 944581d5..9655c6c1 100644
--- a/ecs/jskult-webapp/src/model/db/hdke_tbl.py
+++ b/ecs/jskult-webapp/src/model/db/hdke_tbl.py
@@ -5,3 +5,4 @@ from src.model.db.base_db_model import BaseDBModel
class HdkeTblModel(BaseDBModel):
bch_actf: Optional[str]
+ dump_sts_kbn: Optional[str]
diff --git a/ecs/jskult-webapp/src/model/view/inst_emp_csv_download_view_model.py b/ecs/jskult-webapp/src/model/view/inst_emp_csv_download_view_model.py
new file mode 100644
index 00000000..220294ba
--- /dev/null
+++ b/ecs/jskult-webapp/src/model/view/inst_emp_csv_download_view_model.py
@@ -0,0 +1,5 @@
+from pydantic import BaseModel
+
+
+class InstEmpCsvDownloadViewModel(BaseModel):
+ subtitle: str = '施設担当者データCSVダウンロード'
diff --git a/ecs/jskult-webapp/src/model/view/inst_emp_csv_upload_view_model.py b/ecs/jskult-webapp/src/model/view/inst_emp_csv_upload_view_model.py
new file mode 100644
index 00000000..64bde407
--- /dev/null
+++ b/ecs/jskult-webapp/src/model/view/inst_emp_csv_upload_view_model.py
@@ -0,0 +1,5 @@
+from pydantic import BaseModel
+
+
+class InstEmpCsvUploadViewModel(BaseModel):
+ subtitle: str = '施設担当者データCSVアップロード'
diff --git a/ecs/jskult-webapp/src/model/view/master_mainte_menu_view_model.py b/ecs/jskult-webapp/src/model/view/master_mainte_menu_view_model.py
new file mode 100644
index 00000000..2b1629b1
--- /dev/null
+++ b/ecs/jskult-webapp/src/model/view/master_mainte_menu_view_model.py
@@ -0,0 +1,5 @@
+from pydantic import BaseModel
+
+
+class MasterMainteMenuViewModel(BaseModel):
+ subtitle: str = 'MeDaCA マスターメンテメニュー'
diff --git a/ecs/jskult-webapp/src/model/view/menu_view_model.py b/ecs/jskult-webapp/src/model/view/menu_view_model.py
index 647bdec9..7a7970d2 100644
--- a/ecs/jskult-webapp/src/model/view/menu_view_model.py
+++ b/ecs/jskult-webapp/src/model/view/menu_view_model.py
@@ -3,15 +3,20 @@ from typing import Optional
from pydantic import BaseModel
from src.model.view.user_view_model import UserViewModel
+from src.system_var import constants
class MenuViewModel(BaseModel):
subtitle: str = 'MeDaCA 機能メニュー'
batch_status: Optional[str]
+ dump_status: Optional[str]
user_model: UserViewModel
def is_batch_processing(self):
- return self.batch_status == '1'
+ return self.batch_status == constants.BATCH_STATUS_PROCESSING
+
+ def is_backup_processing(self):
+ return self.dump_status != constants.DUMP_STATUS_UNPROCESSED
def is_available_ult_doctor_menu(self):
return self.user_model.has_ult_doctor_permission()
diff --git a/ecs/jskult-webapp/src/model/view/table_override_view_model.py b/ecs/jskult-webapp/src/model/view/table_override_view_model.py
new file mode 100644
index 00000000..e03b1fd0
--- /dev/null
+++ b/ecs/jskult-webapp/src/model/view/table_override_view_model.py
@@ -0,0 +1,5 @@
+from pydantic import BaseModel
+
+
+class TableOverrideViewModel(BaseModel):
+ subtitle: str = 'テーブル上書きコピー'
diff --git a/ecs/jskult-webapp/src/model/view/user_view_model.py b/ecs/jskult-webapp/src/model/view/user_view_model.py
index 55f1528a..5b523b4c 100644
--- a/ecs/jskult-webapp/src/model/view/user_view_model.py
+++ b/ecs/jskult-webapp/src/model/view/user_view_model.py
@@ -4,10 +4,10 @@ from pydantic import BaseModel
class UserViewModel(BaseModel):
- bio_flg: str # AUTH_FLG1
- doc_flg: str # AUTH_FLG2
- inst_flg: str # AUTH_FLG3
- master_mainte_flg: str # AUTH_FLG4
+ bio_flg: Optional[str] # AUTH_FLG1
+ doc_flg: Optional[str] # AUTH_FLG2
+ inst_flg: Optional[str] # AUTH_FLG3
+ master_mainte_flg: Optional[str] # AUTH_FLG4
user_flg: Optional[str] # MNTUSER_FLG
def has_ult_doctor_permission(self):
diff --git a/ecs/jskult-webapp/src/repositories/hdke_tbl_repository.py b/ecs/jskult-webapp/src/repositories/hdke_tbl_repository.py
index 46f5bfcc..967fbbe1 100644
--- a/ecs/jskult-webapp/src/repositories/hdke_tbl_repository.py
+++ b/ecs/jskult-webapp/src/repositories/hdke_tbl_repository.py
@@ -6,7 +6,7 @@ logger = get_logger('日付テーブル取得')
class HdkeTblRepository(BaseRepository):
- FETCH_SQL = "SELECT bch_actf FROM src05.hdke_tbl"
+ FETCH_SQL = "SELECT bch_actf, dump_sts_kbn FROM src05.hdke_tbl"
def fetch_all(self) -> list[HdkeTblModel]:
try:
diff --git a/ecs/jskult-webapp/src/services/batch_status_service.py b/ecs/jskult-webapp/src/services/batch_status_service.py
index c9f6c6a7..ffb57af0 100644
--- a/ecs/jskult-webapp/src/services/batch_status_service.py
+++ b/ecs/jskult-webapp/src/services/batch_status_service.py
@@ -4,6 +4,7 @@ from src.model.db.hdke_tbl import HdkeTblModel
from src.repositories.base_repository import BaseRepository
from src.repositories.hdke_tbl_repository import HdkeTblRepository
from src.services.base_service import BaseService
+from src.system_var import constants
class BatchStatusService(BaseService):
@@ -25,17 +26,30 @@ class BatchStatusService(BaseService):
@property
def hdke_table_record(self) -> HdkeTblModel:
+ """日付テーブルを取得する"""
+
# 日付マスタのレコードがあることを確認
self.__assert_record_exists()
# 日付テーブルのレコードは必ず1件
return self.__hdke_table_record[0]
def is_batch_processing(self):
+ """バッチ処理中かどうかを判定する"""
+
# 日付マスタのレコードがあることを確認
self.__assert_record_exists()
- return self.hdke_table_record.bch_actf == '1' # TODO: 定数化する
+ return self.hdke_table_record.bch_actf == constants.BATCH_STATUS_PROCESSING
+
+ def is_dump_processing(self):
+ """dump処理処理中かどうかを判定する"""
+
+ # 日付マスタのレコードがあることを確認
+ self.__assert_record_exists()
+ return self.hdke_table_record.dump_sts_kbn != constants.DUMP_STATUS_UNPROCESSED
def __assert_record_exists(self):
+ """日付テーブルが有ることを保証する"""
+
# 日付マスタのレコードがない場合は例外とする
if len(self.__hdke_table_record) == 0:
raise DBException('日付テーブルのレコードが存在しません')
diff --git a/ecs/jskult-webapp/src/static/css/masterMainte.css b/ecs/jskult-webapp/src/static/css/masterMainte.css
new file mode 100644
index 00000000..a59c1681
--- /dev/null
+++ b/ecs/jskult-webapp/src/static/css/masterMainte.css
@@ -0,0 +1,164 @@
+body{
+ background-color: LightCyan;
+ font-family : "ヒラギノ角ゴ Pro W3", "Hiragino Kaku Gothic Pro", "メイリオ", Meiryo, Osaka, "MS Pゴシック", "MS PGothic", sans-serif;
+}
+
+h1{
+ margin-left : 1%;
+}
+
+
+/*ヘッダー*/
+.headerTable{
+ width: 100%;
+}
+
+.headerTdLeft{
+ width: 80%;
+}
+
+.headerTdRight{
+ text-align: right;
+ padding-right: 2%;
+ width: 20%;
+}
+
+.buttonSize{
+ width: 85px;
+}
+
+/*////////////////////////*/
+/*施設担当者データCSVダウンロード*/
+/*////////////////////////*/
+.searchColumnTd{
+ width: 14%;
+}
+
+.searchTextboxTd{
+ width: 18%;
+}
+
+.searchTable{
+ margin-left: 3%;
+ margin-right: 3%;
+ margin-bottom: 1%;
+ padding-bottom: 1%;
+ border-bottom: solid 1px gray;
+ width: 94%;
+}
+
+.searchLabelTd{
+ text-align: right;
+ width: 10%;
+
+}
+
+.searchInputTd{
+ width: 19%;
+}
+
+.searchTextbox{
+ width: 90%;
+ margin-left: 2.5%;
+ margin-right: 2.5%;
+ margin-top: 0.8%;
+ margin-bottom: 0.8%;
+}
+
+.searchDateTextbox{
+ width: 37%;
+ margin-left: 2.5%;
+ margin-right: 2.5%;
+ margin-top: 0.8%;
+ margin-bottom: 0.8%;
+}
+
+.searchButtonTd{
+ text-align: right;
+ padding-top: 1%;
+}
+
+
+.csvOutputMessage{
+ margin-left: 3%;
+}
+
+.errorColor{
+ color: red;
+}
+
+/*//////////////////////////*/
+/*施設担当者データExcelアップロード*/
+/*//////////////////////////*/
+.inputTable{
+ margin-left: 3%;
+ margin-right: 3%;
+ margin-bottom: 1%;
+ padding-bottom: 1%;
+ border-bottom: solid 1px gray;
+ width: 94%;
+}
+
+.inputLabelTd{
+ width: 10%;
+}
+
+.inputTd{
+ width:20%;
+}
+
+.inputButtonTd{
+ width: 50%;
+ text-align: right;
+}
+
+.dataCntDisp{
+ text-align: right;
+ margin-right: 3%;
+}
+
+table.inputData {
+ font-family:arial;
+ background-color: #CDCDCD;
+ font-size: 12pt;
+ text-align: left;
+ white-space: nowrap;
+ border: 0.1px solid silver;
+ padding: 4px;
+ padding-right: 20px;
+ border-collapse: collapse;
+ margin-left: 3%;
+ width: 94%;
+}
+table.inputData tbody th {
+ color: #3D3D3D;
+ padding: 4px;
+ background-color: #e6EEEE;
+ border: 0.1px solid silver;
+ vertical-align: top;
+}
+
+table.inputData tbody td {
+ color: #3D3D3D;
+ padding: 4px;
+ background-color: #FFF;
+ border: 0.1px solid silver;
+ vertical-align: top;
+}
+
+.footerMsg{
+ margin-left: 3%;
+}
+
+
+/*//////////////////////////*/
+/*データ上書きコピー */
+/*//////////////////////////*/
+.tableOverRide{
+ margin-right: 3%;
+ margin-left: 3%;
+ margin-bottom: 2%;
+ border-bottom: solid 1px gray;
+ width: 94%;
+}
+
diff --git a/ecs/jskult-webapp/src/static/css/menuStyle.css b/ecs/jskult-webapp/src/static/css/menuStyle.css
index b1920070..3a07d9fc 100644
--- a/ecs/jskult-webapp/src/static/css/menuStyle.css
+++ b/ecs/jskult-webapp/src/static/css/menuStyle.css
@@ -37,7 +37,7 @@ body{
font-size: 160%;
}
-.notUseBioMsg{
+.notUseBioMsg,.notUseMainteMsg{
font-size: 143%;
color: red;
}
diff --git a/ecs/jskult-webapp/src/system_var/constants.py b/ecs/jskult-webapp/src/system_var/constants.py
index 2ea9454f..064d135b 100644
--- a/ecs/jskult-webapp/src/system_var/constants.py
+++ b/ecs/jskult-webapp/src/system_var/constants.py
@@ -1,5 +1,10 @@
import os.path as path
+# 日付テーブル.バッチ処理ステータス:未処理
+BATCH_STATUS_PROCESSING = '1'
+# 日付テーブル.dump取得状態区分:未処理
+DUMP_STATUS_UNPROCESSED = '0'
+
BIO_TEMPORARY_FILE_DIR_PATH = path.join(path.curdir, 'src', 'data')
BIO_EXCEL_TEMPLATE_FILE_PATH = path.join(BIO_TEMPORARY_FILE_DIR_PATH, 'BioData_template.xlsx')
@@ -112,6 +117,7 @@ LOGOUT_REASON_DO_LOGOUT = 'do_logout'
LOGOUT_REASON_LOGIN_ERROR = 'login_error'
LOGOUT_REASON_BATCH_PROCESSING = 'batch_processing'
LOGOUT_REASON_BATCH_PROCESSING_FOR_MAINTE = 'batch_processing_ult'
+LOGOUT_REASON_BACKUP_PROCESSING = 'dump_processing'
LOGOUT_REASON_NOT_LOGIN = 'not_login'
LOGOUT_REASON_DB_ERROR = 'db_error'
LOGOUT_REASON_UNEXPECTED = 'unexpected'
@@ -121,6 +127,7 @@ LOGOUT_REASON_MESSAGE_MAP = {
LOGOUT_REASON_LOGIN_ERROR: '存在しないユーザー、
またはパスワードが違います。',
LOGOUT_REASON_BATCH_PROCESSING: '日次バッチ処理中なので、
生物由来データ参照は使用出来ません。',
LOGOUT_REASON_BATCH_PROCESSING_FOR_MAINTE: '日次バッチ処理中のため、
マスタ-メンテは使用出来ません。',
+ LOGOUT_REASON_BACKUP_PROCESSING: 'バックアップ取得を開始しました。
日次バッチ更新が終了するまでマスターメンテは使用できません',
LOGOUT_REASON_NOT_LOGIN: 'Loginしてからページにアクセスしてください。',
LOGOUT_REASON_DB_ERROR: 'DB接続に失敗しました。
再度Loginするか、
管理者にお問い合わせください。',
LOGOUT_REASON_UNEXPECTED: '予期しないエラーが発生しました。
再度Loginするか、
管理者に問い合わせてください。'
diff --git a/ecs/jskult-webapp/src/templates/instEmpCsvDL.html b/ecs/jskult-webapp/src/templates/instEmpCsvDL.html
new file mode 100644
index 00000000..7e84fd4c
--- /dev/null
+++ b/ecs/jskult-webapp/src/templates/instEmpCsvDL.html
@@ -0,0 +1,12 @@
+
+
+