common.py 9.75 KB
import sys
import os
import logging
import csv
from datetime import (
    date,
    datetime,
    )
from argparse import ArgumentParser
from configparser import ConfigParser
from ISO8583.ISO8583 import BitNotSet
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
import transaction
from zope.sqlalchemy import register
from opensipkd.waktu import dmyhms
from opensipkd.views.models import Conf as BaseConf
from opensipkd.iso8583.bjb.scripts.common import get_module_object
from ..models import Base


log_format = '%(asctime)s %(levelname)s %(message)s'
formatter = logging.Formatter(log_format)

BIT_18_NAMES = {
    '6010': 'TELLER',
    '6011': 'ATM',
    '6012': 'POS',
    '6013': 'PHONE BANKING',
    '6014': 'INTERNETBANKING',
    '6015': 'KIOSK',
    '6016': 'AUTODEBET',
    '6017': 'MOBILBANKING'}

# Bit 41, 42, dan 43
BIT_PROFILE_NAMES = {
    'INDOMARET': 'INDOMARET',
    'ALFAMART': 'ALFAMART',
    'LINKAJA': 'LINKAJA',
    'GOPAY': 'GOPAY',
    'EMONEY': 'EMONEY',
    'PTPOS': 'PTPOS',
    'TOKOPEDI': 'TOKOPEDIA',
    'BUKALAPA': 'BUKALAPAK',
    'MASAGO': 'MASAGO',
    'BAYARIN': 'BAYARIN'}

my_registry = dict()


def get_file(filename):
    base_dir = os.path.split(__file__)[0]
    fullpath = os.path.join(base_dir, 'data', filename)
    return open(fullpath)


def append_csv(table, filename, keys):
    DBSession = my_registry['db_session']
    with get_file(filename) as f:
        reader = csv.DictReader(f)
        filter_ = dict()
        for cf in reader:
            for key in keys:
                filter_[key] = cf[key]
            q = DBSession.query(table).filter_by(**filter_)
            found = q.first()
            if found:
                continue
            row = table()
            for fieldname in cf:
                val = cf[fieldname]
                if not val:
                    continue
                setattr(row, fieldname, val)
            DBSession.add(row)


def clean_raw(raw):
    if raw[:2] == '\\x':
        raw = raw[2:]
    if raw[:4] == '3032':
        return bytes.fromhex(raw)
    return raw.encode('utf8')


def get_iso(raw, iso_class, debug=False):
    raw = clean_raw(raw)
    iso = iso_class(debug=debug)
    iso.setIsoContent(raw)
    return iso


def get_channel_name(bit_018, bit_041, bit_042, bit_043):
    bit_018 = bit_018.strip()
    if bit_018 == '6025':
        bit_041 = bit_041.strip()
        bit_042 = bit_042.strip()
        bit_043 = bit_043.strip()
        profile_values = [bit_041, bit_042, bit_043]
        for name in BIT_PROFILE_NAMES:
            for bit_value in profile_values:
                if bit_value.find(name) > -1:
                    return BIT_PROFILE_NAMES[name]
    if bit_018 in BIT_18_NAMES:
        return BIT_18_NAMES[bit_018]
    return 'LAINNYA'


def get_channel_info_by_iso(iso):
    d = dict()
    lengkap = True
    for bit in (18, 32, 41, 42, 43):
        bit_name = f'bit_{bit}'
        try:
            d[bit_name] = iso.getBit(bit)
        except BitNotSet:
            lengkap = False
            continue
    if lengkap:
        d['channel'] = get_channel_name(
                iso.getBit(18), iso.getBit(41), iso.getBit(42), iso.getBit(43))
    else:
        d['channel'] = 'LAINNYA'
    return d


def get_channel_name_by_row(row):
    return get_channel_name(row.bit_018, row.bit_041, row.bit_042, row.bit_043)


def get_keys(iso):
    d = get_channel_info_by_iso(iso)
    d.update(dict(
        nomor_bayar=iso.get_invoice_id().strip(),
        stan=iso.get_stan().strip(),
        ntb=iso.get_ntb().strip()))
    return d


def create_log(log_file):
    file_handler = logging.FileHandler(log_file)
    file_handler.setFormatter(formatter)
    console_handler = logging.StreamHandler(sys.stdout)
    console_handler.setFormatter(formatter)
    log = logging.getLogger(sys.argv[0])
    log.setLevel(logging.DEBUG)
    log.addHandler(file_handler)
    log.addHandler(console_handler)
    return log


def get_parser():
    pars = ArgumentParser()
    pars.add_argument('conf')
    pars.add_argument('--update-from-id', type=int)
    pars.add_argument('--update-from-date')
    pars.add_argument('--debug', action='store_true')
    pars.add_argument('--debug-sql', action='store_true')
    return pars


def get_option(argv):
    pars = get_parser()
    return pars.parse_args(argv)


class Conf(BaseConf):
    def as_datetime(self):
        return datetime.strptime(self.nilai, '%d-%m-%Y %H:%M:%S')


def is_live(pid):
    try:
        os.kill(pid, 0)
    except OSError:
        return
    return True


def read_pid_file(filename):
    try:
        f = open(filename)
        s = f.read()
        f.close()
        s = s.split()
        s = s[0]
        return int(s)
    except IOError:
        return
    except ValueError:
        return
    except IndexError:
        return


def write_pid_file(filename):
    pid = os.getpid()
    f = open(filename, 'w')
    f.write(str(pid))
    f.close()
    return pid


def make_pid_file(filename):
    pid = read_pid_file(filename)
    if pid and is_live(pid):
        print(f'PID saya {pid} masih aktif.')
        return
    return write_pid_file(filename)


class BaseApp:
    report_orm = None  # Override, please

    def __init__(self, argv):
        self.option = get_option(argv)
        cp = ConfigParser()
        cp.read(self.option.conf)
        self.conf = cp['main']
        self.pid = make_pid_file(self.conf['pid_file'])
        if not self.pid:
            return
        self.log = create_log(self.conf['log_file'])
        module_name = self.conf['module']
        self.module = get_module_object(module_name)
        module_conf = dict(self.conf)
        self.module.init(module_conf)
        self.prod_session = self.get_db_session()
        self.prod_session.bind.echo = self.option.debug_sql
        engine = create_engine(
                self.conf['report_db_url'], echo=self.option.debug_sql)
        factory = sessionmaker(bind=engine)
        self.rpt_session = factory()
        register(self.rpt_session)
        self.base_q_report = self.rpt_session.query(self.report_orm)

    def get_db_session(self):  # Override, please
        pass

    def get_last_id(self, nama):
        q = self.rpt_session.query(Conf).filter_by(nama=nama)
        return q.first()

    def get_report(self, stan, ntb):
        q = self.base_q_report.filter_by(stan=stan, ntb=ntb)
        return q.first()

    def run_payment(self):  # Override, please
        pass

    def run_reversal(self):  # Override, please
        pass

    def run(self):
        if not self.pid:
            return
        self.run_payment()
        self.run_reversal()


# Tanpa tabel log_iso

class App(BaseApp):
    iso_class = None  # Override, please
    iso_reversal_orm = None  # Override, please

    def get_report(self, iso):  # Override
        d = get_keys(iso)
        return super().get_report(d['stan'], d['ntb'])

    def run_reversal(self, conf_name):  # Override
        last = self.get_last_id(conf_name)
        q = self.prod_session.query(self.iso_reversal_orm).filter(
                self.iso_reversal_orm.tgl > last.as_datetime())
        iso_class = self.module.Doc()
        for row in q.order_by(self.iso_reversal_orm.tgl):
            iso = get_iso(row.iso_request, self.iso_class, self.option.debug)
            rpt = self.get_report(iso)
            if not rpt or rpt.tgl_batal:
                continue
            rpt.tgl_batal = row.tgl
            last.nilai = s_tgl = dmyhms(row.tgl)
            d = get_keys(iso)
            self.log.info(
                f'Tgl batal {s_tgl}, Nomor bayar {d["nomor_bayar"]}, '
                f'STAN {d["stan"]}, NTB {d["ntb"]}, Channel {d["channel"]}')
            with transaction.manager:
                self.rpt_session.add(rpt)
                self.rpt_session.add(last)


# Dengan tabel log_iso

class App2(BaseApp):
    log_orm = None  # Override, please
    field_invoice_id = None  # Override, please
    field_ntb = None  # Override, please

    def get_stan(self, row):
        return row.bit_011.strip()

    def get_invoice_id(self, row):
        s = getattr(row, self.field_invoice_id)
        return s.strip()

    def get_ntb(self, row):
        s = getattr(row, self.field_ntb)
        return s.strip()

    def get_report(self, row):  # Override
        stan = self.get_stan(row)
        ntb = self.get_ntb(row)
        return super().get_report(stan, ntb)

    def get_keys(self, row):
        return dict(
            nomor_bayar=self.get_invoice_id(row),
            stan=self.get_stan(row),
            ntb=self.get_ntb(row),
            channel=get_channel_name_by_row(row))

    def run_reversal(self, conf_name):  # Override
        last = self.get_last_id(conf_name)
        q = self.prod_session.query(self.log_orm).filter_by(
                mti='0410', bit_039='00').filter(
                self.log_orm.id > last.id)
        for row in q.order_by(self.log_orm.id):
            rpt = self.get_report(row)
            if not rpt or rpt.tgl_batal:
                continue
            rpt.tgl_batal = row.created
            s_tgl = dmyhms(row.created)
            last.nilai = row.id
            d = self.get_keys(row)
            self.log.info(
                f'Tgl batal {s_tgl}, Nomor bayar {d["nomor_bayar"]}, '
                f'STAN {d["stan"]}, NTB {d["ntb"]}, Channel {d["channel"]}')
            with transaction.manager:
                self.rpt_session.add(rpt)
                self.rpt_session.add(last)


def init_db(argv=sys.argv[1:]):
    conf_file = argv[0]
    conf = ConfigParser()
    conf.read(conf_file)
    db_url = conf.get('main', 'report_db_url')
    engine = create_engine(db_url, echo=True)
    Base.metadata.create_all(engine)
    BaseConf.metadata.create_all(engine)
    factory = sessionmaker(bind=engine)
    my_registry['db_session'] = db_session = factory()
    register(db_session)
    with transaction.manager:
        append_csv(Conf, 'conf.csv', ['nama'])