Package Gnumed :: Package wxpython :: Module gmBillingWidgets
[frames] | no frames]

Source Code for Module Gnumed.wxpython.gmBillingWidgets

   1  # -*- coding: utf-8 -*- 
   2  """GNUmed billing handling widgets.""" 
   3   
   4  #================================================================ 
   5  __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>" 
   6  __license__ = "GPL v2 or later" 
   7   
   8  import logging 
   9  import sys 
  10   
  11   
  12  import wx 
  13   
  14   
  15  if __name__ == '__main__': 
  16          sys.path.insert(0, '../../') 
  17  from Gnumed.pycommon import gmTools 
  18  from Gnumed.pycommon import gmDateTime 
  19  from Gnumed.pycommon import gmMatchProvider 
  20  from Gnumed.pycommon import gmDispatcher 
  21  from Gnumed.pycommon import gmPG2 
  22  from Gnumed.pycommon import gmCfg 
  23  from Gnumed.pycommon import gmCfg2 
  24  from Gnumed.pycommon import gmPrinting 
  25  from Gnumed.pycommon import gmNetworkTools 
  26   
  27  from Gnumed.business import gmBilling 
  28  from Gnumed.business import gmPerson 
  29  from Gnumed.business import gmStaff 
  30  from Gnumed.business import gmDocuments 
  31  from Gnumed.business import gmPraxis 
  32  from Gnumed.business import gmForms 
  33  from Gnumed.business import gmDemographicRecord 
  34   
  35  from Gnumed.wxpython import gmListWidgets 
  36  from Gnumed.wxpython import gmRegetMixin 
  37  from Gnumed.wxpython import gmPhraseWheel 
  38  from Gnumed.wxpython import gmGuiHelpers 
  39  from Gnumed.wxpython import gmEditArea 
  40  from Gnumed.wxpython import gmPersonContactWidgets 
  41  from Gnumed.wxpython import gmPatSearchWidgets 
  42  from Gnumed.wxpython import gmMacro 
  43  from Gnumed.wxpython import gmFormWidgets 
  44  from Gnumed.wxpython import gmDocumentWidgets 
  45  from Gnumed.wxpython import gmDataPackWidgets 
  46   
  47   
  48  _log = logging.getLogger('gm.ui') 
  49   
  50  #================================================================ 
51 -def edit_billable(parent=None, billable=None):
52 ea = cBillableEAPnl(parent, -1) 53 ea.data = billable 54 ea.mode = gmTools.coalesce(billable, 'new', 'edit') 55 dlg = gmEditArea.cGenericEditAreaDlg2 ( 56 parent = parent, 57 id = -1, 58 edit_area = ea, 59 single_entry = gmTools.bool2subst((billable is None), False, True) 60 ) 61 dlg.SetTitle(gmTools.coalesce(billable, _('Adding new billable'), _('Editing billable'))) 62 if dlg.ShowModal() == wx.ID_OK: 63 dlg.DestroyLater() 64 return True 65 dlg.DestroyLater() 66 return False
67 68 #----------------------------------------------------------------
69 -def manage_billables(parent=None):
70 71 if parent is None: 72 parent = wx.GetApp().GetTopWindow() 73 74 #------------------------------------------------------------ 75 def edit(billable=None): 76 return edit_billable(parent = parent, billable = billable)
77 #------------------------------------------------------------ 78 def delete(billable): 79 if billable.is_in_use: 80 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete this billable item. It is in use.'), beep = True) 81 return False 82 return gmBilling.delete_billable(pk_billable = billable['pk_billable']) 83 #------------------------------------------------------------ 84 def get_tooltip(item): 85 if item is None: 86 return None 87 return item.format() 88 #------------------------------------------------------------ 89 def refresh(lctrl): 90 billables = gmBilling.get_billables() 91 items = [ [ 92 b['billable_code'], 93 b['billable_description'], 94 '%(currency)s%(raw_amount)s' % b, 95 '%s (%s)' % (b['catalog_short'], b['catalog_version']), 96 gmTools.coalesce(b['comment'], ''), 97 b['pk_billable'] 98 ] for b in billables ] 99 lctrl.set_string_items(items) 100 lctrl.set_data(billables) 101 #------------------------------------------------------------ 102 def manage_data_packs(billable): 103 gmDataPackWidgets.manage_data_packs(parent = parent) 104 return True 105 #------------------------------------------------------------ 106 def browse_catalogs(billable): 107 dbcfg = gmCfg.cCfgSQL() 108 url = dbcfg.get2 ( 109 option = 'external.urls.schedules_of_fees', 110 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 111 bias = 'user', 112 default = 'http://www.e-bis.de/goae/defaultFrame.htm' 113 ) 114 gmNetworkTools.open_url_in_browser(url = url) 115 return False 116 #------------------------------------------------------------ 117 msg = _('\nThese are the items for billing registered with GNUmed.\n') 118 119 gmListWidgets.get_choices_from_list ( 120 parent = parent, 121 msg = msg, 122 caption = _('Showing billable items.'), 123 columns = [_('Code'), _('Description'), _('Value'), _('Catalog'), _('Comment'), '#'], 124 single_selection = True, 125 new_callback = edit, 126 edit_callback = edit, 127 delete_callback = delete, 128 refresh_callback = refresh, 129 middle_extra_button = ( 130 _('Data packs'), 131 _('Browse and install billing catalog (schedule of fees) data packs'), 132 manage_data_packs 133 ), 134 right_extra_button = ( 135 _('Catalogs (WWW)'), 136 _('Browse billing catalogs (schedules of fees) on the web'), 137 browse_catalogs 138 ), 139 list_tooltip_callback = get_tooltip 140 ) 141 142 #----------------------------------------------------------------
143 -class cBillablePhraseWheel(gmPhraseWheel.cPhraseWheel):
144
145 - def __init__(self, *args, **kwargs):
146 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 147 query = """ 148 SELECT -- DISTINCT ON (label) 149 r_vb.pk_billable 150 AS data, 151 r_vb.billable_code || ': ' || r_vb.billable_description || ' (' || r_vb.catalog_short || ' - ' || r_vb.catalog_version || ')' 152 AS list_label, 153 r_vb.billable_code || ' (' || r_vb.catalog_short || ' - ' || r_vb.catalog_version || ')' 154 AS field_label 155 FROM 156 ref.v_billables r_vb 157 WHERE 158 r_vb.active 159 AND ( 160 r_vb.billable_code %(fragment_condition)s 161 OR 162 r_vb.billable_description %(fragment_condition)s 163 ) 164 ORDER BY list_label 165 LIMIT 20 166 """ 167 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query) 168 mp.setThresholds(1, 2, 4) 169 self.matcher = mp
170 #------------------------------------------------------------
171 - def _data2instance(self):
172 return gmBilling.cBillable(aPK_obj = list(self._data.values())[0]['data'])
173 #------------------------------------------------------------
174 - def _get_data_tooltip(self):
175 if self.GetData() is None: 176 return None 177 billable = gmBilling.cBillable(aPK_obj = list(self._data.values())[0]['data']) 178 return billable.format()
179 #------------------------------------------------------------
180 - def set_from_instance(self, instance):
181 val = '%s (%s - %s)' % ( 182 instance['billable_code'], 183 instance['catalog_short'], 184 instance['catalog_version'] 185 ) 186 self.SetText(value = val, data = instance['pk_billable'])
187 #------------------------------------------------------------
188 - def set_from_pk(self, pk):
189 self.set_from_instance(gmBilling.cBillable(aPK_obj = pk))
190 191 #---------------------------------------------------------------- 192 from Gnumed.wxGladeWidgets import wxgBillableEAPnl 193
194 -class cBillableEAPnl(wxgBillableEAPnl.wxgBillableEAPnl, gmEditArea.cGenericEditAreaMixin):
195
196 - def __init__(self, *args, **kwargs):
197 198 try: 199 data = kwargs['billable'] 200 del kwargs['billable'] 201 except KeyError: 202 data = None 203 204 wxgBillableEAPnl.wxgBillableEAPnl.__init__(self, *args, **kwargs) 205 gmEditArea.cGenericEditAreaMixin.__init__(self) 206 207 self.mode = 'new' 208 self.data = data 209 if data is not None: 210 self.mode = 'edit'
211 212 #self.__init_ui() 213 #---------------------------------------------------------------- 214 # def __init_ui(self): 215 # # adjust phrasewheels etc 216 #---------------------------------------------------------------- 217 # generic Edit Area mixin API 218 #----------------------------------------------------------------
219 - def _valid_for_save(self):
220 221 validity = True 222 223 vat = self._TCTRL_vat.GetValue().strip() 224 if vat == '': 225 self.display_tctrl_as_valid(tctrl = self._TCTRL_vat, valid = True) 226 else: 227 success, vat = gmTools.input2decimal(initial = vat) 228 if success: 229 self.display_tctrl_as_valid(tctrl = self._TCTRL_vat, valid = True) 230 else: 231 validity = False 232 self.display_tctrl_as_valid(tctrl = self._TCTRL_vat, valid = False) 233 self.StatusText = _('VAT must be empty or a number.') 234 self._TCTRL_vat.SetFocus() 235 236 currency = self._TCTRL_currency.GetValue().strip() 237 if currency == '': 238 validity = False 239 self.display_tctrl_as_valid(tctrl = self._TCTRL_currency, valid = False) 240 self.StatusText = _('Currency is missing.') 241 self._TCTRL_currency.SetFocus() 242 else: 243 self.display_tctrl_as_valid(tctrl = self._TCTRL_currency, valid = True) 244 245 success, val = gmTools.input2decimal(initial = self._TCTRL_amount.GetValue()) 246 if success: 247 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = True) 248 else: 249 validity = False 250 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False) 251 self.StatusText = _('Value is missing.') 252 self._TCTRL_amount.SetFocus() 253 254 if self._TCTRL_description.GetValue().strip() == '': 255 validity = False 256 self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = False) 257 self.StatusText = _('Description is missing.') 258 self._TCTRL_description.SetFocus() 259 else: 260 self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = True) 261 262 if self._PRW_coding_system.GetData() is None: 263 validity = False 264 self._PRW_coding_system.display_as_valid(False) 265 self.StatusText = _('Coding system is missing.') 266 self._PRW_coding_system.SetFocus() 267 else: 268 self._PRW_coding_system.display_as_valid(True) 269 270 if self._TCTRL_code.GetValue().strip() == '': 271 validity = False 272 self.display_tctrl_as_valid(tctrl = self._TCTRL_code, valid = False) 273 self.StatusText = _('Code is missing.') 274 self._TCTRL_code.SetFocus() 275 else: 276 self.display_tctrl_as_valid(tctrl = self._TCTRL_code, valid = True) 277 278 return validity
279 #----------------------------------------------------------------
280 - def _save_as_new(self):
281 data = gmBilling.create_billable ( 282 code = self._TCTRL_code.GetValue().strip(), 283 term = self._TCTRL_description.GetValue().strip(), 284 data_source = self._PRW_coding_system.GetData(), 285 return_existing = False 286 ) 287 if data is None: 288 self.StatusText = _('Billable already exists.') 289 return False 290 291 val = self._TCTRL_amount.GetValue().strip() 292 if val != '': 293 tmp, val = gmTools.input2decimal(val) 294 data['raw_amount'] = val 295 val = self._TCTRL_currency.GetValue().strip() 296 if val != '': 297 data['currency'] = val 298 vat = self._TCTRL_vat.GetValue().strip() 299 if vat != '': 300 tmp, vat = gmTools.input2decimal(vat) 301 data['vat_multiplier'] = vat / 100 302 data['comment'] = self._TCTRL_comment.GetValue().strip() 303 data['active'] = self._CHBOX_active.GetValue() 304 305 data.save() 306 307 self.data = data 308 309 return True
310 #----------------------------------------------------------------
311 - def _save_as_update(self):
312 self.data['billable_description'] = self._TCTRL_description.GetValue().strip() 313 tmp, self.data['raw_amount'] = gmTools.input2decimal(self._TCTRL_amount.GetValue()) 314 self.data['currency'] = self._TCTRL_currency.GetValue().strip() 315 vat = self._TCTRL_vat.GetValue().strip() 316 if vat == '': 317 vat = 0 318 else: 319 tmp, vat = gmTools.input2decimal(vat) 320 self.data['vat_multiplier'] = vat / 100 321 self.data['comment'] = self._TCTRL_comment.GetValue().strip() 322 self.data['active'] = self._CHBOX_active.GetValue() 323 self.data.save() 324 return True
325 #----------------------------------------------------------------
326 - def _refresh_as_new(self):
327 self._TCTRL_code.SetValue('') 328 self._PRW_coding_system.SetText('', None) 329 self._TCTRL_description.SetValue('') 330 self._TCTRL_amount.SetValue('') 331 self._TCTRL_currency.SetValue('') 332 self._TCTRL_vat.SetValue('') 333 self._TCTRL_comment.SetValue('') 334 self._CHBOX_active.SetValue(True) 335 336 self._TCTRL_code.SetFocus()
337 #----------------------------------------------------------------
339 self._refresh_as_new()
340 #----------------------------------------------------------------
341 - def _refresh_from_existing(self):
342 self._TCTRL_code.SetValue(self.data['billable_code']) 343 self._TCTRL_code.Enable(False) 344 self._PRW_coding_system.SetText('%s (%s)' % (self.data['catalog_short'], self.data['catalog_version']), self.data['pk_data_source']) 345 self._PRW_coding_system.Enable(False) 346 self._TCTRL_description.SetValue(self.data['billable_description']) 347 self._TCTRL_amount.SetValue('%s' % self.data['raw_amount']) 348 self._TCTRL_currency.SetValue(self.data['currency']) 349 self._TCTRL_vat.SetValue('%s' % (self.data['vat_multiplier'] * 100)) 350 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], '')) 351 self._CHBOX_active.SetValue(self.data['active']) 352 353 self._TCTRL_description.SetFocus()
354 #---------------------------------------------------------------- 355 356 #================================================================ 357 # invoice related widgets 358 #----------------------------------------------------------------
359 -def configure_invoice_template(parent=None, with_vat=True):
360 361 if parent is None: 362 parent = wx.GetApp().GetTopWindow() 363 364 template = gmFormWidgets.manage_form_templates ( 365 parent = parent, 366 template_types = ['invoice'] 367 ) 368 369 if template is None: 370 gmDispatcher.send(signal = 'statustext', msg = _('No invoice template configured.'), beep = True) 371 return None 372 373 if template['engine'] not in ['L', 'X']: 374 gmDispatcher.send(signal = 'statustext', msg = _('No invoice template configured.'), beep = True) 375 return None 376 377 if with_vat: 378 option = 'form_templates.invoice_with_vat' 379 else: 380 option = 'form_templates.invoice_no_vat' 381 382 dbcfg = gmCfg.cCfgSQL() 383 dbcfg.set ( 384 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 385 option = option, 386 value = '%s - %s' % (template['name_long'], template['external_version']) 387 ) 388 389 return template
390 #----------------------------------------------------------------
391 -def get_invoice_template(parent=None, with_vat=True):
392 393 dbcfg = gmCfg.cCfgSQL() 394 if with_vat: 395 option = 'form_templates.invoice_with_vat' 396 else: 397 option = 'form_templates.invoice_no_vat' 398 399 template = dbcfg.get2 ( 400 option = option, 401 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 402 bias = 'user' 403 ) 404 405 if template is None: 406 template = configure_invoice_template(parent = parent, with_vat = with_vat) 407 if template is None: 408 gmGuiHelpers.gm_show_error ( 409 aMessage = _('There is no invoice template configured.'), 410 aTitle = _('Getting invoice template') 411 ) 412 return None 413 else: 414 try: 415 name, ver = template.split(' - ') 416 except Exception: 417 _log.exception('problem splitting invoice template name [%s]', template) 418 gmDispatcher.send(signal = 'statustext', msg = _('Problem loading invoice template.'), beep = True) 419 return None 420 template = gmForms.get_form_template(name_long = name, external_version = ver) 421 if template is None: 422 gmGuiHelpers.gm_show_error ( 423 aMessage = _('Cannot load invoice template [%s - %s]') % (name, ver), 424 aTitle = _('Getting invoice template') 425 ) 426 return None 427 428 return template
429 430 #================================================================ 431 # per-patient bill related widgets 432 #----------------------------------------------------------------
433 -def edit_bill(parent=None, bill=None, single_entry=False):
434 435 if bill is None: 436 # manually creating bills is not yet supported 437 return 438 439 ea = cBillEAPnl(parent, -1) 440 ea.data = bill 441 ea.mode = gmTools.coalesce(bill, 'new', 'edit') 442 dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea, single_entry = single_entry) 443 dlg.SetTitle(gmTools.coalesce(bill, _('Adding new bill'), _('Editing bill'))) 444 if dlg.ShowModal() == wx.ID_OK: 445 dlg.DestroyLater() 446 return True 447 dlg.DestroyLater() 448 return False
449 450 #----------------------------------------------------------------
451 -def create_bill_from_items(bill_items=None):
452 453 if len(bill_items) == 0: 454 return None 455 456 item = bill_items[0] 457 currency = item['currency'] 458 vat = item['vat_multiplier'] 459 pk_pat = item['pk_patient'] 460 461 # check item consistency 462 has_errors = False 463 for item in bill_items: 464 if item['pk_bill'] is not None: 465 msg = _( 466 'This item is already invoiced:\n' 467 '\n' 468 '%s\n' 469 '\n' 470 'Cannot put it on a second bill.' 471 ) % item.format() 472 has_errors = True 473 break 474 if (item['currency'] != currency) or ( 475 item['vat_multiplier'] != vat) or ( 476 item['pk_patient'] != pk_pat 477 ): 478 msg = _( 479 'All items to be included with a bill must\n' 480 'coincide on currency, VAT, and patient.\n' 481 '\n' 482 'This item does not:\n' 483 '\n' 484 '%s\n' 485 ) % item.format() 486 has_errors = True 487 break 488 if has_errors: 489 gmGuiHelpers.gm_show_warning(aTitle = _('Checking invoice items'), aMessage = msg) 490 return None 491 492 # create bill 493 person = gmPerson.cPerson(pk_pat) 494 dbcfg = gmCfg.cCfgSQL() 495 invoice_id_template = dbcfg.get2 ( 496 option = u'billing.invoice_id_template', 497 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 498 bias = 'user' 499 ) 500 invoice_id = None 501 max_attempts = 3 502 attempt = 0 503 while (invoice_id is None) and (attempt < max_attempts+1): 504 attempt += 1 505 invoice_id = gmBilling.generate_invoice_id(template = invoice_id_template, person = person) 506 if invoice_id is None: 507 continue 508 if gmBilling.lock_invoice_id(invoice_id): 509 break 510 invoice_id = None 511 if invoice_id is None: 512 gmGuiHelpers.gm_show_warning ( 513 aTitle = _('Generating bill'), 514 aMessage = _('Could not generate invoice ID.\n\nTry again later.') 515 ) 516 return None 517 518 bill = gmBilling.create_bill(invoice_id = invoice_id) 519 gmBilling.unlock_invoice_id(invoice_id) 520 _log.info('created bill [%s]', bill['invoice_id']) 521 bill.add_items(items = bill_items) 522 bill.set_missing_address_from_default() 523 524 return bill
525 526 #----------------------------------------------------------------
527 -def create_invoice_from_bill(parent = None, bill=None, print_it=False, keep_a_copy=True):
528 529 bill_patient_not_active = False 530 # do we have a current patient ? 531 curr_pat = gmPerson.gmCurrentPatient() 532 if curr_pat.connected: 533 # is the bill about the current patient, too ? 534 # (because that's what the new invoice would get 535 # created for and attached to) 536 if curr_pat.ID != bill['pk_patient']: 537 bill_patient_not_active = True 538 else: 539 bill_patient_not_active = True 540 541 # FIXME: could ask whether to set fk_receiver_identity 542 # FIXME: but this would need enabling the bill EA to edit same 543 if bill_patient_not_active: 544 activate_patient = gmGuiHelpers.gm_show_question ( 545 title = _('Creating invoice'), 546 question = _( 547 'Cannot find an existing invoice PDF for this bill.\n' 548 '\n' 549 'Active patient: %s\n' 550 'Patient on bill: #%s\n' 551 '\n' 552 'Activate patient on bill so invoice PDF can be created ?' 553 ) % ( 554 gmTools.coalesce(curr_pat.ID, '', '#%s'), 555 bill['pk_patient'] 556 ) 557 ) 558 if not activate_patient: 559 return False 560 if not gmPatSearchWidgets.set_active_patient(patient = bill['pk_patient']): 561 gmGuiHelpers.gm_show_error ( 562 aTitle = _('Creating invoice'), 563 aMessage = _('Cannot activate patient #%s.') % bill['pk_patient'] 564 ) 565 return False 566 567 if None in [ bill['close_date'], bill['pk_receiver_address'], bill['apply_vat'] ]: 568 edit_bill(parent = parent, bill = bill, single_entry = True) 569 # cannot invoice open bills 570 if bill['close_date'] is None: 571 _log.error('cannot create invoice from bill, bill not closed') 572 gmGuiHelpers.gm_show_warning ( 573 aTitle = _('Creating invoice'), 574 aMessage = _( 575 'Cannot create invoice from bill.\n' 576 '\n' 577 'The bill is not closed.' 578 ) 579 ) 580 return False 581 # cannot create invoice if no receiver address 582 if bill['pk_receiver_address'] is None: 583 _log.error('cannot create invoice from bill, lacking receiver address') 584 gmGuiHelpers.gm_show_warning ( 585 aTitle = _('Creating invoice'), 586 aMessage = _( 587 'Cannot create invoice from bill.\n' 588 '\n' 589 'There is no receiver address.' 590 ) 591 ) 592 return False 593 # cannot create invoice if applying VAT is undecided 594 if bill['apply_vat'] is None: 595 _log.error('cannot create invoice from bill, apply_vat undecided') 596 gmGuiHelpers.gm_show_warning ( 597 aTitle = _('Creating invoice'), 598 aMessage = _( 599 'Cannot create invoice from bill.\n' 600 '\n' 601 'You must decide on whether to apply VAT.' 602 ) 603 ) 604 return False 605 606 # find template 607 template = get_invoice_template(parent = parent, with_vat = bill['apply_vat']) 608 if template is None: 609 gmGuiHelpers.gm_show_warning ( 610 aTitle = _('Creating invoice'), 611 aMessage = _( 612 'Cannot create invoice from bill\n' 613 'without an invoice template.' 614 ) 615 ) 616 return False 617 618 # process template 619 try: 620 invoice = template.instantiate() 621 except KeyError: 622 _log.exception('cannot instantiate invoice template [%s]', template) 623 gmGuiHelpers.gm_show_error ( 624 aMessage = _('Invalid invoice template [%s - %s (%s)]') % (name, ver, template['engine']), 625 aTitle = _('Printing medication list') 626 ) 627 return False 628 629 ph = gmMacro.gmPlaceholderHandler() 630 #ph.debug = True 631 ph.set_cache_value('bill', bill) 632 invoice.substitute_placeholders(data_source = ph) 633 ph.unset_cache_value('bill') 634 pdf_name = invoice.generate_output() 635 if pdf_name is None: 636 gmGuiHelpers.gm_show_error ( 637 aMessage = _('Error generating invoice PDF.'), 638 aTitle = _('Creating invoice') 639 ) 640 return False 641 642 # keep a copy 643 if keep_a_copy: 644 files2import = [] 645 files2import.extend(invoice.final_output_filenames) 646 files2import.extend(invoice.re_editable_filenames) 647 doc = gmDocumentWidgets.save_files_as_new_document ( 648 parent = parent, 649 filenames = files2import, 650 document_type = template['instance_type'], 651 review_as_normal = True, 652 reference = bill['invoice_id'], 653 pk_org_unit = gmPraxis.gmCurrentPraxisBranch()['pk_org_unit'], 654 date_generated = gmDateTime.pydt_now_here() 655 ) 656 bill['pk_doc'] = doc['pk_doc'] 657 bill.save() 658 659 if not print_it: 660 return True 661 662 # print template 663 _cfg = gmCfg2.gmCfgData() 664 printed = gmPrinting.print_files(filenames = [pdf_name], jobtype = 'invoice', verbose = _cfg.get(option = 'debug')) 665 if not printed: 666 gmGuiHelpers.gm_show_error ( 667 aMessage = _('Error printing the invoice.'), 668 aTitle = _('Printing invoice') 669 ) 670 return True 671 672 return True
673 674 #----------------------------------------------------------------
675 -def delete_bill(parent=None, bill=None):
676 677 if parent is None: 678 parent = wx.GetApp().GetTopWindow() 679 680 dlg = gmGuiHelpers.c3ButtonQuestionDlg ( 681 parent, -1, 682 caption = _('Deleting bill'), 683 question = _( 684 'When deleting the bill [%s]\n' 685 'do you want to keep its items (effectively \"unbilling\" them)\n' 686 'or do you want to also delete the bill items from the patient ?\n' 687 ) % bill['invoice_id'], 688 button_defs = [ 689 {'label': _('Delete + keep'), 'tooltip': _('Delete the bill but keep ("unbill") its items.'), 'default': True}, 690 {'label': _('Delete all'), 'tooltip': _('Delete both the bill and its items from the patient.')} 691 ], 692 show_checkbox = True, 693 checkbox_msg = _('Also remove invoice PDF'), 694 checkbox_tooltip = _('Also remove the invoice PDF from the document archive (because it will not correspond to the bill anymore).') 695 ) 696 button_pressed = dlg.ShowModal() 697 delete_invoice = dlg.checkbox_is_checked() 698 dlg.DestroyLater() 699 700 if button_pressed == wx.ID_CANCEL: 701 return False 702 703 delete_items = (button_pressed == wx.ID_NO) 704 705 if delete_invoice: 706 if bill['pk_doc'] is not None: 707 gmDocuments.delete_document ( 708 document_id = bill['pk_doc'], 709 encounter_id = gmPerson.cPatient(aPK_obj = bill['pk_patient']).emr.active_encounter['pk_encounter'] 710 ) 711 712 items = bill['pk_bill_items'] 713 success = gmBilling.delete_bill(pk_bill = bill['pk_bill']) 714 if delete_items: 715 for item in items: 716 gmBilling.delete_bill_item(pk_bill_item = item) 717 718 return success
719 720 #----------------------------------------------------------------
721 -def remove_items_from_bill(parent=None, bill=None):
722 723 if bill is None: 724 return False 725 726 list_data = bill.bill_items 727 if len(list_data) == 0: 728 return False 729 730 if parent is None: 731 parent = wx.GetApp().GetTopWindow() 732 733 list_items = [ [ 734 gmDateTime.pydt_strftime(b['date_to_bill'], '%Y %b %d', accuracy = gmDateTime.acc_days), 735 b['unit_count'], 736 '%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], '', ' - %s')), 737 '%(curr)s %(total_val)s (%(count)s %(x)s %(unit_val)s%(x)s%(val_multiplier)s)' % { 738 'curr': b['currency'], 739 'total_val': b['total_amount'], 740 'count': b['unit_count'], 741 'x': gmTools.u_multiply, 742 'unit_val': b['net_amount_per_unit'], 743 'val_multiplier': b['amount_multiplier'] 744 }, 745 '%(curr)s%(vat)s (%(perc_vat)s%%)' % { 746 'vat': b['vat'], 747 'curr': b['currency'], 748 'perc_vat': b['vat_multiplier'] * 100 749 }, 750 '%s (%s)' % (b['catalog_short'], b['catalog_version']), 751 b['pk_bill_item'] 752 ] for b in list_data ] 753 754 msg = _('Select the items you want to remove from bill [%s]:\n') % bill['invoice_id'] 755 items2remove = gmListWidgets.get_choices_from_list ( 756 parent = parent, 757 msg = msg, 758 caption = _('Removing items from bill'), 759 columns = [_('Date'), _('Count'), _('Description'), _('Value'), _('VAT'), _('Catalog'), '#'], 760 single_selection = False, 761 choices = list_items, 762 data = list_data 763 ) 764 765 if items2remove is None: 766 return False 767 768 if len(items2remove) == len(list_items): 769 gmGuiHelpers.gm_show_info ( 770 title = _('Removing items from bill'), 771 info = _( 772 'Cannot remove all items from a bill because\n' 773 'GNUmed does not support empty bills.\n' 774 '\n' 775 'You must delete the bill itself if you want to\n' 776 'remove all items (at which point you can opt to\n' 777 'keep the items and only delete the bill).' 778 ) 779 ) 780 return False 781 782 dlg = gmGuiHelpers.c3ButtonQuestionDlg ( 783 parent, -1, 784 caption = _('Removing items from bill'), 785 question = _( 786 '%s items selected from bill [%s]\n' 787 '\n' 788 'Do you want to only remove the selected items\n' 789 'from the bill ("unbill" them) or do you want\n' 790 'to delete them entirely from the patient ?\n' 791 '\n' 792 'Note that neither action is reversible.' 793 ) % ( 794 len(items2remove), 795 bill['invoice_id'] 796 ), 797 button_defs = [ 798 {'label': _('"Unbill"'), 'tooltip': _('Only "unbill" items (remove from bill but do not delete from patient).'), 'default': True}, 799 {'label': _('Delete'), 'tooltip': _('Completely delete items from the patient.')} 800 ], 801 show_checkbox = True, 802 checkbox_msg = _('Also remove invoice PDF'), 803 checkbox_tooltip = _('Also remove the invoice PDF from the document archive (because it will not correspond to the bill anymore).') 804 ) 805 button_pressed = dlg.ShowModal() 806 delete_invoice = dlg.checkbox_is_checked() 807 dlg.DestroyLater() 808 809 if button_pressed == wx.ID_CANCEL: 810 return False 811 812 # remember this because unlinking/deleting the items 813 # will remove the patient PK from the bill 814 pk_patient = bill['pk_patient'] 815 816 for item in items2remove: 817 item['pk_bill'] = None 818 item.save() 819 if button_pressed == wx.ID_NO: 820 gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item']) 821 822 if delete_invoice: 823 if bill['pk_doc'] is not None: 824 gmDocuments.delete_document ( 825 document_id = bill['pk_doc'], 826 encounter_id = gmPerson.cPatient(aPK_obj = pk_patient).emr.active_encounter['pk_encounter'] 827 ) 828 829 # delete bill, too, if empty 830 if len(bill.bill_items) == 0: 831 gmBilling.delete_bill(pk_bill = bill['pk_bill']) 832 833 return True
834 835 #----------------------------------------------------------------
836 -def manage_bills(parent=None, patient=None):
837 838 if parent is None: 839 parent = wx.GetApp().GetTopWindow() 840 841 #------------------------------------------------------------ 842 def show_pdf(bill): 843 if bill is None: 844 return False 845 846 # find invoice 847 invoice = bill.invoice 848 if invoice is not None: 849 success, msg = invoice.parts[-1].display_via_mime() 850 if not success: 851 gmGuiHelpers.gm_show_error(aMessage = msg, aTitle = _('Displaying invoice')) 852 return False 853 854 # create it ? 855 create_it = gmGuiHelpers.gm_show_question ( 856 title = _('Displaying invoice'), 857 question = _( 858 'Cannot find an existing\n' 859 'invoice PDF for this bill.\n' 860 '\n' 861 'Do you want to create one ?' 862 ), 863 ) 864 if not create_it: 865 return False 866 867 # prepare invoicing 868 if not bill.set_missing_address_from_default(): 869 gmGuiHelpers.gm_show_warning ( 870 aTitle = _('Creating invoice'), 871 aMessage = _( 872 'There is no pre-configured billing address.\n' 873 '\n' 874 'Select the address you want to send the bill to.' 875 ) 876 ) 877 edit_bill(parent = parent, bill = bill, single_entry = True) 878 if bill['pk_receiver_address'] is None: 879 return False 880 if bill['close_date'] is None: 881 bill['close_date'] = gmDateTime.pydt_now_here() 882 bill.save() 883 884 return create_invoice_from_bill(parent = parent, bill = bill, print_it = True, keep_a_copy = True)
885 #------------------------------------------------------------ 886 def edit(bill): 887 return edit_bill(parent = parent, bill = bill, single_entry = True) 888 #------------------------------------------------------------ 889 def delete(bill): 890 return delete_bill(parent = parent, bill = bill) 891 #------------------------------------------------------------ 892 def remove_items(bill): 893 return remove_items_from_bill(parent = parent, bill = bill) 894 #------------------------------------------------------------ 895 def get_tooltip(item): 896 if item is None: 897 return None 898 return item.format() 899 #------------------------------------------------------------ 900 def refresh(lctrl): 901 if patient is None: 902 bills = gmBilling.get_bills() 903 else: 904 bills = gmBilling.get_bills(pk_patient = patient.ID) 905 items = [] 906 for b in bills: 907 if b['close_date'] is None: 908 close_date = _('<open>') 909 else: 910 close_date = gmDateTime.pydt_strftime(b['close_date'], '%Y %b %d') 911 if b['total_amount'] is None: 912 amount = _('no items on bill') 913 else: 914 amount = gmTools.bool2subst ( 915 b['apply_vat'], 916 _('%(currency)s%(total_amount_with_vat)s (with %(percent_vat)s%% VAT)') % b, 917 '%(currency)s%(total_amount)s' % b, 918 _('without VAT: %(currency)s%(total_amount)s / with %(percent_vat)s%% VAT: %(currency)s%(total_amount_with_vat)s') % b 919 ) 920 items.append ([ 921 close_date, 922 b['invoice_id'], 923 amount, 924 gmTools.coalesce(b['comment'], '') 925 ]) 926 lctrl.set_string_items(items) 927 lctrl.set_data(bills) 928 #------------------------------------------------------------ 929 return gmListWidgets.get_choices_from_list ( 930 parent = parent, 931 caption = _('Showing bills.'), 932 columns = [_('Close date'), _('Invoice ID'), _('Value'), _('Comment')], 933 single_selection = True, 934 edit_callback = edit, 935 delete_callback = delete, 936 refresh_callback = refresh, 937 middle_extra_button = ( 938 'PDF', 939 _('Create if necessary, and show the corresponding invoice PDF'), 940 show_pdf 941 ), 942 right_extra_button = ( 943 _('Unbill'), 944 _('Select and remove items from a bill.'), 945 remove_items 946 ), 947 list_tooltip_callback = get_tooltip 948 ) 949 950 #---------------------------------------------------------------- 951 from Gnumed.wxGladeWidgets import wxgBillEAPnl 952
953 -class cBillEAPnl(wxgBillEAPnl.wxgBillEAPnl, gmEditArea.cGenericEditAreaMixin):
954
955 - def __init__(self, *args, **kwargs):
956 957 try: 958 data = kwargs['bill'] 959 del kwargs['bill'] 960 except KeyError: 961 data = None 962 963 wxgBillEAPnl.wxgBillEAPnl.__init__(self, *args, **kwargs) 964 gmEditArea.cGenericEditAreaMixin.__init__(self) 965 966 self.mode = 'new' 967 self.data = data 968 if data is not None: 969 self.mode = 'edit' 970 971 self._3state2bool = { 972 wx.CHK_UNCHECKED: False, 973 wx.CHK_CHECKED: True, 974 wx.CHK_UNDETERMINED: None 975 } 976 self.bool_to_3state = { 977 False: wx.CHK_UNCHECKED, 978 True: wx.CHK_CHECKED, 979 None: wx.CHK_UNDETERMINED 980 }
981 982 # self.__init_ui() 983 #---------------------------------------------------------------- 984 # def __init_ui(self): 985 #---------------------------------------------------------------- 986 # generic Edit Area mixin API 987 #----------------------------------------------------------------
988 - def _valid_for_save(self):
989 validity = True 990 991 # flag but do not count as wrong 992 if not self._PRW_close_date.is_valid_timestamp(empty_is_valid = False): 993 self._PRW_close_date.SetFocus() 994 995 # flag but do not count as wrong 996 if self._CHBOX_vat_applies.ThreeStateValue == wx.CHK_UNDETERMINED: 997 self._CHBOX_vat_applies.SetFocus() 998 self._CHBOX_vat_applies.SetBackgroundColour('yellow') 999 1000 return validity
1001 #----------------------------------------------------------------
1002 - def _save_as_new(self):
1003 # not intended to be used 1004 return False
1005 #----------------------------------------------------------------
1006 - def _save_as_update(self):
1007 self.data['close_date'] = self._PRW_close_date.GetData() 1008 self.data['apply_vat'] = self._3state2bool[self._CHBOX_vat_applies.ThreeStateValue] 1009 self.data['comment'] = self._TCTRL_comment.GetValue() 1010 self.data.save() 1011 return True
1012 #----------------------------------------------------------------
1013 - def _refresh_as_new(self):
1014 pass # not used
1015 #----------------------------------------------------------------
1017 self._refresh_as_new()
1018 #----------------------------------------------------------------
1019 - def _refresh_from_existing(self):
1020 self._TCTRL_invoice_id.SetValue(self.data['invoice_id']) 1021 self._PRW_close_date.SetText(data = self.data['close_date']) 1022 1023 self.data.set_missing_address_from_default() 1024 if self.data['pk_receiver_address'] is None: 1025 self._TCTRL_address.SetValue('') 1026 else: 1027 adr = self.data.address 1028 self._TCTRL_address.SetValue(adr.format(single_line = True, show_type = False)) 1029 1030 self._TCTRL_value.SetValue('%(currency)s%(total_amount)s' % self.data) 1031 self._CHBOX_vat_applies.ThreeStateValue = self.bool_to_3state[self.data['apply_vat']] 1032 self._CHBOX_vat_applies.SetLabel(_('&VAT applies (%s%%)') % self.data['percent_vat']) 1033 if self.data['apply_vat'] is True: 1034 tmp = '%s %%(currency)s%%(total_vat)s %s %s %%(currency)s%%(total_amount_with_vat)s' % ( 1035 gmTools.u_corresponds_to, 1036 gmTools.u_arrow2right, 1037 gmTools.u_sum, 1038 ) 1039 self._TCTRL_value_with_vat.SetValue(tmp % self.data) 1040 elif self.data['apply_vat'] is None: 1041 self._TCTRL_value_with_vat.SetValue('?') 1042 else: 1043 self._TCTRL_value_with_vat.SetValue('') 1044 1045 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], '')) 1046 1047 self._PRW_close_date.SetFocus()
1048 #---------------------------------------------------------------- 1049 # event handling 1050 #----------------------------------------------------------------
1051 - def _on_vat_applies_box_checked(self, event):
1052 if self._CHBOX_vat_applies.ThreeStateValue == wx.CHK_CHECKED: 1053 tmp = '%s %%(currency)s%%(total_vat)s %s %s %%(currency)s%%(total_amount_with_vat)s' % ( 1054 gmTools.u_corresponds_to, 1055 gmTools.u_arrow2right, 1056 gmTools.u_sum, 1057 ) 1058 self._TCTRL_value_with_vat.SetValue(tmp % self.data) 1059 return 1060 if self._CHBOX_vat_applies.ThreeStateValue == wx.CHK_UNDETERMINED: 1061 self._TCTRL_value_with_vat.SetValue('?') 1062 return 1063 self._TCTRL_value_with_vat.SetValue('')
1064 #----------------------------------------------------------------
1065 - def _on_select_address_button_pressed(self, event):
1066 adr = gmPersonContactWidgets.select_address ( 1067 missing = _('billing'), 1068 person = gmPerson.cPerson(aPK_obj = self.data['pk_patient']) 1069 ) 1070 if adr is None: 1071 gmGuiHelpers.gm_show_info ( 1072 aTitle = _('Selecting address'), 1073 aMessage = _('GNUmed does not know any addresses for this patient.') 1074 ) 1075 return 1076 self.data['pk_receiver_address'] = adr['pk_lnk_person_org_address'] 1077 self.data.save() 1078 self._TCTRL_address.SetValue(adr.format(single_line = True, show_type = False))
1079 1080 #================================================================ 1081 # per-patient bill items related widgets 1082 #----------------------------------------------------------------
1083 -def edit_bill_item(parent=None, bill_item=None, single_entry=False):
1084 1085 if bill_item is not None: 1086 if bill_item.is_in_use: 1087 gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit already invoiced bill item.'), beep = True) 1088 return False 1089 1090 ea = cBillItemEAPnl(parent, -1) 1091 ea.data = bill_item 1092 ea.mode = gmTools.coalesce(bill_item, 'new', 'edit') 1093 dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea, single_entry = single_entry) 1094 dlg.SetTitle(gmTools.coalesce(bill_item, _('Adding new bill item'), _('Editing bill item'))) 1095 if dlg.ShowModal() == wx.ID_OK: 1096 dlg.DestroyLater() 1097 return True 1098 dlg.DestroyLater() 1099 return False
1100 #----------------------------------------------------------------
1101 -def manage_bill_items(parent=None, pk_patient=None):
1102 1103 if parent is None: 1104 parent = wx.GetApp().GetTopWindow() 1105 #------------------------------------------------------------ 1106 def edit(item=None): 1107 return edit_bill_item(parent = parent, bill_item = item, single_entry = (item is not None))
1108 #------------------------------------------------------------ 1109 def delete(item): 1110 if item.is_in_use is not None: 1111 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete already invoiced bill items.'), beep = True) 1112 return False 1113 gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item']) 1114 return True 1115 #------------------------------------------------------------ 1116 def get_tooltip(item): 1117 if item is None: 1118 return None 1119 return item.format() 1120 #------------------------------------------------------------ 1121 def refresh(lctrl): 1122 b_items = gmBilling.get_bill_items(pk_patient = pk_patient) 1123 items = [ [ 1124 gmDateTime.pydt_strftime(b['date_to_bill'], '%Y %b %d', accuracy = gmDateTime.acc_days), 1125 b['unit_count'], 1126 '%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], '', ' - %s')), 1127 b['currency'], 1128 '%s (%s %s %s%s%s)' % ( 1129 b['total_amount'], 1130 b['unit_count'], 1131 gmTools.u_multiply, 1132 b['net_amount_per_unit'], 1133 gmTools.u_multiply, 1134 b['amount_multiplier'] 1135 ), 1136 '%s (%s%%)' % ( 1137 b['vat'], 1138 b['vat_multiplier'] * 100 1139 ), 1140 '%s (%s)' % (b['catalog_short'], b['catalog_version']), 1141 b['pk_bill_item'] 1142 ] for b in b_items ] 1143 lctrl.set_string_items(items) 1144 lctrl.set_data(b_items) 1145 #------------------------------------------------------------ 1146 gmListWidgets.get_choices_from_list ( 1147 parent = parent, 1148 #msg = msg, 1149 caption = _('Showing bill items.'), 1150 columns = [_('Date'), _('Count'), _('Description'), _('$__replace_by_your_currency_symbol')[:-len('__replace_by_your_currency_symbol')], _('Value'), _('VAT'), _('Catalog'), '#'], 1151 single_selection = True, 1152 new_callback = edit, 1153 edit_callback = edit, 1154 delete_callback = delete, 1155 refresh_callback = refresh, 1156 list_tooltip_callback = get_tooltip 1157 ) 1158 1159 #------------------------------------------------------------
1160 -class cPersonBillItemsManagerPnl(gmListWidgets.cGenericListManagerPnl):
1161 """A list for managing a patient's bill items. 1162 1163 Does NOT act on/listen to the current patient. 1164 """
1165 - def __init__(self, *args, **kwargs):
1166 1167 try: 1168 self.__identity = kwargs['identity'] 1169 del kwargs['identity'] 1170 except KeyError: 1171 self.__identity = None 1172 1173 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs) 1174 1175 self.refresh_callback = self.refresh 1176 self.new_callback = self._add_item 1177 self.edit_callback = self._edit_item 1178 self.delete_callback = self._del_item 1179 1180 self.__show_non_invoiced_only = True 1181 1182 self.__init_ui() 1183 self.refresh()
1184 #-------------------------------------------------------- 1185 # external API 1186 #--------------------------------------------------------
1187 - def refresh(self, *args, **kwargs):
1188 if self.__identity is None: 1189 self._LCTRL_items.set_string_items() 1190 return 1191 1192 b_items = gmBilling.get_bill_items(pk_patient = self.__identity.ID, non_invoiced_only = self.__show_non_invoiced_only) 1193 items = [ [ 1194 gmDateTime.pydt_strftime(b['date_to_bill'], '%Y %b %d', accuracy = gmDateTime.acc_days), 1195 b['unit_count'], 1196 '%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], '', ' - %s')), 1197 b['currency'], 1198 b['total_amount'], 1199 '%s (%s%%)' % ( 1200 b['vat'], 1201 b['vat_multiplier'] * 100 1202 ), 1203 '%s (%s)' % (b['catalog_short'], b['catalog_version']), 1204 '%s %s %s %s %s' % ( 1205 b['unit_count'], 1206 gmTools.u_multiply, 1207 b['net_amount_per_unit'], 1208 gmTools.u_multiply, 1209 b['amount_multiplier'] 1210 ), 1211 gmTools.coalesce(b['pk_bill'], gmTools.u_diameter), 1212 b['pk_encounter_to_bill'], 1213 b['pk_bill_item'] 1214 ] for b in b_items ] 1215 1216 self._LCTRL_items.set_string_items(items = items) 1217 self._LCTRL_items.set_column_widths() 1218 self._LCTRL_items.set_data(data = b_items)
1219 #-------------------------------------------------------- 1220 # internal helpers 1221 #--------------------------------------------------------
1222 - def __init_ui(self):
1223 self._LCTRL_items.set_columns(columns = [ 1224 _('Charge date'), 1225 _('Count'), 1226 _('Description'), 1227 _('$__replace_by_your_currency_symbol')[:-len('__replace_by_your_currency_symbol')], 1228 _('Value'), 1229 _('VAT'), 1230 _('Catalog'), 1231 _('Count %s Value %s Factor') % (gmTools.u_multiply, gmTools.u_multiply), 1232 _('Invoice'), 1233 _('Encounter'), 1234 '#' 1235 ]) 1236 self._LCTRL_items.item_tooltip_callback = self._get_item_tooltip 1237 # self.left_extra_button = ( 1238 # _('Select pending'), 1239 # _('Select non-invoiced (pending) items.'), 1240 # self._select_pending_items 1241 # ) 1242 self.left_extra_button = ( 1243 _('Invoice selected items'), 1244 _('Create invoice from selected items.'), 1245 self._invoice_selected_items 1246 ) 1247 self.middle_extra_button = ( 1248 _('Bills'), 1249 _('Browse bills of this patient.'), 1250 self._browse_bills 1251 ) 1252 self.right_extra_button = ( 1253 _('Billables'), 1254 _('Browse list of billables.'), 1255 self._browse_billables 1256 )
1257 #--------------------------------------------------------
1258 - def _add_item(self):
1259 return edit_bill_item(parent = self, bill_item = None, single_entry = False)
1260 #--------------------------------------------------------
1261 - def _edit_item(self, bill_item):
1262 return edit_bill_item(parent = self, bill_item = bill_item, single_entry = True)
1263 #--------------------------------------------------------
1264 - def _del_item(self, item):
1265 if item['pk_bill'] is not None: 1266 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete already invoiced bill items.'), beep = True) 1267 return False 1268 go_ahead = gmGuiHelpers.gm_show_question ( 1269 _( 'Do you really want to delete this\n' 1270 'bill item from the patient ?'), 1271 _('Deleting bill item') 1272 ) 1273 if not go_ahead: 1274 return False 1275 gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item']) 1276 return True
1277 #--------------------------------------------------------
1278 - def _get_item_tooltip(self, item):
1279 if item is None: 1280 return None 1281 return item.format()
1282 #--------------------------------------------------------
1283 - def _select_pending_items(self, item):
1284 pass
1285 #--------------------------------------------------------
1286 - def _invoice_selected_items(self, item):
1287 bill_items = self._LCTRL_items.get_selected_item_data() 1288 bill = create_bill_from_items(bill_items) 1289 if bill is None: 1290 return 1291 if bill['pk_receiver_address'] is None: 1292 gmGuiHelpers.gm_show_error ( 1293 aMessage = _( 1294 'Cannot create invoice.\n' 1295 '\n' 1296 'No receiver address selected.' 1297 ), 1298 aTitle = _('Creating invoice') 1299 ) 1300 return 1301 if bill['close_date'] is None: 1302 bill['close_date'] = gmDateTime.pydt_now_here() 1303 bill.save() 1304 create_invoice_from_bill(parent = self, bill = bill, print_it = True, keep_a_copy = True)
1305 #--------------------------------------------------------
1306 - def _browse_billables(self, item):
1307 manage_billables(parent = self) 1308 return False
1309 #--------------------------------------------------------
1310 - def _browse_bills(self, item):
1311 manage_bills(parent = self, patient = self.__identity)
1312 #-------------------------------------------------------- 1313 # properties 1314 #--------------------------------------------------------
1315 - def _get_identity(self):
1316 return self.__identity
1317
1318 - def _set_identity(self, identity):
1319 self.__identity = identity 1320 self.refresh()
1321 1322 identity = property(_get_identity, _set_identity) 1323 #--------------------------------------------------------
1325 return self.__show_non_invoiced_only
1326
1327 - def _set_show_non_invoiced_only(self, value):
1328 self.__show_non_invoiced_only = value 1329 self.refresh()
1330 1331 show_non_invoiced_only = property(_get_show_non_invoiced_only, _set_show_non_invoiced_only)
1332 1333 #------------------------------------------------------------ 1334 from Gnumed.wxGladeWidgets import wxgBillItemEAPnl 1335
1336 -class cBillItemEAPnl(wxgBillItemEAPnl.wxgBillItemEAPnl, gmEditArea.cGenericEditAreaMixin):
1337
1338 - def __init__(self, *args, **kwargs):
1339 1340 try: 1341 data = kwargs['bill_item'] 1342 del kwargs['bill_item'] 1343 except KeyError: 1344 data = None 1345 1346 wxgBillItemEAPnl.wxgBillItemEAPnl.__init__(self, *args, **kwargs) 1347 gmEditArea.cGenericEditAreaMixin.__init__(self) 1348 1349 self.mode = 'new' 1350 self.data = data 1351 if data is not None: 1352 self.mode = 'edit' 1353 1354 self.__init_ui()
1355 #----------------------------------------------------------------
1356 - def __init_ui(self):
1357 self._PRW_encounter.set_context(context = 'patient', val = gmPerson.gmCurrentPatient().ID) 1358 self._PRW_billable.add_callback_on_selection(self._on_billable_selected) 1359 self._PRW_billable.add_callback_on_modified(self._on_billable_modified)
1360 #---------------------------------------------------------------- 1361 # generic Edit Area mixin API 1362 #----------------------------------------------------------------
1363 - def _valid_for_save(self):
1364 1365 validity = True 1366 1367 if self._TCTRL_factor.GetValue().strip() == '': 1368 validity = False 1369 self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = False) 1370 self._TCTRL_factor.SetFocus() 1371 else: 1372 converted, factor = gmTools.input2decimal(self._TCTRL_factor.GetValue()) 1373 if not converted: 1374 validity = False 1375 self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = False) 1376 self._TCTRL_factor.SetFocus() 1377 else: 1378 self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = True) 1379 1380 if self._TCTRL_amount.GetValue().strip() == '': 1381 validity = False 1382 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False) 1383 self._TCTRL_amount.SetFocus() 1384 else: 1385 converted, factor = gmTools.input2decimal(self._TCTRL_amount.GetValue()) 1386 if not converted: 1387 validity = False 1388 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False) 1389 self._TCTRL_amount.SetFocus() 1390 else: 1391 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = True) 1392 1393 if self._TCTRL_count.GetValue().strip() == '': 1394 validity = False 1395 self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = False) 1396 self._TCTRL_count.SetFocus() 1397 else: 1398 converted, factor = gmTools.input2decimal(self._TCTRL_count.GetValue()) 1399 if not converted: 1400 validity = False 1401 self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = False) 1402 self._TCTRL_count.SetFocus() 1403 else: 1404 self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = True) 1405 1406 if self._PRW_date.is_valid_timestamp(empty_is_valid = True): 1407 self._PRW_date.display_as_valid(True) 1408 else: 1409 validity = False 1410 self._PRW_date.display_as_valid(False) 1411 self._PRW_date.SetFocus() 1412 1413 if self._PRW_encounter.GetData() is None: 1414 validity = False 1415 self._PRW_encounter.display_as_valid(False) 1416 self._PRW_encounter.SetFocus() 1417 else: 1418 self._PRW_encounter.display_as_valid(True) 1419 1420 if self._PRW_billable.GetData() is None: 1421 validity = False 1422 self._PRW_billable.display_as_valid(False) 1423 self._PRW_billable.SetFocus() 1424 else: 1425 self._PRW_billable.display_as_valid(True) 1426 1427 return validity
1428 #----------------------------------------------------------------
1429 - def _save_as_new(self):
1430 data = gmBilling.create_bill_item ( 1431 pk_encounter = self._PRW_encounter.GetData(), 1432 pk_billable = self._PRW_billable.GetData(), 1433 pk_staff = gmStaff.gmCurrentProvider()['pk_staff'] # should be settable ! 1434 ) 1435 data['raw_date_to_bill'] = self._PRW_date.GetData() 1436 converted, data['unit_count'] = gmTools.input2decimal(self._TCTRL_count.GetValue()) 1437 converted, data['net_amount_per_unit'] = gmTools.input2decimal(self._TCTRL_amount.GetValue()) 1438 converted, data['amount_multiplier'] = gmTools.input2decimal(self._TCTRL_factor.GetValue()) 1439 data['item_detail'] = self._TCTRL_comment.GetValue().strip() 1440 data.save() 1441 1442 self.data = data 1443 return True
1444 #----------------------------------------------------------------
1445 - def _save_as_update(self):
1446 self.data['pk_encounter_to_bill'] = self._PRW_encounter.GetData() 1447 self.data['raw_date_to_bill'] = self._PRW_date.GetData() 1448 converted, self.data['unit_count'] = gmTools.input2decimal(self._TCTRL_count.GetValue()) 1449 converted, self.data['net_amount_per_unit'] = gmTools.input2decimal(self._TCTRL_amount.GetValue()) 1450 converted, self.data['amount_multiplier'] = gmTools.input2decimal(self._TCTRL_factor.GetValue()) 1451 self.data['item_detail'] = self._TCTRL_comment.GetValue().strip() 1452 return self.data.save()
1453 #----------------------------------------------------------------
1454 - def _refresh_as_new(self):
1455 self._PRW_billable.SetText() 1456 self._PRW_encounter.set_from_instance(gmPerson.gmCurrentPatient().emr.active_encounter) 1457 self._PRW_date.SetData() 1458 self._TCTRL_count.SetValue('1') 1459 self._TCTRL_amount.SetValue('') 1460 self._LBL_currency.SetLabel(gmTools.u_euro) 1461 self._TCTRL_factor.SetValue('1') 1462 self._TCTRL_comment.SetValue('') 1463 1464 self._PRW_billable.Enable() 1465 self._PRW_billable.SetFocus()
1466 #----------------------------------------------------------------
1468 self._PRW_billable.SetText() 1469 self._TCTRL_count.SetValue('1') 1470 self._TCTRL_amount.SetValue('') 1471 self._TCTRL_comment.SetValue('') 1472 1473 self._PRW_billable.Enable() 1474 self._PRW_billable.SetFocus()
1475 #----------------------------------------------------------------
1476 - def _refresh_from_existing(self):
1477 self._PRW_billable.set_from_pk(self.data['pk_billable']) 1478 self._PRW_encounter.SetData(self.data['pk_encounter_to_bill']) 1479 self._PRW_date.SetData(data = self.data['raw_date_to_bill']) 1480 self._TCTRL_count.SetValue('%s' % self.data['unit_count']) 1481 self._TCTRL_amount.SetValue('%s' % self.data['net_amount_per_unit']) 1482 self._LBL_currency.SetLabel(self.data['currency']) 1483 self._TCTRL_factor.SetValue('%s' % self.data['amount_multiplier']) 1484 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['item_detail'], '')) 1485 1486 self._PRW_billable.Disable() 1487 self._PRW_date.SetFocus()
1488 #----------------------------------------------------------------
1489 - def _on_billable_selected(self, item):
1490 if item is None: 1491 return 1492 if self._TCTRL_amount.GetValue().strip() != '': 1493 return 1494 val = '%s' % self._PRW_billable.GetData(as_instance = True)['raw_amount'] 1495 wx.CallAfter(self._TCTRL_amount.SetValue, val)
1496 #----------------------------------------------------------------
1497 - def _on_billable_modified(self):
1498 if self._PRW_billable.GetData() is None: 1499 wx.CallAfter(self._TCTRL_amount.SetValue, '')
1500 1501 #============================================================ 1502 # a plugin for billing 1503 #------------------------------------------------------------ 1504 from Gnumed.wxGladeWidgets import wxgBillingPluginPnl 1505
1506 -class cBillingPluginPnl(wxgBillingPluginPnl.wxgBillingPluginPnl, gmRegetMixin.cRegetOnPaintMixin):
1507 - def __init__(self, *args, **kwargs):
1508 1509 wxgBillingPluginPnl.wxgBillingPluginPnl.__init__(self, *args, **kwargs) 1510 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 1511 self.__register_interests()
1512 #-----------------------------------------------------
1513 - def __reset_ui(self):
1514 self._PNL_bill_items.identity = None 1515 self._CHBOX_show_non_invoiced_only.SetValue(1) 1516 self._PRW_billable.SetText('', None) 1517 self._TCTRL_factor.SetValue('1.0') 1518 self._TCTRL_factor.Disable() 1519 self._TCTRL_details.SetValue('') 1520 self._TCTRL_details.Disable()
1521 #----------------------------------------------------- 1522 # event handling 1523 #-----------------------------------------------------
1524 - def __register_interests(self):
1525 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection) 1526 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection) 1527 1528 gmDispatcher.connect(signal = 'bill.bill_item_mod_db', receiver = self._on_bill_item_modified) 1529 1530 self._PRW_billable.add_callback_on_selection(self._on_billable_selected_in_prw)
1531 #-----------------------------------------------------
1533 self.__reset_ui()
1534 #-----------------------------------------------------
1535 - def _on_post_patient_selection(self):
1536 self._schedule_data_reget()
1537 #-----------------------------------------------------
1538 - def _on_bill_item_modified(self):
1539 self._schedule_data_reget()
1540 #-----------------------------------------------------
1542 self._PNL_bill_items.show_non_invoiced_only = self._CHBOX_show_non_invoiced_only.GetValue()
1543 #--------------------------------------------------------
1544 - def _on_insert_bill_item_button_pressed(self, event):
1545 if self._PRW_billable.GetData() is None: 1546 gmGuiHelpers.gm_show_warning ( 1547 _('No billable item selected.\n\nCannot insert bill item.'), 1548 _('Inserting bill item') 1549 ) 1550 return False 1551 val = self._TCTRL_factor.GetValue().strip() 1552 if val == '': 1553 factor = 1.0 1554 else: 1555 converted, factor = gmTools.input2decimal(val) 1556 if not converted: 1557 gmGuiHelpers.gm_show_warning ( 1558 _('"Factor" must be a number\n\nCannot insert bill item.'), 1559 _('Inserting bill item') 1560 ) 1561 return False 1562 bill_item = gmBilling.create_bill_item ( 1563 pk_encounter = gmPerson.gmCurrentPatient().emr.active_encounter['pk_encounter'], 1564 pk_billable = self._PRW_billable.GetData(), 1565 pk_staff = gmStaff.gmCurrentProvider()['pk_staff'] 1566 ) 1567 bill_item['amount_multiplier'] = factor 1568 bill_item['item_detail'] = self._TCTRL_details.GetValue() 1569 bill_item.save() 1570 1571 self._TCTRL_details.SetValue('') 1572 1573 return True
1574 #--------------------------------------------------------
1575 - def _on_billable_selected_in_prw(self, billable):
1576 if billable is None: 1577 self._TCTRL_factor.Disable() 1578 self._TCTRL_details.Disable() 1579 self._BTN_insert_item.Disable() 1580 else: 1581 self._TCTRL_factor.Enable() 1582 self._TCTRL_details.Enable() 1583 self._BTN_insert_item.Enable()
1584 #----------------------------------------------------- 1585 # reget-on-paint mixin API 1586 #-----------------------------------------------------
1587 - def _populate_with_data(self):
1588 self._PNL_bill_items.identity = gmPerson.gmCurrentPatient() 1589 return True
1590 1591 #============================================================ 1592 # main 1593 #------------------------------------------------------------ 1594 if __name__ == '__main__': 1595 1596 if len(sys.argv) < 2: 1597 sys.exit() 1598 1599 if sys.argv[1] != 'test': 1600 sys.exit() 1601 1602 from Gnumed.pycommon import gmI18N 1603 gmI18N.activate_locale() 1604 gmI18N.install_domain(domain = 'gnumed') 1605 1606 #---------------------------------------- 1607 app = wx.PyWidgetTester(size = (600, 600)) 1608 #app.SetWidget(cXxxPhraseWheel, -1) 1609 app.MainLoop() 1610