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