Package Gnumed :: Package business :: Module gmBilling |
[frames] | no frames]

# Source Code for Module Gnumed.business.gmBilling

      1  # -*- coding: utf8 -*-
      2  """Billing code.
      3
      4  Copyright: authors
      5  """
      6  #============================================================
      7  __author__ = "Nico Latzer <nl@mnet-online.de>, Karsten Hilbert <Karsten.Hilbert@gmx.net>"
      8  __license__ = 'GPL v2 or later (details at http://www.gnu.org)'
      9
     10  import sys
     11  import logging
     12
     13
     14  if __name__ == '__main__':
     15          sys.path.insert(0, '../../')
     16  from Gnumed.pycommon import gmPG2
     17  from Gnumed.pycommon import gmBusinessDBObject
     18  from Gnumed.pycommon import gmTools
     19  from Gnumed.pycommon import gmDateTime
     20  from Gnumed.business import gmDemographicRecord
     21  from Gnumed.business import gmDocuments
     22
     23  _log = logging.getLogger('gm.bill')
     24
     25  INVOICE_DOCUMENT_TYPE = u'invoice'
     26  #============================================================
     27  # billables
     28  #------------------------------------------------------------
     29  _SQL_get_billable_fields = u"SELECT * FROM ref.v_billables WHERE %s"
     30
     31 -class cBillable(gmBusinessDBObject.cBusinessDBObject):
     32          """Items which can be billed to patients."""
     33
     34          _cmd_fetch_payload = _SQL_get_billable_fields % u"""pk_billable = %s"""
     35          _cmds_store_payload = [
     36                  u"""UPDATE ref.billable SET
     37                                  code = %(billable_code)s,
     38                                  term = %(billable_description)s,
     39                                  amount = %(raw_amount)s,
     40                                  currency = %(currency)s,
     41                                  vat_multiplier = %(vat_multiplier)s
     42                          WHERE
     43                                  pk = %(pk_billabs)s
     44                                          AND
     45                                  xmin = %(xmin_billable)s
     46                          RETURNING
     47                                  xmin AS xmin_billable
     48                  """]
     49
     50          _updatable_fields = [
     51                  'billable_description',
     52                  'raw_amount',
     53                  'vat_multiplier',
     54          ]
     55          #--------------------------------------------------------
     56 -        def format(self):
     57                  txt = u'%s                                    [#%s]\n\n' % (
     58                          gmTools.bool2subst (
     59                                  self._payload[self._idx['active']],
     60                                  _('Active billable item'),
     61                                  _('Inactive billable item')
     62                          ),
     63                          self._payload[self._idx['pk_billable']]
     64                  )
     65                  txt += u' %s: %s\n' % (
     66                          self._payload[self._idx['billable_code']],
     67                          self._payload[self._idx['billable_description']]
     68                  )
     69                  txt += _(' %(curr)s%(raw_val)s + %(perc_vat)s%% VAT = %(curr)s%(val_w_vat)s\n') % {
     70                          'curr': self._payload[self._idx['currency']],
     71                          'raw_val': self._payload[self._idx['raw_amount']],
     72                          'perc_vat': self._payload[self._idx['vat_multiplier']] * 100,
     73                          'val_w_vat': self._payload[self._idx['amount_with_vat']]
     74                  }
     75                  txt += u' %s %s%s (%s)' % (
     76                          self._payload[self._idx['catalog_short']],
     77                          self._payload[self._idx['catalog_version']],
     78                          gmTools.coalesce(self._payload[self._idx['catalog_language']], u'', ' - %s'),
     79                          self._payload[self._idx['catalog_long']]
     80                  )
     81                  txt += gmTools.coalesce(self._payload[self._idx['comment']], u'', u'\n %s')
     82
     83                  return txt
     84          #--------------------------------------------------------
     85 -        def _get_is_in_use(self):
     86                  cmd = u'SELECT EXISTS(SELECT 1 FROM bill.bill_item WHERE fk_billable = %(pk)s LIMIT 1)'
     87                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self._payload[self._idx['pk_billable']]}}])
     88                  return rows[0][0]
     89
     90          is_in_use = property(_get_is_in_use, lambda x:x)
     91  #------------------------------------------------------------
     92 -def get_billables(active_only=True, order_by=None):
     93
     94          if order_by is None:
     95                  order_by = u' ORDER BY catalog_long, catalog_version, billable_code'
     96          else:
     97                  order_by = u' ORDER BY %s' % order_by
     98
     99          if active_only:
    100                  where = u'active IS true'
    101          else:
    102                  where = u'true'
    103
    104          cmd = (_SQL_get_billable_fields % where) + order_by
    105          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
    106          return [ cBillable(row = {'data': r, 'idx': idx, 'pk_field': 'pk_billable'}) for r in rows ]
    107  #------------------------------------------------------------
    108 -def delete_billable(pk_billable=None):
    109          cmd = u"""
    110                  DELETE FROM ref.billable
    111                  WHERE
    112                          pk = %(pk)s
    113                                  AND
    114                          NOT EXISTS (
    115                                  SELECT 1 FROM bill.bill_item WHERE fk_billable = %(pk)s
    116                          )
    117          """
    118          args = {'pk': pk_billable}
    119          gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
    120  #============================================================
    121  # bill items
    122  #------------------------------------------------------------
    123  _SQL_fetch_bill_item_fields = u"SELECT * FROM bill.v_bill_items WHERE %s"
    124
    125 -class cBillItem(gmBusinessDBObject.cBusinessDBObject):
    126
    127          _cmd_fetch_payload = _SQL_fetch_bill_item_fields % u"pk_bill_item = %s"
    128          _cmds_store_payload = [
    129                  u"""UPDATE bill.bill_item SET
    130                                  fk_provider = %(pk_provider)s,
    131                                  fk_encounter = %(pk_encounter_to_bill)s,
    132                                  date_to_bill = %(raw_date_to_bill)s,
    133                                  description = gm.nullify_empty_string(%(item_detail)s),
    134                                  net_amount_per_unit = %(net_amount_per_unit)s,
    135                                  currency = gm.nullify_empty_string(%(currency)s),
    136                                  fk_bill = %(pk_bill)s,
    137                                  unit_count = %(unit_count)s,
    138                                  amount_multiplier = %(amount_multiplier)s
    139                          WHERE
    140                                  pk = %(pk_bill_item)s
    141                                          AND
    142                                  xmin = %(xmin_bill_item)s
    143                          RETURNING
    144                                  xmin AS xmin_bill_item
    145                  """]
    146
    147          _updatable_fields = [
    148                  'pk_provider',
    149                  'pk_encounter_to_bill',
    150                  'raw_date_to_bill',
    151                  'item_detail',
    152                  'net_amount_per_unit',
    153                  'currency',
    154                  'pk_bill',
    155                  'unit_count',
    156                  'amount_multiplier'
    157          ]
    158          #--------------------------------------------------------
    159 -        def format(self):
    160                  txt = u'%s (%s %s%s)         [#%s]\n' % (
    161                          gmTools.bool2subst(
    162                                  self._payload[self._idx['pk_bill']] is None,
    163                                  _('Open item'),
    164                                  _('Billed item'),
    165                          ),
    166                          self._payload[self._idx['catalog_short']],
    167                          self._payload[self._idx['catalog_version']],
    168                          gmTools.coalesce(self._payload[self._idx['catalog_language']], u'', ' - %s'),
    169                          self._payload[self._idx['pk_bill_item']]
    170                  )
    171                  txt += u' %s: %s\n' % (
    172                          self._payload[self._idx['billable_code']],
    173                          self._payload[self._idx['billable_description']]
    174                  )
    175                  txt += gmTools.coalesce (
    176                          self._payload[self._idx['billable_comment']],
    177                          u'',
    178                          u'  (%s)\n',
    179                  )
    180                  txt += gmTools.coalesce (
    181                          self._payload[self._idx['item_detail']],
    182                          u'',
    183                          _(' Details: %s\n'),
    184                  )
    185
    186                  txt += u'\n'
    187                  txt += _(' %s of units: %s\n') % (
    188                          gmTools.u_numero,
    189                          self._payload[self._idx['unit_count']]
    190                  )
    191                  txt += _(' Amount per unit: %(curr)s%(val_p_unit)s (%(cat_curr)s%(cat_val)s per catalog)\n') % {
    192                          'curr': self._payload[self._idx['currency']],
    193                          'val_p_unit': self._payload[self._idx['net_amount_per_unit']],
    194                          'cat_curr': self._payload[self._idx['billable_currency']],
    195                          'cat_val': self._payload[self._idx['billable_amount']]
    196                  }
    197                  txt += _(' Amount multiplier: %s\n') % self._payload[self._idx['amount_multiplier']]
    198                  txt += _(' VAT would be: %(perc_vat)s%% %(equals)s %(curr)s%(vat)s\n') % {
    199                          'perc_vat': self._payload[self._idx['vat_multiplier']] * 100,
    200                          'equals': gmTools.u_corresponds_to,
    201                          'curr': self._payload[self._idx['currency']],
    202                          'vat': self._payload[self._idx['vat']]
    203                  }
    204
    205                  txt += u'\n'
    206                  txt += _(' Charge date: %s') % gmDateTime.pydt_strftime (
    207                          self._payload[self._idx['date_to_bill']],
    208                          '%Y %b %d',
    209                          accuracy = gmDateTime.acc_days
    210                  )
    211                  bill = self.bill
    212                  if bill is not None:
    213                          txt += _('\n On bill: %s') % bill['invoice_id']
    214
    215                  return txt
    216          #--------------------------------------------------------
    217 -        def _get_billable(self):
    218                  return cBillable(aPK_obj = self._payload[self._idx['pk_billable']])
    219
    220          billable = property(_get_billable, lambda x:x)
    221          #--------------------------------------------------------
    222 -        def _get_bill(self):
    223                  if self._payload[self._idx['pk_bill']] is None:
    224                          return None
    225                  return cBill(aPK_obj = self._payload[self._idx['pk_bill']])
    226
    227          bill = property(_get_bill, lambda x:x)
    228          #--------------------------------------------------------
    229 -        def _get_is_in_use(self):
    230                  return self._payload[self._idx['pk_bill']] is not None
    231
    232          is_in_use = property(_get_is_in_use, lambda x:x)
    233  #------------------------------------------------------------
    234 -def get_bill_items(pk_patient=None, non_invoiced_only=False):
    235          if non_invoiced_only:
    236                  cmd = _SQL_fetch_bill_item_fields % u"pk_patient = %(pat)s AND pk_bill IS NULL"
    237          else:
    238                  cmd = _SQL_fetch_bill_item_fields % u"pk_patient = %(pat)s"
    239          args = {'pat': pk_patient}
    240          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    241          return [ cBillItem(row = {'data': r, 'idx': idx, 'pk_field': 'pk_bill_item'}) for r in rows ]
    242  #------------------------------------------------------------
    243 -def create_bill_item(pk_encounter=None, pk_billable=None, pk_staff=None):
    244
    245          billable = cBillable(aPK_obj = pk_billable)
    246          cmd = u"""
    247                  INSERT INTO bill.bill_item (
    248                          fk_provider,
    249                          fk_encounter,
    250                          net_amount_per_unit,
    251                          currency,
    252                          fk_billable
    253                  ) VALUES (
    254                          %(staff)s,
    255                          %(enc)s,
    256                          %(val)s,
    257                          %(curr)s,
    258                          %(billable)s
    259                  )
    260                  RETURNING pk"""
    261          args = {
    262                  'staff': pk_staff,
    263                  'enc': pk_encounter,
    264                  'val': billable['raw_amount'],
    265                  'curr': billable['currency'],
    266                  'billable': pk_billable
    267          }
    268          rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
    269          return cBillItem(aPK_obj = rows[0][0])
    270  #------------------------------------------------------------
    271 -def delete_bill_item(pk_bill_item=None):
    272          cmd = u'DELETE FROM bill.bill_item WHERE pk = %(pk)s AND fk_bill IS NULL'
    273          args = {'pk': pk_bill_item}
    274          gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
    275
    276  #============================================================
    277  # bills
    278  #------------------------------------------------------------
    279  _SQL_get_bill_fields = u"""SELECT * FROM bill.v_bills WHERE %s"""
    280
    281 -class cBill(gmBusinessDBObject.cBusinessDBObject):
    282          """Represents a bill"""
    283
    284          _cmd_fetch_payload = _SQL_get_bill_fields % u"pk_bill = %s"
    285          _cmds_store_payload = [
    286                  u"""UPDATE bill.bill SET
    287                                  invoice_id = gm.nullify_empty_string(%(invoice_id)s),
    288                                  close_date = %(close_date)s,
    289                                  apply_vat = %(apply_vat)s,
    290                                  comment = gm.nullify_empty_string(%(comment)s),
    291                                  fk_receiver_identity = %(pk_receiver_identity)s,
    292                                  fk_receiver_address = %(pk_receiver_address)s,
    293                                  fk_doc = %(pk_doc)s
    294                          WHERE
    295                                  pk = %(pk_bill)s
    296                                          AND
    297                                  xmin = %(xmin_bill)s
    298                          RETURNING
    299                                  pk as pk_bill,
    300                                  xmin as xmin_bill
    301                  """
    302          ]
    303          _updatable_fields = [
    304                  u'invoice_id',
    305                  u'pk_receiver_identity',
    306                  u'close_date',
    307                  u'apply_vat',
    308                  u'comment',
    309                  u'pk_receiver_address',
    310                  u'pk_doc'
    311          ]
    312          #--------------------------------------------------------
    313 -        def format(self):
    314                  txt = u'%s                       [#%s]\n' % (
    315                          gmTools.bool2subst (
    316                                  (self._payload[self._idx['close_date']] is None),
    317                                  _('Open bill'),
    318                                  _('Closed bill')
    319                          ),
    320                          self._payload[self._idx['pk_bill']]
    321                  )
    322                  txt += _(' Invoice ID: %s\n') % self._payload[self._idx['invoice_id']]
    323
    324                  if self._payload[self._idx['close_date']] is not None:
    325                          txt += _(' Closed: %s\n') % gmDateTime.pydt_strftime (
    326                                  self._payload[self._idx['close_date']],
    327                                  '%Y %b %d',
    328                                  accuracy = gmDateTime.acc_days
    329                          )
    330
    331                  if self._payload[self._idx['comment']] is not None:
    332                          txt += _(' Comment: %s\n') % self._payload[self._idx['comment']]
    333
    334                  txt += _(' Bill value: %(curr)s%(val)s\n') % {
    335                          'curr': self._payload[self._idx['currency']],
    336                          'val': self._payload[self._idx['total_amount']]
    337                  }
    338
    339                  if self._payload[self._idx['apply_vat']]:
    340                          txt += _(' VAT: %(perc_vat)s%% %(equals)s %(curr)s%(vat)s\n') % {
    341                                  'perc_vat': self._payload[self._idx['percent_vat']],
    342                                  'equals': gmTools.u_corresponds_to,
    343                                  'curr': self._payload[self._idx['currency']],
    344                                  'vat': self._payload[self._idx['total_vat']]
    345                          }
    346                          txt += _(' Value + VAT: %(curr)s%(val)s\n') % {
    347                                  'curr': self._payload[self._idx['currency']],
    348                                  'val': self._payload[self._idx['total_amount_with_vat']]
    349                          }
    350                  else:
    351                          txt += _(' VAT: does not apply\n')
    352
    353                  if self._payload[self._idx['pk_bill_items']] is None:
    354                          txt += _(' Items billed: 0\n')
    355                  else:
    356                          txt += _(' Items billed: %s\n') % len(self._payload[self._idx['pk_bill_items']])
    357                  txt += _(' Invoice: %s\n') % (
    358                          gmTools.bool2subst (
    359                                  self._payload[self._idx['pk_doc']] is None,
    360                                  _('not available'),
    361                                  u'#%s' % self._payload[self._idx['pk_doc']]
    362                          )
    363                  )
    364                  txt += _(' Patient: #%s\n') % self._payload[self._idx['pk_patient']]
    365                  txt += gmTools.coalesce (
    366                          self._payload[self._idx['pk_receiver_identity']],
    367                          u'',
    368                          _(' Receiver: #%s\n')
    369                  )
    370                  if self._payload[self._idx['pk_receiver_address']] is not None:
    371                          txt += u'\n '.join(gmDemographicRecord.get_patient_address(pk_patient_address = self._payload[self._idx['pk_receiver_address']]).format())
    372
    373                  return txt
    374          #--------------------------------------------------------
    375 -        def add_items(self, items=None):
    376                  """Requires no pending changes within the bill itself."""
    377                  # should check for item consistency first
    378                  conn = gmPG2.get_connection(readonly = False)
    379                  for item in items:
    380                          item['pk_bill'] = self._payload[self._idx['pk_bill']]
    381                          item.save(conn = conn)
    382                  conn.commit()
    383                  self.refetch_payload()          # make sure aggregates are re-filled from view
    384          #--------------------------------------------------------
    385 -        def _get_bill_items(self):
    386                  return [ cBillItem(aPK_obj = pk) for pk in self._payload[self._idx['pk_bill_items']] ]
    387
    388          bill_items = property(_get_bill_items, lambda x:x)
    389          #--------------------------------------------------------
    390 -        def _get_invoice(self):
    391                  if self._payload[self._idx['pk_doc']] is None:
    392                          return None
    393                  return gmDocuments.cDocument(aPK_obj = self._payload[self._idx['pk_doc']])
    394
    395          invoice = property(_get_invoice, lambda x:x)
    396          #--------------------------------------------------------
    397 -        def _get_address(self):
    398                  if self._payload[self._idx['pk_receiver_address']] is None:
    399                          return None
    400                  return gmDemographicRecord.get_address_from_patient_address_pk (
    401                          pk_patient_address = self._payload[self._idx['pk_receiver_address']]
    402                  )
    403
    404          address = property(_get_address, lambda x:x)
    405          #--------------------------------------------------------
    406 -        def _get_default_address(self):
    407                  return gmDemographicRecord.get_patient_address_by_type (
    408                          pk_patient = self._payload[self._idx['pk_patient']],
    409                          adr_type = u'billing'
    410                  )
    411
    412          default_address = property(_get_default_address, lambda x:x)
    413          #--------------------------------------------------------
    414 -        def set_missing_address_from_default(self):
    415                  if self._payload[self._idx['pk_receiver_address']] is not None:
    416                          return True
    417                  adr = self.default_address
    418                  if adr is None:
    419                          return False
    420                  self['pk_receiver_address'] = adr['pk_lnk_person_org_address']
    421                  return self.save_payload()
    422  #------------------------------------------------------------
    423 -def get_bills(order_by=None, pk_patient=None):
    424
    425          args = {'pat': pk_patient}
    426          where_parts = [u'true']
    427
    428          if pk_patient is not None:
    429                  where_parts.append(u'pk_patient = %(pat)s')
    430
    431          if order_by is None:
    432                  order_by = u''
    433          else:
    434                  order_by = u' ORDER BY %s' % order_by
    435
    436          cmd = (_SQL_get_bill_fields % u' AND '.join(where_parts)) + order_by
    437          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    438          return [ cBill(row = {'data': r, 'idx': idx, 'pk_field': 'pk_bill'}) for r in rows ]
    439  #------------------------------------------------------------
    440 -def create_bill(conn=None, invoice_id=None):
    441
    442          args = {u'inv_id': invoice_id}
    443          cmd = u"""
    444                  INSERT INTO bill.bill (invoice_id)
    445                  VALUES (gm.nullify_empty_string(%(inv_id)s))
    446                  RETURNING pk
    447          """
    448          rows, idx = gmPG2.run_rw_queries(link_obj = conn, queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False)
    449
    450          return cBill(aPK_obj = rows[0]['pk'])
    451  #------------------------------------------------------------
    452 -def delete_bill(pk_bill=None):
    453          args = {'pk': pk_bill}
    454          cmd = u"DELETE FROM bill.bill WHERE pk = %(pk)s"
    455          gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
    456          return True
    457  #------------------------------------------------------------
    458 -def get_bill_receiver(pk_patient=None):
    459          pass
    460  #------------------------------------------------------------
    461 -def get_invoice_id(pk_patient=None):
    462          return u'GM%s / %s' % (
    463                  pk_patient,
    464                  gmDateTime.pydt_strftime (
    465                          gmDateTime.pydt_now_here(),
    466                          '%Y-%m-%d / %H%M%S'
    467                  )
    468          )
    469  #============================================================
    470  # main
    471  #------------------------------------------------------------
    472  if __name__ == "__main__":
    473
    474          if len(sys.argv) < 2:
    475                  sys.exit()
    476
    477          if sys.argv[1] != 'test':
    478                  sys.exit()
    479
    480  #       from Gnumed.pycommon import gmLog2
    481  #       from Gnumed.pycommon import gmI18N
    482  #       from Gnumed.business import gmPerson
    483
    484  #       gmI18N.activate_locale()
    485  ##      gmDateTime.init()
    486
    487 -        def test_default_address():
    488                  bills = get_bills(pk_patient = 12)
    489                  first_bill = bills[0]
    490                  print first_bill.default_address
    491
    492 -        def test_me():
    493                  print "--------------"
    494                  me = cBillable(aPK_obj=1)
    495                  fields = me.get_fields()
    496                  for field in fields:
    497                          print field, ':', me[field]
    498                  print "updatable:", me.get_updatable_fields()
    499                  #me['vat']=4; me.store_payload()
    500          #--------------------------------------------------
    501          #test_me()
    502          test_default_address()
    503