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 ) 655 bill['pk_doc'] = doc['pk_doc'] 656 bill.save() 657 658 if not print_it: 659 return True 660 661 # print template 662 _cfg = gmCfg2.gmCfgData() 663 printed = gmPrinting.print_files(filenames = [pdf_name], jobtype = 'invoice', verbose = _cfg.get(option = 'debug')) 664 if not printed: 665 gmGuiHelpers.gm_show_error ( 666 aMessage = _('Error printing the invoice.'), 667 aTitle = _('Printing invoice') 668 ) 669 return True 670 671 return True
672 673 #----------------------------------------------------------------
674 -def delete_bill(parent=None, bill=None):
675 676 if parent is None: 677 parent = wx.GetApp().GetTopWindow() 678 679 dlg = gmGuiHelpers.c3ButtonQuestionDlg ( 680 parent, -1, 681 caption = _('Deleting bill'), 682 question = _( 683 'When deleting the bill [%s]\n' 684 'do you want to keep its items (effectively \"unbilling\" them)\n' 685 'or do you want to also delete the bill items from the patient ?\n' 686 ) % bill['invoice_id'], 687 button_defs = [ 688 {'label': _('Delete + keep'), 'tooltip': _('Delete the bill but keep ("unbill") its items.'), 'default': True}, 689 {'label': _('Delete all'), 'tooltip': _('Delete both the bill and its items from the patient.')} 690 ], 691 show_checkbox = True, 692 checkbox_msg = _('Also remove invoice PDF'), 693 checkbox_tooltip = _('Also remove the invoice PDF from the document archive (because it will not correspond to the bill anymore).') 694 ) 695 button_pressed = dlg.ShowModal() 696 delete_invoice = dlg.checkbox_is_checked() 697 dlg.DestroyLater() 698 699 if button_pressed == wx.ID_CANCEL: 700 return False 701 702 delete_items = (button_pressed == wx.ID_NO) 703 704 if delete_invoice: 705 if bill['pk_doc'] is not None: 706 gmDocuments.delete_document ( 707 document_id = bill['pk_doc'], 708 encounter_id = gmPerson.cPatient(aPK_obj = bill['pk_patient']).emr.active_encounter['pk_encounter'] 709 ) 710 711 items = bill['pk_bill_items'] 712 success = gmBilling.delete_bill(pk_bill = bill['pk_bill']) 713 if delete_items: 714 for item in items: 715 gmBilling.delete_bill_item(pk_bill_item = item) 716 717 return success
718 719 #----------------------------------------------------------------
720 -def remove_items_from_bill(parent=None, bill=None):
721 722 if bill is None: 723 return False 724 725 list_data = bill.bill_items 726 if len(list_data) == 0: 727 return False 728 729 if parent is None: 730 parent = wx.GetApp().GetTopWindow() 731 732 list_items = [ [ 733 gmDateTime.pydt_strftime(b['date_to_bill'], '%Y %b %d', accuracy = gmDateTime.acc_days), 734 b['unit_count'], 735 '%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], '', ' - %s')), 736 '%(curr)s %(total_val)s (%(count)s %(x)s %(unit_val)s%(x)s%(val_multiplier)s)' % { 737 'curr': b['currency'], 738 'total_val': b['total_amount'], 739 'count': b['unit_count'], 740 'x': gmTools.u_multiply, 741 'unit_val': b['net_amount_per_unit'], 742 'val_multiplier': b['amount_multiplier'] 743 }, 744 '%(curr)s%(vat)s (%(perc_vat)s%%)' % { 745 'vat': b['vat'], 746 'curr': b['currency'], 747 'perc_vat': b['vat_multiplier'] * 100 748 }, 749 '%s (%s)' % (b['catalog_short'], b['catalog_version']), 750 b['pk_bill_item'] 751 ] for b in list_data ] 752 753 msg = _('Select the items you want to remove from bill [%s]:\n') % bill['invoice_id'] 754 items2remove = gmListWidgets.get_choices_from_list ( 755 parent = parent, 756 msg = msg, 757 caption = _('Removing items from bill'), 758 columns = [_('Date'), _('Count'), _('Description'), _('Value'), _('VAT'), _('Catalog'), '#'], 759 single_selection = False, 760 choices = list_items, 761 data = list_data 762 ) 763 764 if items2remove is None: 765 return False 766 767 if len(items2remove) == len(list_items): 768 gmGuiHelpers.gm_show_info ( 769 title = _('Removing items from bill'), 770 info = _( 771 'Cannot remove all items from a bill because\n' 772 'GNUmed does not support empty bills.\n' 773 '\n' 774 'You must delete the bill itself if you want to\n' 775 'remove all items (at which point you can opt to\n' 776 'keep the items and only delete the bill).' 777 ) 778 ) 779 return False 780 781 dlg = gmGuiHelpers.c3ButtonQuestionDlg ( 782 parent, -1, 783 caption = _('Removing items from bill'), 784 question = _( 785 '%s items selected from bill [%s]\n' 786 '\n' 787 'Do you want to only remove the selected items\n' 788 'from the bill ("unbill" them) or do you want\n' 789 'to delete them entirely from the patient ?\n' 790 '\n' 791 'Note that neither action is reversible.' 792 ) % ( 793 len(items2remove), 794 bill['invoice_id'] 795 ), 796 button_defs = [ 797 {'label': _('"Unbill"'), 'tooltip': _('Only "unbill" items (remove from bill but do not delete from patient).'), 'default': True}, 798 {'label': _('Delete'), 'tooltip': _('Completely delete items from the patient.')} 799 ], 800 show_checkbox = True, 801 checkbox_msg = _('Also remove invoice PDF'), 802 checkbox_tooltip = _('Also remove the invoice PDF from the document archive (because it will not correspond to the bill anymore).') 803 ) 804 button_pressed = dlg.ShowModal() 805 delete_invoice = dlg.checkbox_is_checked() 806 dlg.DestroyLater() 807 808 if button_pressed == wx.ID_CANCEL: 809 return False 810 811 # remember this because unlinking/deleting the items 812 # will remove the patient PK from the bill 813 pk_patient = bill['pk_patient'] 814 815 for item in items2remove: 816 item['pk_bill'] = None 817 item.save() 818 if button_pressed == wx.ID_NO: 819 gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item']) 820 821 if delete_invoice: 822 if bill['pk_doc'] is not None: 823 gmDocuments.delete_document ( 824 document_id = bill['pk_doc'], 825 encounter_id = gmPerson.cPatient(aPK_obj = pk_patient).emr.active_encounter['pk_encounter'] 826 ) 827 828 # delete bill, too, if empty 829 if len(bill.bill_items) == 0: 830 gmBilling.delete_bill(pk_bill = bill['pk_bill']) 831 832 return True
833 834 #----------------------------------------------------------------
835 -def manage_bills(parent=None, patient=None):
836 837 if parent is None: 838 parent = wx.GetApp().GetTopWindow() 839 840 #------------------------------------------------------------ 841 def show_pdf(bill): 842 if bill is None: 843 return False 844 845 # find invoice 846 invoice = bill.invoice 847 if invoice is not None: 848 success, msg = invoice.parts[-1].display_via_mime() 849 if not success: 850 gmGuiHelpers.gm_show_error(aMessage = msg, aTitle = _('Displaying invoice')) 851 return False 852 853 # create it ? 854 create_it = gmGuiHelpers.gm_show_question ( 855 title = _('Displaying invoice'), 856 question = _( 857 'Cannot find an existing\n' 858 'invoice PDF for this bill.\n' 859 '\n' 860 'Do you want to create one ?' 861 ), 862 ) 863 if not create_it: 864 return False 865 866 # prepare invoicing 867 if not bill.set_missing_address_from_default(): 868 gmGuiHelpers.gm_show_warning ( 869 aTitle = _('Creating invoice'), 870 aMessage = _( 871 'There is no pre-configured billing address.\n' 872 '\n' 873 'Select the address you want to send the bill to.' 874 ) 875 ) 876 edit_bill(parent = parent, bill = bill, single_entry = True) 877 if bill['pk_receiver_address'] is None: 878 return False 879 if bill['close_date'] is None: 880 bill['close_date'] = gmDateTime.pydt_now_here() 881 bill.save() 882 883 return create_invoice_from_bill(parent = parent, bill = bill, print_it = True, keep_a_copy = True)
884 #------------------------------------------------------------ 885 def edit(bill): 886 return edit_bill(parent = parent, bill = bill, single_entry = True) 887 #------------------------------------------------------------ 888 def delete(bill): 889 return delete_bill(parent = parent, bill = bill) 890 #------------------------------------------------------------ 891 def remove_items(bill): 892 return remove_items_from_bill(parent = parent, bill = bill) 893 #------------------------------------------------------------ 894 def get_tooltip(item): 895 if item is None: 896 return None 897 return item.format() 898 #------------------------------------------------------------ 899 def refresh(lctrl): 900 if patient is None: 901 bills = gmBilling.get_bills() 902 else: 903 bills = gmBilling.get_bills(pk_patient = patient.ID) 904 items = [] 905 for b in bills: 906 if b['close_date'] is None: 907 close_date = _('<open>') 908 else: 909 close_date = gmDateTime.pydt_strftime(b['close_date'], '%Y %b %d') 910 if b['total_amount'] is None: 911 amount = _('no items on bill') 912 else: 913 amount = gmTools.bool2subst ( 914 b['apply_vat'], 915 _('%(currency)s%(total_amount_with_vat)s (with %(percent_vat)s%% VAT)') % b, 916 '%(currency)s%(total_amount)s' % b, 917 _('without VAT: %(currency)s%(total_amount)s / with %(percent_vat)s%% VAT: %(currency)s%(total_amount_with_vat)s') % b 918 ) 919 items.append ([ 920 close_date, 921 b['invoice_id'], 922 amount, 923 gmTools.coalesce(b['comment'], '') 924 ]) 925 lctrl.set_string_items(items) 926 lctrl.set_data(bills) 927 #------------------------------------------------------------ 928 return gmListWidgets.get_choices_from_list ( 929 parent = parent, 930 caption = _('Showing bills.'), 931 columns = [_('Close date'), _('Invoice ID'), _('Value'), _('Comment')], 932 single_selection = True, 933 edit_callback = edit, 934 delete_callback = delete, 935 refresh_callback = refresh, 936 middle_extra_button = ( 937 'PDF', 938 _('Create if necessary, and show the corresponding invoice PDF'), 939 show_pdf 940 ), 941 right_extra_button = ( 942 _('Unbill'), 943 _('Select and remove items from a bill.'), 944 remove_items 945 ), 946 list_tooltip_callback = get_tooltip 947 ) 948 949 #---------------------------------------------------------------- 950 from Gnumed.wxGladeWidgets import wxgBillEAPnl 951
952 -class cBillEAPnl(wxgBillEAPnl.wxgBillEAPnl, gmEditArea.cGenericEditAreaMixin):
953
954 - def __init__(self, *args, **kwargs):
955 956 try: 957 data = kwargs['bill'] 958 del kwargs['bill'] 959 except KeyError: 960 data = None 961 962 wxgBillEAPnl.wxgBillEAPnl.__init__(self, *args, **kwargs) 963 gmEditArea.cGenericEditAreaMixin.__init__(self) 964 965 self.mode = 'new' 966 self.data = data 967 if data is not None: 968 self.mode = 'edit' 969 970 self._3state2bool = { 971 wx.CHK_UNCHECKED: False, 972 wx.CHK_CHECKED: True, 973 wx.CHK_UNDETERMINED: None 974 } 975 self.bool_to_3state = { 976 False: wx.CHK_UNCHECKED, 977 True: wx.CHK_CHECKED, 978 None: wx.CHK_UNDETERMINED 979 }
980 981 # self.__init_ui() 982 #---------------------------------------------------------------- 983 # def __init_ui(self): 984 #---------------------------------------------------------------- 985 # generic Edit Area mixin API 986 #----------------------------------------------------------------
987 - def _valid_for_save(self):
988 validity = True 989 990 # flag but do not count as wrong 991 if not self._PRW_close_date.is_valid_timestamp(empty_is_valid = False): 992 self._PRW_close_date.SetFocus() 993 994 # flag but do not count as wrong 995 if self._CHBOX_vat_applies.ThreeStateValue == wx.CHK_UNDETERMINED: 996 self._CHBOX_vat_applies.SetFocus() 997 self._CHBOX_vat_applies.SetBackgroundColour('yellow') 998 999 return validity
1000 #----------------------------------------------------------------
1001 - def _save_as_new(self):
1002 # not intended to be used 1003 return False
1004 #----------------------------------------------------------------
1005 - def _save_as_update(self):
1006 self.data['close_date'] = self._PRW_close_date.GetData() 1007 self.data['apply_vat'] = self._3state2bool[self._CHBOX_vat_applies.ThreeStateValue] 1008 self.data['comment'] = self._TCTRL_comment.GetValue() 1009 self.data.save() 1010 return True
1011 #----------------------------------------------------------------
1012 - def _refresh_as_new(self):
1013 pass # not used
1014 #----------------------------------------------------------------
1016 self._refresh_as_new()
1017 #----------------------------------------------------------------
1018 - def _refresh_from_existing(self):
1019 self._TCTRL_invoice_id.SetValue(self.data['invoice_id']) 1020 self._PRW_close_date.SetText(data = self.data['close_date']) 1021 1022 self.data.set_missing_address_from_default() 1023 if self.data['pk_receiver_address'] is None: 1024 self._TCTRL_address.SetValue('') 1025 else: 1026 adr = self.data.address 1027 self._TCTRL_address.SetValue(adr.format(single_line = True, show_type = False)) 1028 1029 self._TCTRL_value.SetValue('%(currency)s%(total_amount)s' % self.data) 1030 self._CHBOX_vat_applies.ThreeStateValue = self.bool_to_3state[self.data['apply_vat']] 1031 self._CHBOX_vat_applies.SetLabel(_('&VAT applies (%s%%)') % self.data['percent_vat']) 1032 if self.data['apply_vat'] is True: 1033 tmp = '%s %%(currency)s%%(total_vat)s %s %s %%(currency)s%%(total_amount_with_vat)s' % ( 1034 gmTools.u_corresponds_to, 1035 gmTools.u_arrow2right, 1036 gmTools.u_sum, 1037 ) 1038 self._TCTRL_value_with_vat.SetValue(tmp % self.data) 1039 elif self.data['apply_vat'] is None: 1040 self._TCTRL_value_with_vat.SetValue('?') 1041 else: 1042 self._TCTRL_value_with_vat.SetValue('') 1043 1044 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], '')) 1045 1046 self._PRW_close_date.SetFocus()
1047 #---------------------------------------------------------------- 1048 # event handling 1049 #----------------------------------------------------------------
1050 - def _on_vat_applies_box_checked(self, event):
1051 if self._CHBOX_vat_applies.ThreeStateValue == wx.CHK_CHECKED: 1052 tmp = '%s %%(currency)s%%(total_vat)s %s %s %%(currency)s%%(total_amount_with_vat)s' % ( 1053 gmTools.u_corresponds_to, 1054 gmTools.u_arrow2right, 1055 gmTools.u_sum, 1056 ) 1057 self._TCTRL_value_with_vat.SetValue(tmp % self.data) 1058 return 1059 if self._CHBOX_vat_applies.ThreeStateValue == wx.CHK_UNDETERMINED: 1060 self._TCTRL_value_with_vat.SetValue('?') 1061 return 1062 self._TCTRL_value_with_vat.SetValue('')
1063 #----------------------------------------------------------------
1064 - def _on_select_address_button_pressed(self, event):
1065 adr = gmPersonContactWidgets.select_address ( 1066 missing = _('billing'), 1067 person = gmPerson.cPerson(aPK_obj = self.data['pk_patient']) 1068 ) 1069 if adr is None: 1070 gmGuiHelpers.gm_show_info ( 1071 aTitle = _('Selecting address'), 1072 aMessage = _('GNUmed does not know any addresses for this patient.') 1073 ) 1074 return 1075 self.data['pk_receiver_address'] = adr['pk_lnk_person_org_address'] 1076 self.data.save() 1077 self._TCTRL_address.SetValue(adr.format(single_line = True, show_type = False))
1078 1079 #================================================================ 1080 # per-patient bill items related widgets 1081 #----------------------------------------------------------------
1082 -def edit_bill_item(parent=None, bill_item=None, single_entry=False):
1083 1084 if bill_item is not None: 1085 if bill_item.is_in_use: 1086 gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit already invoiced bill item.'), beep = True) 1087 return False 1088 1089 ea = cBillItemEAPnl(parent, -1) 1090 ea.data = bill_item 1091 ea.mode = gmTools.coalesce(bill_item, 'new', 'edit') 1092 dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea, single_entry = single_entry) 1093 dlg.SetTitle(gmTools.coalesce(bill_item, _('Adding new bill item'), _('Editing bill item'))) 1094 if dlg.ShowModal() == wx.ID_OK: 1095 dlg.DestroyLater() 1096 return True 1097 dlg.DestroyLater() 1098 return False
1099 #----------------------------------------------------------------
1100 -def manage_bill_items(parent=None, pk_patient=None):
1101 1102 if parent is None: 1103 parent = wx.GetApp().GetTopWindow() 1104 #------------------------------------------------------------ 1105 def edit(item=None): 1106 return edit_bill_item(parent = parent, bill_item = item, single_entry = (item is not None))
1107 #------------------------------------------------------------ 1108 def delete(item): 1109 if item.is_in_use is not None: 1110 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete already invoiced bill items.'), beep = True) 1111 return False 1112 gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item']) 1113 return True 1114 #------------------------------------------------------------ 1115 def get_tooltip(item): 1116 if item is None: 1117 return None 1118 return item.format() 1119 #------------------------------------------------------------ 1120 def refresh(lctrl): 1121 b_items = gmBilling.get_bill_items(pk_patient = pk_patient) 1122 items = [ [ 1123 gmDateTime.pydt_strftime(b['date_to_bill'], '%Y %b %d', accuracy = gmDateTime.acc_days), 1124 b['unit_count'], 1125 '%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], '', ' - %s')), 1126 b['currency'], 1127 '%s (%s %s %s%s%s)' % ( 1128 b['total_amount'], 1129 b['unit_count'], 1130 gmTools.u_multiply, 1131 b['net_amount_per_unit'], 1132 gmTools.u_multiply, 1133 b['amount_multiplier'] 1134 ), 1135 '%s (%s%%)' % ( 1136 b['vat'], 1137 b['vat_multiplier'] * 100 1138 ), 1139 '%s (%s)' % (b['catalog_short'], b['catalog_version']), 1140 b['pk_bill_item'] 1141 ] for b in b_items ] 1142 lctrl.set_string_items(items) 1143 lctrl.set_data(b_items) 1144 #------------------------------------------------------------ 1145 gmListWidgets.get_choices_from_list ( 1146 parent = parent, 1147 #msg = msg, 1148 caption = _('Showing bill items.'), 1149 columns = [_('Date'), _('Count'), _('Description'), _('$__replace_by_your_currency_symbol')[:-len('__replace_by_your_currency_symbol')], _('Value'), _('VAT'), _('Catalog'), '#'], 1150 single_selection = True, 1151 new_callback = edit, 1152 edit_callback = edit, 1153 delete_callback = delete, 1154 refresh_callback = refresh, 1155 list_tooltip_callback = get_tooltip 1156 ) 1157 1158 #------------------------------------------------------------
1159 -class cPersonBillItemsManagerPnl(gmListWidgets.cGenericListManagerPnl):
1160 """A list for managing a patient's bill items. 1161 1162 Does NOT act on/listen to the current patient. 1163 """
1164 - def __init__(self, *args, **kwargs):
1165 1166 try: 1167 self.__identity = kwargs['identity'] 1168 del kwargs['identity'] 1169 except KeyError: 1170 self.__identity = None 1171 1172 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs) 1173 1174 self.refresh_callback = self.refresh 1175 self.new_callback = self._add_item 1176 self.edit_callback = self._edit_item 1177 self.delete_callback = self._del_item 1178 1179 self.__show_non_invoiced_only = True 1180 1181 self.__init_ui() 1182 self.refresh()
1183 #-------------------------------------------------------- 1184 # external API 1185 #--------------------------------------------------------
1186 - def refresh(self, *args, **kwargs):
1187 if self.__identity is None: 1188 self._LCTRL_items.set_string_items() 1189 return 1190 1191 b_items = gmBilling.get_bill_items(pk_patient = self.__identity.ID, non_invoiced_only = self.__show_non_invoiced_only) 1192 items = [ [ 1193 gmDateTime.pydt_strftime(b['date_to_bill'], '%Y %b %d', accuracy = gmDateTime.acc_days), 1194 b['unit_count'], 1195 '%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], '', ' - %s')), 1196 b['currency'], 1197 b['total_amount'], 1198 '%s (%s%%)' % ( 1199 b['vat'], 1200 b['vat_multiplier'] * 100 1201 ), 1202 '%s (%s)' % (b['catalog_short'], b['catalog_version']), 1203 '%s %s %s %s %s' % ( 1204 b['unit_count'], 1205 gmTools.u_multiply, 1206 b['net_amount_per_unit'], 1207 gmTools.u_multiply, 1208 b['amount_multiplier'] 1209 ), 1210 gmTools.coalesce(b['pk_bill'], gmTools.u_diameter), 1211 b['pk_encounter_to_bill'], 1212 b['pk_bill_item'] 1213 ] for b in b_items ] 1214 1215 self._LCTRL_items.set_string_items(items = items) 1216 self._LCTRL_items.set_column_widths() 1217 self._LCTRL_items.set_data(data = b_items)
1218 #-------------------------------------------------------- 1219 # internal helpers 1220 #--------------------------------------------------------
1221 - def __init_ui(self):
1222 self._LCTRL_items.set_columns(columns = [ 1223 _('Charge date'), 1224 _('Count'), 1225 _('Description'), 1226 _('$__replace_by_your_currency_symbol')[:-len('__replace_by_your_currency_symbol')], 1227 _('Value'), 1228 _('VAT'), 1229 _('Catalog'), 1230 _('Count %s Value %s Factor') % (gmTools.u_multiply, gmTools.u_multiply), 1231 _('Invoice'), 1232 _('Encounter'), 1233 '#' 1234 ]) 1235 self._LCTRL_items.item_tooltip_callback = self._get_item_tooltip 1236 # self.left_extra_button = ( 1237 # _('Select pending'), 1238 # _('Select non-invoiced (pending) items.'), 1239 # self._select_pending_items 1240 # ) 1241 self.left_extra_button = ( 1242 _('Invoice selected items'), 1243 _('Create invoice from selected items.'), 1244 self._invoice_selected_items 1245 ) 1246 self.middle_extra_button = ( 1247 _('Bills'), 1248 _('Browse bills of this patient.'), 1249 self._browse_bills 1250 ) 1251 self.right_extra_button = ( 1252 _('Billables'), 1253 _('Browse list of billables.'), 1254 self._browse_billables 1255 )
1256 #--------------------------------------------------------
1257 - def _add_item(self):
1258 return edit_bill_item(parent = self, bill_item = None, single_entry = False)
1259 #--------------------------------------------------------
1260 - def _edit_item(self, bill_item):
1261 return edit_bill_item(parent = self, bill_item = bill_item, single_entry = True)
1262 #--------------------------------------------------------
1263 - def _del_item(self, item):
1264 if item['pk_bill'] is not None: 1265 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete already invoiced bill items.'), beep = True) 1266 return False 1267 go_ahead = gmGuiHelpers.gm_show_question ( 1268 _( 'Do you really want to delete this\n' 1269 'bill item from the patient ?'), 1270 _('Deleting bill item') 1271 ) 1272 if not go_ahead: 1273 return False 1274 gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item']) 1275 return True
1276 #--------------------------------------------------------
1277 - def _get_item_tooltip(self, item):
1278 if item is None: 1279 return None 1280 return item.format()
1281 #--------------------------------------------------------
1282 - def _select_pending_items(self, item):
1283 pass
1284 #--------------------------------------------------------
1285 - def _invoice_selected_items(self, item):
1286 bill_items = self._LCTRL_items.get_selected_item_data() 1287 bill = create_bill_from_items(bill_items) 1288 if bill is None: 1289 return 1290 if bill['pk_receiver_address'] is None: 1291 gmGuiHelpers.gm_show_error ( 1292 aMessage = _( 1293 'Cannot create invoice.\n' 1294 '\n' 1295 'No receiver address selected.' 1296 ), 1297 aTitle = _('Creating invoice') 1298 ) 1299 return 1300 if bill['close_date'] is None: 1301 bill['close_date'] = gmDateTime.pydt_now_here() 1302 bill.save() 1303 create_invoice_from_bill(parent = self, bill = bill, print_it = True, keep_a_copy = True)
1304 #--------------------------------------------------------
1305 - def _browse_billables(self, item):
1306 manage_billables(parent = self) 1307 return False
1308 #--------------------------------------------------------
1309 - def _browse_bills(self, item):
1310 manage_bills(parent = self, patient = self.__identity)
1311 #-------------------------------------------------------- 1312 # properties 1313 #--------------------------------------------------------
1314 - def _get_identity(self):
1315 return self.__identity
1316
1317 - def _set_identity(self, identity):
1318 self.__identity = identity 1319 self.refresh()
1320 1321 identity = property(_get_identity, _set_identity) 1322 #--------------------------------------------------------
1324 return self.__show_non_invoiced_only
1325
1326 - def _set_show_non_invoiced_only(self, value):
1327 self.__show_non_invoiced_only = value 1328 self.refresh()
1329 1330 show_non_invoiced_only = property(_get_show_non_invoiced_only, _set_show_non_invoiced_only)
1331 1332 #------------------------------------------------------------ 1333 from Gnumed.wxGladeWidgets import wxgBillItemEAPnl 1334
1335 -class cBillItemEAPnl(wxgBillItemEAPnl.wxgBillItemEAPnl, gmEditArea.cGenericEditAreaMixin):
1336
1337 - def __init__(self, *args, **kwargs):
1338 1339 try: 1340 data = kwargs['bill_item'] 1341 del kwargs['bill_item'] 1342 except KeyError: 1343 data = None 1344 1345 wxgBillItemEAPnl.wxgBillItemEAPnl.__init__(self, *args, **kwargs) 1346 gmEditArea.cGenericEditAreaMixin.__init__(self) 1347 1348 self.mode = 'new' 1349 self.data = data 1350 if data is not None: 1351 self.mode = 'edit' 1352 1353 self.__init_ui()
1354 #----------------------------------------------------------------
1355 - def __init_ui(self):
1356 self._PRW_encounter.set_context(context = 'patient', val = gmPerson.gmCurrentPatient().ID) 1357 self._PRW_billable.add_callback_on_selection(self._on_billable_selected) 1358 self._PRW_billable.add_callback_on_modified(self._on_billable_modified)
1359 #---------------------------------------------------------------- 1360 # generic Edit Area mixin API 1361 #----------------------------------------------------------------
1362 - def _valid_for_save(self):
1363 1364 validity = True 1365 1366 if self._TCTRL_factor.GetValue().strip() == '': 1367 validity = False 1368 self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = False) 1369 self._TCTRL_factor.SetFocus() 1370 else: 1371 converted, factor = gmTools.input2decimal(self._TCTRL_factor.GetValue()) 1372 if not converted: 1373 validity = False 1374 self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = False) 1375 self._TCTRL_factor.SetFocus() 1376 else: 1377 self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = True) 1378 1379 if self._TCTRL_amount.GetValue().strip() == '': 1380 validity = False 1381 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False) 1382 self._TCTRL_amount.SetFocus() 1383 else: 1384 converted, factor = gmTools.input2decimal(self._TCTRL_amount.GetValue()) 1385 if not converted: 1386 validity = False 1387 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False) 1388 self._TCTRL_amount.SetFocus() 1389 else: 1390 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = True) 1391 1392 if self._TCTRL_count.GetValue().strip() == '': 1393 validity = False 1394 self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = False) 1395 self._TCTRL_count.SetFocus() 1396 else: 1397 converted, factor = gmTools.input2decimal(self._TCTRL_count.GetValue()) 1398 if not converted: 1399 validity = False 1400 self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = False) 1401 self._TCTRL_count.SetFocus() 1402 else: 1403 self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = True) 1404 1405 if self._PRW_date.is_valid_timestamp(empty_is_valid = True): 1406 self._PRW_date.display_as_valid(True) 1407 else: 1408 validity = False 1409 self._PRW_date.display_as_valid(False) 1410 self._PRW_date.SetFocus() 1411 1412 if self._PRW_encounter.GetData() is None: 1413 validity = False 1414 self._PRW_encounter.display_as_valid(False) 1415 self._PRW_encounter.SetFocus() 1416 else: 1417 self._PRW_encounter.display_as_valid(True) 1418 1419 if self._PRW_billable.GetData() is None: 1420 validity = False 1421 self._PRW_billable.display_as_valid(False) 1422 self._PRW_billable.SetFocus() 1423 else: 1424 self._PRW_billable.display_as_valid(True) 1425 1426 return validity
1427 #----------------------------------------------------------------
1428 - def _save_as_new(self):
1429 data = gmBilling.create_bill_item ( 1430 pk_encounter = self._PRW_encounter.GetData(), 1431 pk_billable = self._PRW_billable.GetData(), 1432 pk_staff = gmStaff.gmCurrentProvider()['pk_staff'] # should be settable ! 1433 ) 1434 data['raw_date_to_bill'] = self._PRW_date.GetData() 1435 converted, data['unit_count'] = gmTools.input2decimal(self._TCTRL_count.GetValue()) 1436 converted, data['net_amount_per_unit'] = gmTools.input2decimal(self._TCTRL_amount.GetValue()) 1437 converted, data['amount_multiplier'] = gmTools.input2decimal(self._TCTRL_factor.GetValue()) 1438 data['item_detail'] = self._TCTRL_comment.GetValue().strip() 1439 data.save() 1440 1441 self.data = data 1442 return True
1443 #----------------------------------------------------------------
1444 - def _save_as_update(self):
1445 self.data['pk_encounter_to_bill'] = self._PRW_encounter.GetData() 1446 self.data['raw_date_to_bill'] = self._PRW_date.GetData() 1447 converted, self.data['unit_count'] = gmTools.input2decimal(self._TCTRL_count.GetValue()) 1448 converted, self.data['net_amount_per_unit'] = gmTools.input2decimal(self._TCTRL_amount.GetValue()) 1449 converted, self.data['amount_multiplier'] = gmTools.input2decimal(self._TCTRL_factor.GetValue()) 1450 self.data['item_detail'] = self._TCTRL_comment.GetValue().strip() 1451 return self.data.save()
1452 #----------------------------------------------------------------
1453 - def _refresh_as_new(self):
1454 self._PRW_billable.SetText() 1455 self._PRW_encounter.set_from_instance(gmPerson.gmCurrentPatient().emr.active_encounter) 1456 self._PRW_date.SetData() 1457 self._TCTRL_count.SetValue('1') 1458 self._TCTRL_amount.SetValue('') 1459 self._LBL_currency.SetLabel(gmTools.u_euro) 1460 self._TCTRL_factor.SetValue('1') 1461 self._TCTRL_comment.SetValue('') 1462 1463 self._PRW_billable.Enable() 1464 self._PRW_billable.SetFocus()
1465 #----------------------------------------------------------------
1467 self._PRW_billable.SetText() 1468 self._TCTRL_count.SetValue('1') 1469 self._TCTRL_amount.SetValue('') 1470 self._TCTRL_comment.SetValue('') 1471 1472 self._PRW_billable.Enable() 1473 self._PRW_billable.SetFocus()
1474 #----------------------------------------------------------------
1475 - def _refresh_from_existing(self):
1476 self._PRW_billable.set_from_pk(self.data['pk_billable']) 1477 self._PRW_encounter.SetData(self.data['pk_encounter_to_bill']) 1478 self._PRW_date.SetData(data = self.data['raw_date_to_bill']) 1479 self._TCTRL_count.SetValue('%s' % self.data['unit_count']) 1480 self._TCTRL_amount.SetValue('%s' % self.data['net_amount_per_unit']) 1481 self._LBL_currency.SetLabel(self.data['currency']) 1482 self._TCTRL_factor.SetValue('%s' % self.data['amount_multiplier']) 1483 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['item_detail'], '')) 1484 1485 self._PRW_billable.Disable() 1486 self._PRW_date.SetFocus()
1487 #----------------------------------------------------------------
1488 - def _on_billable_selected(self, item):
1489 if item is None: 1490 return 1491 if self._TCTRL_amount.GetValue().strip() != '': 1492 return 1493 val = '%s' % self._PRW_billable.GetData(as_instance = True)['raw_amount'] 1494 wx.CallAfter(self._TCTRL_amount.SetValue, val)
1495 #----------------------------------------------------------------
1496 - def _on_billable_modified(self):
1497 if self._PRW_billable.GetData() is None: 1498 wx.CallAfter(self._TCTRL_amount.SetValue, '')
1499 1500 #============================================================ 1501 # a plugin for billing 1502 #------------------------------------------------------------ 1503 from Gnumed.wxGladeWidgets import wxgBillingPluginPnl 1504
1505 -class cBillingPluginPnl(wxgBillingPluginPnl.wxgBillingPluginPnl, gmRegetMixin.cRegetOnPaintMixin):
1506 - def __init__(self, *args, **kwargs):
1507 1508 wxgBillingPluginPnl.wxgBillingPluginPnl.__init__(self, *args, **kwargs) 1509 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 1510 self.__register_interests()
1511 #-----------------------------------------------------
1512 - def __reset_ui(self):
1513 self._PNL_bill_items.identity = None 1514 self._CHBOX_show_non_invoiced_only.SetValue(1) 1515 self._PRW_billable.SetText('', None) 1516 self._TCTRL_factor.SetValue('1.0') 1517 self._TCTRL_factor.Disable() 1518 self._TCTRL_details.SetValue('') 1519 self._TCTRL_details.Disable()
1520 #----------------------------------------------------- 1521 # event handling 1522 #-----------------------------------------------------
1523 - def __register_interests(self):
1524 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection) 1525 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection) 1526 1527 gmDispatcher.connect(signal = 'bill.bill_item_mod_db', receiver = self._on_bill_item_modified) 1528 1529 self._PRW_billable.add_callback_on_selection(self._on_billable_selected_in_prw)
1530 #-----------------------------------------------------
1532 self.__reset_ui()
1533 #-----------------------------------------------------
1534 - def _on_post_patient_selection(self):
1535 self._schedule_data_reget()
1536 #-----------------------------------------------------
1537 - def _on_bill_item_modified(self):
1538 self._schedule_data_reget()
1539 #-----------------------------------------------------
1541 self._PNL_bill_items.show_non_invoiced_only = self._CHBOX_show_non_invoiced_only.GetValue()
1542 #--------------------------------------------------------
1543 - def _on_insert_bill_item_button_pressed(self, event):
1544 if self._PRW_billable.GetData() is None: 1545 gmGuiHelpers.gm_show_warning ( 1546 _('No billable item selected.\n\nCannot insert bill item.'), 1547 _('Inserting bill item') 1548 ) 1549 return False 1550 val = self._TCTRL_factor.GetValue().strip() 1551 if val == '': 1552 factor = 1.0 1553 else: 1554 converted, factor = gmTools.input2decimal(val) 1555 if not converted: 1556 gmGuiHelpers.gm_show_warning ( 1557 _('"Factor" must be a number\n\nCannot insert bill item.'), 1558 _('Inserting bill item') 1559 ) 1560 return False 1561 bill_item = gmBilling.create_bill_item ( 1562 pk_encounter = gmPerson.gmCurrentPatient().emr.active_encounter['pk_encounter'], 1563 pk_billable = self._PRW_billable.GetData(), 1564 pk_staff = gmStaff.gmCurrentProvider()['pk_staff'] 1565 ) 1566 bill_item['amount_multiplier'] = factor 1567 bill_item['item_detail'] = self._TCTRL_details.GetValue() 1568 bill_item.save() 1569 1570 self._TCTRL_details.SetValue('') 1571 1572 return True
1573 #--------------------------------------------------------
1574 - def _on_billable_selected_in_prw(self, billable):
1575 if billable is None: 1576 self._TCTRL_factor.Disable() 1577 self._TCTRL_details.Disable() 1578 self._BTN_insert_item.Disable() 1579 else: 1580 self._TCTRL_factor.Enable() 1581 self._TCTRL_details.Enable() 1582 self._BTN_insert_item.Enable()
1583 #----------------------------------------------------- 1584 # reget-on-paint mixin API 1585 #-----------------------------------------------------
1586 - def _populate_with_data(self):
1587 self._PNL_bill_items.identity = gmPerson.gmCurrentPatient() 1588 return True
1589 1590 #============================================================ 1591 # main 1592 #------------------------------------------------------------ 1593 if __name__ == '__main__': 1594 1595 if len(sys.argv) < 2: 1596 sys.exit() 1597 1598 if sys.argv[1] != 'test': 1599 sys.exit() 1600 1601 from Gnumed.pycommon import gmI18N 1602 gmI18N.activate_locale() 1603 gmI18N.install_domain(domain = 'gnumed') 1604 1605 #---------------------------------------- 1606 app = wx.PyWidgetTester(size = (600, 600)) 1607 #app.SetWidget(cXxxPhraseWheel, -1) 1608 app.MainLoop() 1609