pbb.py 10.1 KB
import sys
from datetime import (
    datetime,
    date,
    timedelta,
    )
from ISO8583.ISO8583 import BitNotSet
from sqlalchemy import func
import transaction
from sismiop.services.base import get_id
from opensipkd.string import (
    FixLength,
    DateTimeVar,
    to_str,
    )
from opensipkd.waktu import (
    dmyhms,
    create_datetime,
    )
from opensipkd.iso8583.bjb.scripts.common import get_module_object
from opensipkd.iso8583.bjb.pbb.default import Doc
from opensipkd.iso8583.bjb.pbb.structure import INVOICE_PROFILE
from ..models import Pbb
from .common import (
    get_iso,
    get_channel_info_by_iso,
    BaseApp,
    )


class SpptNotFound(Exception):
    pass


one_second = timedelta(1/24/60/60)
one_day = timedelta(1)

row_limit = 1000

log_psppt_fields = (
    'tgl_rekam_byr_sppt', 'jml_sppt_yg_dibayar', 'nip_rekam_byr_sppt',
    'kd_kanwil', 'kd_kantor', 'kd_tp')

log_iso_bits = [
    (4, int),
    (11, str),
    (48, str)]


def get_log_row(row, fieldnames):
    r = dict()
    for fieldname in fieldnames:
        val = getattr(row, fieldname)
        if isinstance(val, datetime):
            val = to_str(val)
        elif isinstance(val, str):
            val = val.strip()
        r[fieldname] = val
    return r


def log_psppt_msg(psppt):
    invoice_id = get_id(psppt)
    log_psppt = get_log_row(psppt, log_psppt_fields)
    return f'ID {invoice_id} ke {psppt.pembayaran_sppt_ke}, '\
           f'psppt {log_psppt}'


def get_log_iso(iso, bits):
    r = dict()
    for bit, f in bits:
        val = iso.getBit(bit)
        if f == str:
            val = val.strip()
        else:
            val = f(val)
        r[bit] = val
    return r


def log_iso_msg(iso):
    r = get_log_iso(iso, log_iso_bits)
    return f'iso8583 {r}'


def get_profile(iso):
    p = FixLength(INVOICE_PROFILE)
    try:
        p.set_raw(iso.get_invoice_profile())
    except BitNotSet:
        return
    return p


def get_nama_wp(iso, sppt):
    if iso:
        p = get_profile(iso)
        if p:
            return p['Nama']
    return sppt.nm_wp_sppt


def get_user_id(iso):
    try:
        return iso.getBit(107)
    except BitNotSet:
        return


def data_pkeys(psppt):
    return dict(
            kd_propinsi=psppt.kd_propinsi, kd_dati2=psppt.kd_dati2,
            kd_kecamatan=psppt.kd_kecamatan, kd_kelurahan=psppt.kd_kelurahan,
            kd_blok=psppt.kd_blok, no_urut=psppt.no_urut,
            kd_jns_op=psppt.kd_jns_op, thn_pajak_sppt=psppt.thn_pajak_sppt,
            pembayaran_sppt_ke=psppt.pembayaran_sppt_ke)


class App(BaseApp):
    def __init__(self, argv):
        super().__init__(argv)
        if not self.pid:
            return
        d = Doc()
        self.iso_inquiry_orm = d.iso_inquiry_model
        self.iso_payment_orm = d.iso_payment_model
        self.iso_reversal_orm = d.iso_reversal_model
        self.base_q_iso = self.prod_session.query(
            self.iso_payment_orm, self.iso_inquiry_orm).filter(
            self.iso_payment_orm.inquiry_id == self.iso_inquiry_orm.id)
        self.models = get_module_object(self.conf['models'])
        self.Psppt = self.models.PembayaranSppt
        self.Sppt = self.models.Sppt
        self.base_q_sppt = self.prod_session.query(self.Sppt)
        self.nip_pospbb = self.conf['nip_pospbb']

    def get_iso_reversal_orm(self):  # Override
        return self.iso_reversal_orm

    def get_sppt(self, psppt):
        q = self.base_q_sppt.filter_by(
                kd_propinsi=psppt.kd_propinsi,
                kd_dati2=psppt.kd_dati2,
                kd_kecamatan=psppt.kd_kecamatan,
                kd_kelurahan=psppt.kd_kelurahan,
                kd_blok=psppt.kd_blok,
                no_urut=psppt.no_urut,
                kd_jns_op=psppt.kd_jns_op,
                thn_pajak_sppt=psppt.thn_pajak_sppt)
        return q.first()

    def create_data(self, psppt, row_pay, iso):
        sppt = self.get_sppt(psppt)
        if not sppt:
            msg = log_psppt_msg(psppt)
            self.log.error(f'{msg} tidak ada di tabel sppt')
            raise SpptNotFound
        nama_wp = get_nama_wp(iso, sppt)
        bank_id = iso and iso.get_bank_id() or None
        user_id = iso and get_user_id(iso) or None
        discount = hasattr(psppt, 'discount') and psppt.discount or None
        stan = iso and iso.get_stan() or None
        ntb = iso and iso.get_ntb() or None
        channel_kode = iso and iso.get_channel().strip() or \
            (row_pay and row_pay.channel) or '0000'
        return dict(
            stan=stan, ntb=ntb, jml_sppt_yg_dibayar=psppt.jml_sppt_yg_dibayar,
            denda_sppt=psppt.denda_sppt, discount=discount,
            tgl_pembayaran_sppt=psppt.tgl_pembayaran_sppt.date(),
            tgl_rekam_byr_sppt=psppt.tgl_rekam_byr_sppt,
            nm_wp_sppt=nama_wp, channel_kode=channel_kode,
            bank_id=bank_id, user_id=user_id,
            pbb_yg_harus_dibayar_sppt=sppt.pbb_yg_harus_dibayar_sppt)

    def get_iso_query(self, psppt):
        iso_inq = self.iso_inquiry_orm
        q = self.base_q_iso.filter_by(
                propinsi=psppt.kd_propinsi,
                kabupaten=psppt.kd_dati2,
                kecamatan=psppt.kd_kecamatan,
                kelurahan=psppt.kd_kelurahan,
                blok=psppt.kd_blok,
                urut=psppt.no_urut,
                jenis=psppt.kd_jns_op,
                tahun=psppt.thn_pajak_sppt,
                ke=psppt.pembayaran_sppt_ke)
        return q.order_by(iso_inq.id.desc())

    def get_iso_row(self, psppt):
        iso_inq = self.iso_inquiry_orm
        awal = psppt.tgl_rekam_byr_sppt.date()
        akhir = awal + one_day
        q = self.get_iso_query(psppt)
        q = q.filter(iso_inq.tgl >= awal, iso_inq.tgl < akhir)
        return q.first()

    def get_channel(self, psppt):
        row_pay = self.get_iso_row(psppt)
        if row_pay:
            row_pay, row_inq = row_pay
            iso = get_iso(row_pay.iso_request, Doc, self.option.debug)
            info = get_channel_info_by_iso(iso)
            return info['channel'], row_pay, row_inq, iso
        elif psppt.nip_rekam_byr_sppt.strip() == self.nip_pospbb:
            return 'POSPBB', None, None, None
        q = self.get_iso_query(psppt)
        row = q.first()
        if row:
            pay, inq = row
            msg = log_psppt_msg(psppt)
            msg = f'{msg}, iso8583 ada tapi tanggalnya beda yaitu {inq.tgl}'
            self.log.warning(msg)
        return 'MANUAL', None, None, None

    def get_report(self, psppt):
        q = self.rpt_session.query(Pbb).filter_by(
                 kd_propinsi=psppt.kd_propinsi,
                 kd_dati2=psppt.kd_dati2,
                 kd_kecamatan=psppt.kd_kecamatan,
                 kd_kelurahan=psppt.kd_kelurahan,
                 kd_blok=psppt.kd_blok,
                 no_urut=psppt.no_urut,
                 kd_jns_op=psppt.kd_jns_op,
                 thn_pajak_sppt=psppt.thn_pajak_sppt,
                 pembayaran_sppt_ke=psppt.pembayaran_sppt_ke)
        return q.first()

    def update_last(self):
        self.last.nilai = dmyhms(self.last_psppt.tgl_rekam_byr_sppt)
        with transaction.manager:
            self.rpt_session.add(self.last)

    def log_msg(self, no, psppt, iso, channel_nama):
        msg = log_psppt_msg(psppt)
        msg = f'#{no}/{self.count} {msg}'
        if iso:
            s = log_iso_msg(iso)
            msg = f'{msg}, {s}'
        return f'{msg}, Channel {channel_nama}'

    def update_from_date(self):
        q = self.prod_session.query(self.Psppt).filter(
                self.Psppt.tgl_rekam_byr_sppt > self.tgl_awal,
                self.Psppt.tgl_rekam_byr_sppt < self.tgl_akhir)
        q = q.order_by(self.Psppt.tgl_rekam_byr_sppt)
        no = self.offset
        found = False
        for psppt in q.offset(self.offset).limit(row_limit):
            found = True
            no += 1
            channel_nama, row_pay, row_inq, iso = self.get_channel(psppt)
            d = dict(channel_nama=channel_nama)
            if row_inq:
                d['tgl_inquiry'] = row_inq.tgl
            msg = self.log_msg(no, psppt, iso, channel_nama)
            if not psppt.jml_sppt_yg_dibayar:
                self.log.warning(
                    f'{msg}, field jml_sppt_yg_dibayar 0, abaikan')
                continue
            data = self.create_data(psppt, row_pay, iso)
            d.update(data)
            rpt = self.get_report(psppt)
            if rpt:
                method = 'UPDATE'
                rpt.from_dict(d)
            else:
                method = 'INSERT'
                pkeys = data_pkeys(psppt)
                d.update(pkeys)
                rpt = Pbb(**d)
            self.log.info(f'{msg}, {method}')
            with transaction.manager:
                self.rpt_session.add(rpt)
            self.last_psppt = psppt
        self.offset += row_limit
        return found

    def get_count(self):
        q = self.prod_session.query(func.count()).filter(
                self.Psppt.tgl_rekam_byr_sppt > self.tgl_awal,
                self.Psppt.tgl_rekam_byr_sppt < self.tgl_akhir)
        return q.scalar()

    def run_payment(self):  # Override
        self.last_psppt = None
        self.last = None
        self.offset = 0
        if self.option.update_from_id:
            raise UpdateFromIdNotImplemented
        if self.option.update_from_date:
            t = self.option.update_from_date.split(',')
            awal = datetime.strptime(t[0], '%d-%m-%Y')
            self.tgl_awal = datetime(awal.year, awal.month, awal.day)
            self.tgl_awal -= one_second
            if t[1:]:
                self.tgl_akhir = datetime.strptime(t[1], '%d-%m-%Y').date()
            else:
                self.tgl_akhir = date.today()
            self.tgl_akhir += one_day
        else:
            self.last = self.get_last_id('pbb payment last date')
            self.tgl_awal = self.last.as_datetime()
            self.tgl_akhir = date.today() + one_day
        self.count = self.get_count()
        while True:
            found = self.update_from_date()
            if not found:
                break
        if self.last_psppt:
            self.update_last()

    def get_doc_for_reversal(self, row):
        return Doc

    def run_reversal(self):  # Override
        pass


def main(argv=sys.argv[1:]):
    app = App(argv)
    app.run()