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

# Source Code for Module Gnumed.business.gmBilling

      1  # -*- coding: utf-8 -*-
      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 = 'invoice'
     26  #============================================================
     27  # billables
     28  #------------------------------------------------------------
     29  _SQL_get_billable_fields = "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 % "pk_billable = %s"
     35          _cmds_store_payload = [
     36                  """UPDATE ref.billable SET
     37                                  fk_data_source = %(pk_data_source)s,
     38                                  code = %(billable_code)s,
     39                                  term = %(billable_description)s,
     40                                  comment = gm.nullify_empty_string(%(comment)s),
     41                                  amount = %(raw_amount)s,
     42                                  currency = %(currency)s,
     43                                  vat_multiplier = %(vat_multiplier)s,
     44                                  active = %(active)s
     45                                  --, discountable = %(discountable)s
     46                          WHERE
     47                                  pk = %(pk_billable)s
     48                                          AND
     49                                  xmin = %(xmin_billable)s
     50                          RETURNING
     51                                  xmin AS xmin_billable
     52                  """]
     53
     54          _updatable_fields = [
     55                  'billable_code',
     56                  'billable_description',
     57                  'raw_amount',
     58                  'vat_multiplier',
     59                  'comment',
     60                  'currency',
     61                  'active',
     62                  'pk_data_source'
     63          ]
     64          #--------------------------------------------------------
     65 -        def format(self):
     66                  txt = '%s                                    [#%s]\n\n' % (
     67                          gmTools.bool2subst (
     68                                  self._payload[self._idx['active']],
     69                                  _('Active billable item'),
     70                                  _('Inactive billable item')
     71                          ),
     72                          self._payload[self._idx['pk_billable']]
     73                  )
     74                  txt += ' %s: %s\n' % (
     75                          self._payload[self._idx['billable_code']],
     76                          self._payload[self._idx['billable_description']]
     77                  )
     78                  txt += _(' %(curr)s%(raw_val)s + %(perc_vat)s%% VAT = %(curr)s%(val_w_vat)s\n') % {
     79                          'curr': self._payload[self._idx['currency']],
     80                          'raw_val': self._payload[self._idx['raw_amount']],
     81                          'perc_vat': self._payload[self._idx['vat_multiplier']] * 100,
     82                          'val_w_vat': self._payload[self._idx['amount_with_vat']]
     83                  }
     84                  txt += ' %s %s%s (%s)' % (
     85                          self._payload[self._idx['catalog_short']],
     86                          self._payload[self._idx['catalog_version']],
     87                          gmTools.coalesce(self._payload[self._idx['catalog_language']], '', ' - %s'),
     88                          self._payload[self._idx['catalog_long']]
     89                  )
     90                  txt += gmTools.coalesce(self._payload[self._idx['comment']], '', '\n %s')
     91
     92                  return txt
     93          #--------------------------------------------------------
     94 -        def _get_is_in_use(self):
     95                  cmd = 'SELECT EXISTS(SELECT 1 FROM bill.bill_item WHERE fk_billable = %(pk)s LIMIT 1)'
     96                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self._payload[self._idx['pk_billable']]}}])
     97                  return rows[0][0]
     98
     99          is_in_use = property(_get_is_in_use, lambda x:x)
    100
    101  #------------------------------------------------------------
    102 -def get_billables(active_only=True, order_by=None, return_pks=False):
    103
    104          if order_by is None:
    105                  order_by = ' ORDER BY catalog_long, catalog_version, billable_code'
    106          else:
    107                  order_by = ' ORDER BY %s' % order_by
    108
    109          if active_only:
    110                  where = 'active IS true'
    111          else:
    112                  where = 'true'
    113
    114          cmd = (_SQL_get_billable_fields % where) + order_by
    115          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
    116          if return_pks:
    117                  return [ r['pk_billable'] for r in rows ]
    118          return [ cBillable(row = {'data': r, 'idx': idx, 'pk_field': 'pk_billable'}) for r in rows ]
    119
    120  #------------------------------------------------------------
    121 -def create_billable(code=None, term=None, data_source=None, return_existing=False):
    122          args = {
    123                  'code': code.strip(),
    124                  'term': term.strip(),
    125                  'data_src': data_source
    126          }
    127          cmd = """
    128                  INSERT INTO ref.billable (code, term, fk_data_source)
    129                  SELECT
    130                          %(code)s,
    131                          %(term)s,
    132                          %(data_src)s
    133                  WHERE NOT EXISTS (
    134                          SELECT 1 FROM ref.billable
    135                          WHERE
    136                                  code = %(code)s
    137                                          AND
    138                                  term = %(term)s
    139                                          AND
    140                                  fk_data_source = %(data_src)s
    141                  )
    142                  RETURNING pk"""
    143          rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False, return_data = True)
    144          if len(rows) > 0:
    145                  return cBillable(aPK_obj = rows[0]['pk'])
    146
    147          if not return_existing:
    148                  return None
    149
    150          cmd = """
    151                  SELECT * FROM ref.v_billables
    152                  WHERE
    153                          code = %(code)s
    154                                  AND
    155                          term = %(term)s
    156                                  AND
    157                          pk_data_source = %(data_src)s
    158          """
    159          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    160          return cBillable(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_billable'})
    161
    162  #------------------------------------------------------------
    163 -def delete_billable(pk_billable=None):
    164          cmd = """
    165                  DELETE FROM ref.billable
    166                  WHERE
    167                          pk = %(pk)s
    168                                  AND
    169                          NOT EXISTS (
    170                                  SELECT 1 FROM bill.bill_item WHERE fk_billable = %(pk)s
    171                          )
    172          """
    173          args = {'pk': pk_billable}
    174          gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
    175
    176  #============================================================
    177  # bill items
    178  #------------------------------------------------------------
    179  _SQL_get_bill_item_fields = u"SELECT * FROM bill.v_bill_items WHERE %s"
    180
    181 -class cBillItem(gmBusinessDBObject.cBusinessDBObject):
    182
    183          _cmd_fetch_payload = _SQL_get_bill_item_fields % u"pk_bill_item = %s"
    184          _cmds_store_payload = [
    185                  """UPDATE bill.bill_item SET
    186                                  fk_provider = %(pk_provider)s,
    187                                  fk_encounter = %(pk_encounter_to_bill)s,
    188                                  date_to_bill = %(raw_date_to_bill)s,
    189                                  description = gm.nullify_empty_string(%(item_detail)s),
    190                                  net_amount_per_unit = %(net_amount_per_unit)s,
    191                                  currency = gm.nullify_empty_string(%(currency)s),
    192                                  fk_bill = %(pk_bill)s,
    193                                  unit_count = %(unit_count)s,
    194                                  amount_multiplier = %(amount_multiplier)s
    195                          WHERE
    196                                  pk = %(pk_bill_item)s
    197                                          AND
    198                                  xmin = %(xmin_bill_item)s
    199                          RETURNING
    200                                  xmin AS xmin_bill_item
    201                  """]
    202
    203          _updatable_fields = [
    204                  'pk_provider',
    205                  'pk_encounter_to_bill',
    206                  'raw_date_to_bill',
    207                  'item_detail',
    208                  'net_amount_per_unit',
    209                  'currency',
    210                  'pk_bill',
    211                  'unit_count',
    212                  'amount_multiplier'
    213          ]
    214          #--------------------------------------------------------
    215 -        def format(self):
    216                  txt = '%s (%s %s%s)         [#%s]\n' % (
    217                          gmTools.bool2subst(
    218                                  self._payload[self._idx['pk_bill']] is None,
    219                                  _('Open item'),
    220                                  _('Billed item'),
    221                          ),
    222                          self._payload[self._idx['catalog_short']],
    223                          self._payload[self._idx['catalog_version']],
    224                          gmTools.coalesce(self._payload[self._idx['catalog_language']], '', ' - %s'),
    225                          self._payload[self._idx['pk_bill_item']]
    226                  )
    227                  txt += ' %s: %s\n' % (
    228                          self._payload[self._idx['billable_code']],
    229                          self._payload[self._idx['billable_description']]
    230                  )
    231                  txt += gmTools.coalesce (
    232                          self._payload[self._idx['billable_comment']],
    233                          '',
    234                          '  (%s)\n',
    235                  )
    236                  txt += gmTools.coalesce (
    237                          self._payload[self._idx['item_detail']],
    238                          '',
    239                          _(' Details: %s\n'),
    240                  )
    241
    242                  txt += '\n'
    243                  txt += _(' %s of units: %s\n') % (
    244                          gmTools.u_numero,
    245                          self._payload[self._idx['unit_count']]
    246                  )
    247                  txt += _(' Amount per unit: %(curr)s%(val_p_unit)s (%(cat_curr)s%(cat_val)s per catalog)\n') % {
    248                          'curr': self._payload[self._idx['currency']],
    249                          'val_p_unit': self._payload[self._idx['net_amount_per_unit']],
    250                          'cat_curr': self._payload[self._idx['billable_currency']],
    251                          'cat_val': self._payload[self._idx['billable_amount']]
    252                  }
    253                  txt += _(' Amount multiplier: %s\n') % self._payload[self._idx['amount_multiplier']]
    254                  txt += _(' VAT would be: %(perc_vat)s%% %(equals)s %(curr)s%(vat)s\n') % {
    255                          'perc_vat': self._payload[self._idx['vat_multiplier']] * 100,
    256                          'equals': gmTools.u_corresponds_to,
    257                          'curr': self._payload[self._idx['currency']],
    258                          'vat': self._payload[self._idx['vat']]
    259                  }
    260
    261                  txt += '\n'
    262                  txt += _(' Charge date: %s') % gmDateTime.pydt_strftime (
    263                          self._payload[self._idx['date_to_bill']],
    264                          '%Y %b %d',
    265                          accuracy = gmDateTime.acc_days
    266                  )
    267                  bill = self.bill
    268                  if bill is not None:
    269                          txt += _('\n On bill: %s') % bill['invoice_id']
    270
    271                  return txt
    272          #--------------------------------------------------------
    273 -        def _get_billable(self):
    274                  return cBillable(aPK_obj = self._payload[self._idx['pk_billable']])
    275
    276          billable = property(_get_billable, lambda x:x)
    277          #--------------------------------------------------------
    278 -        def _get_bill(self):
    279                  if self._payload[self._idx['pk_bill']] is None:
    280                          return None
    281                  return cBill(aPK_obj = self._payload[self._idx['pk_bill']])
    282
    283          bill = property(_get_bill, lambda x:x)
    284          #--------------------------------------------------------
    285 -        def _get_is_in_use(self):
    286                  return self._payload[self._idx['pk_bill']] is not None
    287
    288          is_in_use = property(_get_is_in_use, lambda x:x)
    289  #------------------------------------------------------------
    290 -def get_bill_items(pk_patient=None, non_invoiced_only=False, return_pks=False):
    291          if non_invoiced_only:
    292                  cmd = _SQL_get_bill_item_fields % u"pk_patient = %(pat)s AND pk_bill IS NULL"
    293          else:
    294                  cmd = _SQL_get_bill_item_fields % u"pk_patient = %(pat)s"
    295          args = {'pat': pk_patient}
    296          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    297          if return_pks:
    298                  return [ r['pk_bill_item'] for r in rows ]
    299          return [ cBillItem(row = {'data': r, 'idx': idx, 'pk_field': 'pk_bill_item'}) for r in rows ]
    300
    301  #------------------------------------------------------------
    302 -def create_bill_item(pk_encounter=None, pk_billable=None, pk_staff=None):
    303
    304          billable = cBillable(aPK_obj = pk_billable)
    305          cmd = """
    306                  INSERT INTO bill.bill_item (
    307                          fk_provider,
    308                          fk_encounter,
    309                          net_amount_per_unit,
    310                          currency,
    311                          fk_billable
    312                  ) VALUES (
    313                          %(staff)s,
    314                          %(enc)s,
    315                          %(val)s,
    316                          %(curr)s,
    317                          %(billable)s
    318                  )
    319                  RETURNING pk"""
    320          args = {
    321                  'staff': pk_staff,
    322                  'enc': pk_encounter,
    323                  'val': billable['raw_amount'],
    324                  'curr': billable['currency'],
    325                  'billable': pk_billable
    326          }
    327          rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
    328          return cBillItem(aPK_obj = rows[0][0])
    329
    330  #------------------------------------------------------------
    331 -def delete_bill_item(link_obj=None, pk_bill_item=None):
    332          cmd = 'DELETE FROM bill.bill_item WHERE pk = %(pk)s AND fk_bill IS NULL'
    333          args = {'pk': pk_bill_item}
    334          gmPG2.run_rw_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}])
    335
    336  #============================================================
    337  # bills
    338  #------------------------------------------------------------
    339  _SQL_get_bill_fields = """SELECT * FROM bill.v_bills WHERE %s"""
    340
    341 -class cBill(gmBusinessDBObject.cBusinessDBObject):
    342          """Represents a bill"""
    343
    344          _cmd_fetch_payload = _SQL_get_bill_fields % "pk_bill = %s"
    345          _cmds_store_payload = [
    346                  """UPDATE bill.bill SET
    347                                  invoice_id = gm.nullify_empty_string(%(invoice_id)s),
    348                                  close_date = %(close_date)s,
    349                                  apply_vat = %(apply_vat)s,
    350                                  comment = gm.nullify_empty_string(%(comment)s),
    351                                  fk_receiver_identity = %(pk_receiver_identity)s,
    352                                  fk_receiver_address = %(pk_receiver_address)s,
    353                                  fk_doc = %(pk_doc)s
    354                          WHERE
    355                                  pk = %(pk_bill)s
    356                                          AND
    357                                  xmin = %(xmin_bill)s
    358                          RETURNING
    359                                  pk as pk_bill,
    360                                  xmin as xmin_bill
    361                  """
    362          ]
    363          _updatable_fields = [
    364                  'invoice_id',
    365                  'pk_receiver_identity',
    366                  'close_date',
    367                  'apply_vat',
    368                  'comment',
    369                  'pk_receiver_address',
    370                  'pk_doc'
    371          ]
    372          #--------------------------------------------------------
    373 -        def format(self, include_receiver=True, include_doc=True):
    374                  txt = '%s                       [#%s]\n' % (
    375                          gmTools.bool2subst (
    376                                  (self._payload[self._idx['close_date']] is None),
    377                                  _('Open bill'),
    378                                  _('Closed bill')
    379                          ),
    380                          self._payload[self._idx['pk_bill']]
    381                  )
    382                  txt += _(' Invoice ID: %s\n') % self._payload[self._idx['invoice_id']]
    383
    384                  if self._payload[self._idx['close_date']] is not None:
    385                          txt += _(' Closed: %s\n') % gmDateTime.pydt_strftime (
    386                                  self._payload[self._idx['close_date']],
    387                                  '%Y %b %d',
    388                                  accuracy = gmDateTime.acc_days
    389                          )
    390
    391                  if self._payload[self._idx['comment']] is not None:
    392                          txt += _(' Comment: %s\n') % self._payload[self._idx['comment']]
    393
    394                  txt += _(' Bill value: %(curr)s%(val)s\n') % {
    395                          'curr': self._payload[self._idx['currency']],
    396                          'val': self._payload[self._idx['total_amount']]
    397                  }
    398
    399                  if self._payload[self._idx['apply_vat']] is None:
    400                          txt += _(' VAT: undecided\n')
    401                  elif self._payload[self._idx['apply_vat']] is True:
    402                          txt += _(' VAT: %(perc_vat)s%% %(equals)s %(curr)s%(vat)s\n') % {
    403                                  'perc_vat': self._payload[self._idx['percent_vat']],
    404                                  'equals': gmTools.u_corresponds_to,
    405                                  'curr': self._payload[self._idx['currency']],
    406                                  'vat': self._payload[self._idx['total_vat']]
    407                          }
    408                          txt += _(' Value + VAT: %(curr)s%(val)s\n') % {
    409                                  'curr': self._payload[self._idx['currency']],
    410                                  'val': self._payload[self._idx['total_amount_with_vat']]
    411                          }
    412                  else:
    413                          txt += _(' VAT: does not apply\n')
    414
    415                  if self._payload[self._idx['pk_bill_items']] is None:
    416                          txt += _(' Items billed: 0\n')
    417                  else:
    418                          txt += _(' Items billed: %s\n') % len(self._payload[self._idx['pk_bill_items']])
    419                  if include_doc:
    420                          txt += _(' Invoice: %s\n') % (
    421                                  gmTools.bool2subst (
    422                                          self._payload[self._idx['pk_doc']] is None,
    423                                          _('not available'),
    424                                          '#%s' % self._payload[self._idx['pk_doc']]
    425                                  )
    426                          )
    427                  txt += _(' Patient: #%s\n') % self._payload[self._idx['pk_patient']]
    428                  if include_receiver:
    429                          txt += gmTools.coalesce (
    430                                  self._payload[self._idx['pk_receiver_identity']],
    431                                  '',
    432                                  _(' Receiver: #%s\n')
    433                          )
    434                          if self._payload[self._idx['pk_receiver_address']] is not None:
    435                                  txt += '\n '.join(gmDemographicRecord.get_patient_address(pk_patient_address = self._payload[self._idx['pk_receiver_address']]).format())
    436
    437                  return txt
    438          #--------------------------------------------------------
    439 -        def add_items(self, items=None):
    440                  """Requires no pending changes within the bill itself."""
    441                  # should check for item consistency first
    442                  conn = gmPG2.get_connection(readonly = False)
    443                  for item in items:
    444                          item['pk_bill'] = self._payload[self._idx['pk_bill']]
    445                          item.save(conn = conn)
    446                  conn.commit()
    447                  self.refetch_payload()          # make sure aggregates are re-filled from view
    448          #--------------------------------------------------------
    449 -        def _get_bill_items(self):
    450                  return [ cBillItem(aPK_obj = pk) for pk in self._payload[self._idx['pk_bill_items']] ]
    451
    452          bill_items = property(_get_bill_items, lambda x:x)
    453          #--------------------------------------------------------
    454 -        def _get_invoice(self):
    455                  if self._payload[self._idx['pk_doc']] is None:
    456                          return None
    457                  return gmDocuments.cDocument(aPK_obj = self._payload[self._idx['pk_doc']])
    458
    459          invoice = property(_get_invoice, lambda x:x)
    460          #--------------------------------------------------------
    461 -        def _get_address(self):
    462                  if self._payload[self._idx['pk_receiver_address']] is None:
    463                          return None
    464                  return gmDemographicRecord.get_address_from_patient_address_pk (
    465                          pk_patient_address = self._payload[self._idx['pk_receiver_address']]
    466                  )
    467
    468          address = property(_get_address, lambda x:x)
    469          #--------------------------------------------------------
    470 -        def _get_default_address(self):
    471                  return gmDemographicRecord.get_patient_address_by_type (
    472                          pk_patient = self._payload[self._idx['pk_patient']],
    473                          adr_type = 'billing'
    474                  )
    475
    476          default_address = property(_get_default_address, lambda x:x)
    477          #--------------------------------------------------------
    478 -        def _get_home_address(self):
    479                  return gmDemographicRecord.get_patient_address_by_type (
    480                          pk_patient = self._payload[self._idx['pk_patient']],
    481                          adr_type = 'home'
    482                  )
    483
    484          home_address = property(_get_home_address, lambda x:x)
    485          #--------------------------------------------------------
    486 -        def set_missing_address_from_default(self):
    487                  if self._payload[self._idx['pk_receiver_address']] is not None:
    488                          return True
    489                  adr = self.default_address
    490                  if adr is None:
    491                          adr = self.home_address
    492                          if adr is None:
    493                                  return False
    494                  self['pk_receiver_address'] = adr['pk_lnk_person_org_address']
    495                  return self.save_payload()
    496
    497  #------------------------------------------------------------
    498 -def get_bills(order_by=None, pk_patient=None, return_pks=False):
    499
    500          args = {'pat': pk_patient}
    501          where_parts = ['true']
    502
    503          if pk_patient is not None:
    504                  where_parts.append('pk_patient = %(pat)s')
    505
    506          if order_by is None:
    507                  order_by = ''
    508          else:
    509                  order_by = ' ORDER BY %s' % order_by
    510
    511          cmd = (_SQL_get_bill_fields % ' AND '.join(where_parts)) + order_by
    512          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    513          if return_pks:
    514                  return [ r['pk_bill'] for r in rows ]
    515          return [ cBill(row = {'data': r, 'idx': idx, 'pk_field': 'pk_bill'}) for r in rows ]
    516
    517  #------------------------------------------------------------
    518 -def get_bills4document(pk_document=None):
    519          args = {'pk_doc': pk_document}
    520          cmd = _SQL_get_bill_fields % 'pk_doc = %(pk_doc)s'
    521          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    522          return [ cBill(row = {'data': r, 'idx': idx, 'pk_field': 'pk_bill'}) for r in rows ]
    523
    524  #------------------------------------------------------------
    525 -def create_bill(conn=None, invoice_id=None):
    526
    527          args = {'inv_id': invoice_id}
    528          cmd = """
    529                  INSERT INTO bill.bill (invoice_id)
    530                  VALUES (gm.nullify_empty_string(%(inv_id)s))
    531                  RETURNING pk
    532          """
    533          rows, idx = gmPG2.run_rw_queries(link_obj = conn, queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False)
    534
    535          return cBill(aPK_obj = rows[0]['pk'])
    536
    537  #------------------------------------------------------------
    538 -def delete_bill(link_obj=None, pk_bill=None):
    539          args = {'pk': pk_bill}
    540          cmd = "DELETE FROM bill.bill WHERE pk = %(pk)s"
    541          gmPG2.run_rw_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}])
    542          return True
    543
    544  #------------------------------------------------------------
    545 -def get_bill_receiver(pk_patient=None):
    546          pass
    547
    548  #------------------------------------------------------------
    549 -def get_invoice_id(pk_patient=None):
    550          return 'GM%s / %s' % (
    551                  pk_patient,
    552                  gmDateTime.pydt_strftime (
    553                          gmDateTime.pydt_now_here(),
    554                          '%Y-%m-%d / %H%M%S'
    555                  )
    556          )
    557
    558  #------------------------------------------------------------
    559 -def get_scan2pay_data(branch, bill, provider=None, comment=None):
    560          """Create scan2pay data for generating a QR code.
    561
    562          https://www.scan2pay.info
    563          --------------------------------
    564          BCD                                                                                                             # (3) fixed, barcode tag
    565          002                                                                                                             # (3) fixed, version
    566          1                                                                                                               # (1) charset, 1 = utf8
    567          SCT                                                                                                             # (3) fixed
    568          $<praxis_id::BIC//Bank//%(value)s::11>$                                 # (11) <BIC>
    569          $2<range_of::$<current_provider_name::%(lastnames)s::>$,$<praxis::%(praxis)s::>$::70>2$                 # (70) <Name of beneficiary> "Empfänger" - Praxis
    570          $<praxis_id::IBAN//Bank//%(value)s::34>$                                # (34) <IBAN>
    571          EUR$<bill::%(total_amount_with_vat)s::12>$                              # (12) <Amount in EURO> "EUR12.5"
    572                                                                                                                          # (4) <purpose of transfer> - leer
    573                                                                                                                          # (35) <remittance info - struct> - only this XOR the next field - GNUmed: leer
    574          $2<range_of::InvID=$<bill::%(invoice_id)s::>$/Date=$<today::%d.%B %Y::>$::140$>2$       # (140) <remittance info - text> "Client:Marie Louise La Lune" - "Rg Nr, date"
    575          <beneficiary-to-payor info>                                                             # (70)  "pay soon :-)" - optional - GNUmed nur wenn bytes verfügbar
    576          --------------------------------
    577          total: 331 bytes (not chars ! - cave UTF8)
    578          EOL: LF or CRLF
    579          last *used* element not followed by anything, IOW can omit pending non-used elements
    580          """
    581          assert (branch is not None), '<branch> must not be <None>'
    582          assert (bill is not None), '<bill> must not be <None>'
    583
    584          data = {}
    585          IBANs = branch.get_external_ids(id_type = 'IBAN', issuer = 'Bank')
    586          if len(IBANs) == 0:
    587                  _log.debug('no IBAN found, cannot create scan2pay data')
    588                  return None
    589          data['IBAN'] = IBANs[0]['value'][:34]
    590          data['beneficiary'] = gmTools.coalesce (
    591                  value2test = provider,
    592                  return_instead = branch['praxis'][:70],
    593                  template4value = '%%(lastnames)s, %s' % branch['praxis']
    594          )[:70]
    595          BICs = branch.get_external_ids(id_type = 'BIC', issuer = 'Bank')
    596          if len(BICs) == 0:
    597                  data['BIC'] = ''
    598          else:
    599                  data['BIC'] = BICs[0]['value'][:11]
    600          data['amount'] = bill['total_amount_with_vat'][:9]
    601          data['ref'] = (_('Inv: %s, %s') % (
    602                  bill['invoice_id'],
    603                  gmDateTime.pydt_strftime(gmDateTime.pydt_now_here(), '%d.%B %Y')
    604          ))[:140]
    605          data['cmt'] = gmTools.coalesce(comment, '', '\n%s')[:70]
    606
    607          data_str = 'BCD\n002\n1\nSCT\n%(BIC)s\n%(beneficiary)s\n%(IBAN)s\nEUR%(amount)s\n\n\n%(ref)s%(cmt)s' % data
    608          data_str_bytes = bytes(data_str, 'utf8')[:331]
    609          return str(data_str_bytes, 'utf8')
    610
    611  #============================================================
    612  # main
    613  #------------------------------------------------------------
    614  if __name__ == "__main__":
    615
    616          if len(sys.argv) < 2:
    617                  sys.exit()
    618
    619          if sys.argv[1] != 'test':
    620                  sys.exit()
    621
    622  #       from Gnumed.pycommon import gmLog2
    623  #       from Gnumed.pycommon import gmI18N
    624  #       from Gnumed.business import gmPerson
    625          from Gnumed.business import gmPraxis
    626
    627  #       gmI18N.activate_locale()
    628  ##      gmDateTime.init()
    629
    630 -        def test_default_address():
    631                  bills = get_bills(pk_patient = 12)
    632                  first_bill = bills[0]
    633                  print(first_bill.default_address)
    634
    635 -        def test_me():
    636                  print("--------------")
    637                  me = cBillable(aPK_obj=1)
    638                  fields = me.get_fields()
    639                  for field in fields:
    640                          print(field, ':', me[field])
    641                  print("updatable:", me.get_updatable_fields())
    642                  #me['vat']=4; me.store_payload()
    643
    644          #--------------------------------------------------
    645 -        def test_get_scan2pay_data():
    646                  prax = gmPraxis.get_praxis_branches()[0]
    647                  bills = get_bills(pk_patient = 12)
    648                  print(get_scan2pay_data (
    649                          prax,
    650                          bills[0],
    651                          provider=None,
    652                          comment = 'GNUmed test harness' + ('x' * 400)
    653                  ))
    654
    655          #--------------------------------------------------
    656          #test_me()
    657          #test_default_address()
    658          test_get_scan2pay_data()
    659