1
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 gmPrinting
24 from Gnumed.pycommon import gmNetworkTools
25
26 from Gnumed.business import gmBilling
27 from Gnumed.business import gmPerson
28 from Gnumed.business import gmStaff
29 from Gnumed.business import gmDocuments
30 from Gnumed.business import gmPraxis
31 from Gnumed.business import gmForms
32 from Gnumed.business import gmDemographicRecord
33
34 from Gnumed.wxpython import gmListWidgets
35 from Gnumed.wxpython import gmRegetMixin
36 from Gnumed.wxpython import gmPhraseWheel
37 from Gnumed.wxpython import gmGuiHelpers
38 from Gnumed.wxpython import gmEditArea
39 from Gnumed.wxpython import gmPersonContactWidgets
40 from Gnumed.wxpython import gmPatSearchWidgets
41 from Gnumed.wxpython import gmMacro
42 from Gnumed.wxpython import gmFormWidgets
43 from Gnumed.wxpython import gmDocumentWidgets
44 from Gnumed.wxpython import gmDataPackWidgets
45
46
47 _log = logging.getLogger('gm.ui')
48
49
51
52 if parent is None:
53 parent = wx.GetApp().GetTopWindow()
54
55
56
57
58 def delete(billable):
59 if billable.is_in_use:
60 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete this billable item. It is in use.'), beep = True)
61 return False
62 return gmBilling.delete_billable(pk_billable = billable['pk_billable'])
63
64 def get_tooltip(item):
65 if item is None:
66 return None
67 return item.format()
68
69 def refresh(lctrl):
70 billables = gmBilling.get_billables()
71 items = [ [
72 b['billable_code'],
73 b['billable_description'],
74 u'%(currency)s%(raw_amount)s' % b,
75 u'%s (%s)' % (b['catalog_short'], b['catalog_version']),
76 gmTools.coalesce(b['comment'], u''),
77 b['pk_billable']
78 ] for b in billables ]
79 lctrl.set_string_items(items)
80 lctrl.set_data(billables)
81
82 def manage_data_packs(billable):
83 gmDataPackWidgets.manage_data_packs(parent = parent)
84 return True
85
86 def browse_catalogs(billable):
87 dbcfg = gmCfg.cCfgSQL()
88 url = dbcfg.get2 (
89 option = 'external.urls.schedules_of_fees',
90 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
91 bias = 'user',
92 default = u'http://www.e-bis.de/goae/defaultFrame.htm'
93 )
94 gmNetworkTools.open_url_in_browser(url = url)
95 return False
96
97 msg = _('\nThese are the items for billing registered with GNUmed.\n')
98
99 gmListWidgets.get_choices_from_list (
100 parent = parent,
101 msg = msg,
102 caption = _('Showing billable items.'),
103 columns = [_('Code'), _('Description'), _('Value'), _('Catalog'), _('Comment'), u'#'],
104 single_selection = True,
105
106
107 delete_callback = delete,
108 refresh_callback = refresh,
109 middle_extra_button = (
110 _('Data packs'),
111 _('Browse and install billing catalog (schedule of fees) data packs'),
112 manage_data_packs
113 ),
114 right_extra_button = (
115 _('Catalogs (WWW)'),
116 _('Browse billing catalogs (schedules of fees) on the web'),
117 browse_catalogs
118 ),
119 list_tooltip_callback = get_tooltip
120 )
121
122
124
126 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
127 query = u"""
128 SELECT -- DISTINCT ON (label)
129 r_vb.pk_billable
130 AS data,
131 r_vb.billable_code || ': ' || r_vb.billable_description || ' (' || r_vb.catalog_short || ' - ' || r_vb.catalog_version || ')'
132 AS list_label,
133 r_vb.billable_code || ' (' || r_vb.catalog_short || ' - ' || r_vb.catalog_version || ')'
134 AS field_label
135 FROM
136 ref.v_billables r_vb
137 WHERE
138 r_vb.active
139 AND (
140 r_vb.billable_code %(fragment_condition)s
141 OR
142 r_vb.billable_description %(fragment_condition)s
143 )
144 ORDER BY list_label
145 LIMIT 20
146 """
147 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query)
148 mp.setThresholds(1, 2, 4)
149 self.matcher = mp
150
153
159
161 val = u'%s (%s - %s)' % (
162 instance['billable_code'],
163 instance['catalog_short'],
164 instance['catalog_version']
165 )
166 self.SetText(value = val, data = instance['pk_billable'])
167
170
171
172
173
205
207
208 dbcfg = gmCfg.cCfgSQL()
209 if with_vat:
210 option = u'form_templates.invoice_with_vat'
211 else:
212 option = u'form_templates.invoice_no_vat'
213
214 template = dbcfg.get2 (
215 option = option,
216 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
217 bias = 'user'
218 )
219
220 if template is None:
221 template = configure_invoice_template(parent = parent, with_vat = with_vat)
222 if template is None:
223 gmGuiHelpers.gm_show_error (
224 aMessage = _('There is no invoice template configured.'),
225 aTitle = _('Getting invoice template')
226 )
227 return None
228 else:
229 try:
230 name, ver = template.split(u' - ')
231 except:
232 _log.exception('problem splitting invoice template name [%s]', template)
233 gmDispatcher.send(signal = 'statustext', msg = _('Problem loading invoice template.'), beep = True)
234 return None
235 template = gmForms.get_form_template(name_long = name, external_version = ver)
236 if template is None:
237 gmGuiHelpers.gm_show_error (
238 aMessage = _('Cannot load invoice template [%s - %s]') % (name, ver),
239 aTitle = _('Getting invoice template')
240 )
241 return None
242
243 return template
244
245
246
247
248 -def edit_bill(parent=None, bill=None, single_entry=False):
264
266
267 if len(bill_items) == 0:
268 return None
269
270 item = bill_items[0]
271 currency = item['currency']
272 vat = item['vat_multiplier']
273 pat = item['pk_patient']
274
275
276 has_errors = False
277 for item in bill_items:
278 if (item['currency'] != currency) or (
279 item['vat_multiplier'] != vat) or (
280 item['pk_patient'] != pat
281 ):
282 msg = _(
283 'All items to be included with a bill must\n'
284 'coincide on currency, VAT, and patient.\n'
285 '\n'
286 'This item does not:\n'
287 '\n'
288 '%s\n'
289 ) % item.format()
290 has_errors = True
291
292 if item['pk_bill'] is not None:
293 msg = _(
294 'This item is already invoiced:\n'
295 '\n'
296 '%s\n'
297 '\n'
298 'Cannot put it on a second bill.'
299 ) % item.format()
300 has_errors = True
301
302 if has_errors:
303 gmGuiHelpers.gm_show_warning(aTitle = _('Checking invoice items'), aMessage = msg)
304 return None
305
306
307 bill = gmBilling.create_bill(invoice_id = gmBilling.get_invoice_id(pk_patient = pat))
308 _log.info('created bill [%s]', bill['invoice_id'])
309 bill.add_items(items = bill_items)
310 bill.set_missing_address_from_default()
311
312 return bill
313
315
316 bill_patient_not_active = False
317
318 curr_pat = gmPerson.gmCurrentPatient()
319 if curr_pat.connected:
320
321
322
323 if curr_pat.ID != bill['pk_patient']:
324 bill_patient_not_active = True
325 else:
326 bill_patient_not_active = True
327
328
329
330 if bill_patient_not_active:
331 activate_patient = gmGuiHelpers.gm_show_question (
332 title = _('Creating invoice'),
333 question = _(
334 'Cannot find an existing invoice PDF for this bill.\n'
335 '\n'
336 'Active patient: %s\n'
337 'Patient on bill: #%s\n'
338 '\n'
339 'Activate patient on bill so invoice PDF can be created ?'
340 ) % (
341 gmTools.coalesce(curr_pat.ID, u'', u'#%s'),
342 bill['pk_patient']
343 )
344 )
345 if not activate_patient:
346 return False
347 if not gmPatSearchWidgets.set_active_patient(patient = bill['pk_patient']):
348 gmGuiHelpers.gm_show_error (
349 aTitle = _('Creating invoice'),
350 aMessage = _('Cannot activate patient #%s.') % bill['pk_patient']
351 )
352 return False
353
354 if None in [ bill['close_date'], bill['pk_receiver_address'] ]:
355 edit_bill(parent = parent, bill = bill, single_entry = True)
356
357 if bill['close_date'] is None:
358 _log.error('cannot create invoice from bill, bill not closed')
359 gmGuiHelpers.gm_show_warning (
360 aTitle = _('Creating invoice'),
361 aMessage = _(
362 'Cannot create invoice from bill.\n'
363 '\n'
364 'The bill is not closed.'
365 )
366 )
367 return False
368
369 if bill['pk_receiver_address'] is None:
370 _log.error('cannot create invoice from bill, lacking receiver address')
371 gmGuiHelpers.gm_show_warning (
372 aTitle = _('Creating invoice'),
373 aMessage = _(
374 'Cannot create invoice from bill.\n'
375 '\n'
376 'There is no receiver address.'
377 )
378 )
379 return False
380
381
382 template = get_invoice_template(parent = parent, with_vat = bill['apply_vat'])
383 if template is None:
384 gmGuiHelpers.gm_show_warning (
385 aTitle = _('Creating invoice'),
386 aMessage = _(
387 'Cannot create invoice from bill\n'
388 'without an invoice template.'
389 )
390 )
391 return False
392
393
394 try:
395 invoice = template.instantiate()
396 except KeyError:
397 _log.exception('cannot instantiate invoice template [%s]', template)
398 gmGuiHelpers.gm_show_error (
399 aMessage = _('Invalid invoice template [%s - %s (%s)]') % (name, ver, template['engine']),
400 aTitle = _('Printing medication list')
401 )
402 return False
403
404 ph = gmMacro.gmPlaceholderHandler()
405
406 ph.set_cache_value('bill', bill)
407 invoice.substitute_placeholders(data_source = ph)
408 ph.unset_cache_value('bill')
409 pdf_name = invoice.generate_output()
410 if pdf_name is None:
411 gmGuiHelpers.gm_show_error (
412 aMessage = _('Error generating invoice PDF.'),
413 aTitle = _('Creating invoice')
414 )
415 return False
416
417
418 if keep_a_copy:
419 files2import = []
420 files2import.extend(invoice.final_output_filenames)
421 files2import.extend(invoice.re_editable_filenames)
422 doc = gmDocumentWidgets.save_files_as_new_document (
423 parent = parent,
424 filenames = files2import,
425 document_type = template['instance_type'],
426 review_as_normal = True,
427 reference = bill['invoice_id']
428 )
429 bill['pk_doc'] = doc['pk_doc']
430 bill.save()
431
432 if not print_it:
433 return True
434
435
436 printed = gmPrinting.print_files(filenames = [pdf_name], jobtype = 'invoice')
437 if not printed:
438 gmGuiHelpers.gm_show_error (
439 aMessage = _('Error printing the invoice.'),
440 aTitle = _('Printing invoice')
441 )
442 return True
443
444 return True
445
446
448
449 if parent is None:
450 parent = wx.GetApp().GetTopWindow()
451
452 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
453 parent, -1,
454 caption = _('Deleting bill'),
455 question = _(
456 'When deleting the bill [%s]\n'
457 'do you want to keep its items (effectively \"unbilling\" them)\n'
458 'or do you want to also delete the bill items from the patient ?\n'
459 ) % bill['invoice_id'],
460 button_defs = [
461 {'label': _('Delete + keep'), 'tooltip': _('Delete the bill but keep ("unbill") its items.'), 'default': True},
462 {'label': _('Delete all'), 'tooltip': _('Delete both the bill and its items from the patient.')}
463 ],
464 show_checkbox = True,
465 checkbox_msg = _('Also remove invoice PDF'),
466 checkbox_tooltip = _('Also remove the invoice PDF from the document archive (because it will not correspond to the bill anymore).')
467 )
468 button_pressed = dlg.ShowModal()
469 delete_invoice = dlg.checkbox_is_checked()
470 dlg.Destroy()
471
472 if button_pressed == wx.ID_CANCEL:
473 return False
474
475 if button_pressed == wx.ID_YES:
476 for item in bill.bill_items:
477 item['pk_bill'] = None
478 item.save()
479
480 if button_pressed == wx.ID_NO:
481 for item in bill.bill_items:
482 item['pk_bill'] = None
483 item.save()
484 gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item'])
485
486 if delete_invoice:
487 if bill['pk_doc'] is not None:
488 gmDocuments.delete_document (
489 document_id = bill['pk_doc'],
490 encounter_id = gmPerson.cPatient(aPK_obj = bill['pk_patient']).emr.active_encounter['pk_encounter']
491 )
492
493 return gmBilling.delete_bill(pk_bill = bill['pk_bill'])
494
495
497
498 if bill is None:
499 return False
500
501 list_data = bill.bill_items
502 if len(list_data) == 0:
503 return False
504
505 if parent is None:
506 parent = wx.GetApp().GetTopWindow()
507
508 list_items = [ [
509 gmDateTime.pydt_strftime(b['date_to_bill'], '%Y %b %d', accuracy = gmDateTime.acc_days),
510 b['unit_count'],
511 u'%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], u'', u' - %s')),
512 u'%(curr)s %(total_val)s (%(count)s %(x)s %(unit_val)s%(x)s%(val_multiplier)s)' % {
513 'curr': b['currency'],
514 'total_val': b['total_amount'],
515 'count': b['unit_count'],
516 'x': gmTools.u_multiply,
517 'unit_val': b['net_amount_per_unit'],
518 'val_multiplier': b['amount_multiplier']
519 },
520 u'%(curr)s%(vat)s (%(perc_vat)s%%)' % {
521 'vat': b['vat'],
522 'curr': b['currency'],
523 'perc_vat': b['vat_multiplier'] * 100
524 },
525 u'%s (%s)' % (b['catalog_short'], b['catalog_version']),
526 b['pk_bill_item']
527 ] for b in list_data ]
528
529 msg = _('Select the items you want to remove from bill [%s]:\n') % bill['invoice_id']
530 items2remove = gmListWidgets.get_choices_from_list (
531 parent = parent,
532 msg = msg,
533 caption = _('Removing items from bill'),
534 columns = [_('Date'), _('Count'), _('Description'), _('Value'), _('VAT'), _('Catalog'), u'#'],
535 single_selection = False,
536 choices = list_items,
537 data = list_data
538 )
539
540 if items2remove is None:
541 return False
542
543 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
544 parent, -1,
545 caption = _('Removing items from bill'),
546 question = _(
547 '%s items selected from bill [%s]\n'
548 '\n'
549 'Do you want to only remove the selected items\n'
550 'from the bill ("unbill" them) or do you want\n'
551 'to delete them entirely from the patient ?\n'
552 '\n'
553 'Note that neither action is reversible.'
554 ) % (
555 len(items2remove),
556 bill['invoice_id']
557 ),
558 button_defs = [
559 {'label': _('"Unbill"'), 'tooltip': _('Only "unbill" items (remove from bill but do not delete from patient).'), 'default': True},
560 {'label': _('Delete'), 'tooltip': _('Completely delete items from the patient.')}
561 ],
562 show_checkbox = True,
563 checkbox_msg = _('Also remove invoice PDF'),
564 checkbox_tooltip = _('Also remove the invoice PDF from the document archive (because it will not correspond to the bill anymore).')
565 )
566 button_pressed = dlg.ShowModal()
567 delete_invoice = dlg.checkbox_is_checked()
568 dlg.Destroy()
569
570 if button_pressed == wx.ID_CANCEL:
571 return False
572
573
574
575 pk_patient = bill['pk_patient']
576
577 for item in items2remove:
578 item['pk_bill'] = None
579 item.save()
580 if button_pressed == wx.ID_NO:
581 gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item'])
582
583 if delete_invoice:
584 if bill['pk_doc'] is not None:
585 gmDocuments.delete_document (
586 document_id = bill['pk_doc'],
587 encounter_id = gmPerson.cPatient(aPK_obj = pk_patient).emr.active_encounter['pk_encounter']
588 )
589
590
591 if len(bill.bill_items) == 0:
592 gmBilling.delete_bill(pk_bill = bill['pk_bill'])
593
594 return True
595
597
598 if parent is None:
599 parent = wx.GetApp().GetTopWindow()
600
601
602 def show_pdf(bill):
603 if bill is None:
604 return False
605
606
607 invoice = bill.invoice
608 if invoice is not None:
609 success, msg = invoice.parts[-1].display_via_mime()
610 if not success:
611 gmGuiHelpers.gm_show_error(aMessage = msg, aTitle = _('Displaying invoice'))
612 return False
613
614
615 create_it = gmGuiHelpers.gm_show_question (
616 title = _('Displaying invoice'),
617 question = _(
618 'Cannot find an existing\n'
619 'invoice PDF for this bill.\n'
620 '\n'
621 'Do you want to create one ?'
622 ),
623 )
624 if not create_it:
625 return False
626
627
628 if not bill.set_missing_address_from_default():
629 gmGuiHelpers.gm_show_warning (
630 aTitle = _('Creating invoice'),
631 aMessage = _(
632 'There is no pre-configured billing address.\n'
633 '\n'
634 'Select the address you want to send the bill to.'
635 )
636 )
637 edit_bill(parent = parent, bill = bill, single_entry = True)
638 if bill['pk_receiver_address'] is None:
639 return False
640 if bill['close_date'] is None:
641 bill['close_date'] = gmDateTime.pydt_now_here()
642 bill.save()
643
644 return create_invoice_from_bill(parent = parent, bill = bill, print_it = True, keep_a_copy = True)
645
646 def edit(bill):
647 return edit_bill(parent = parent, bill = bill, single_entry = True)
648
649 def delete(bill):
650 return delete_bill(parent = parent, bill = bill)
651
652 def remove_items(bill):
653 return remove_items_from_bill(parent = parent, bill = bill)
654
655 def get_tooltip(item):
656 if item is None:
657 return None
658 return item.format()
659
660 def refresh(lctrl):
661 if patient is None:
662 bills = gmBilling.get_bills()
663 else:
664 bills = gmBilling.get_bills(pk_patient = patient.ID)
665 items = []
666 for b in bills:
667 if b['close_date'] is None:
668 close_date = _('<open>')
669 else:
670 close_date = gmDateTime.pydt_strftime(b['close_date'], '%Y %b %d')
671 if b['total_amount'] is None:
672 amount = _('no items on bill')
673 else:
674 amount = gmTools.bool2subst (
675 b['apply_vat'],
676 _('%(currency)s%(total_amount_with_vat)s (with %(percent_vat)s%% VAT)') % b,
677 u'%(currency)s%(total_amount)s' % b
678 )
679 items.append ([
680 close_date,
681 b['invoice_id'],
682 amount,
683 gmTools.coalesce(b['comment'], u'')
684 ])
685 lctrl.set_string_items(items)
686 lctrl.set_data(bills)
687
688 return gmListWidgets.get_choices_from_list (
689 parent = parent,
690 caption = _('Showing bills.'),
691 columns = [_('Close date'), _('Invoice ID'), _('Value'), _('Comment')],
692 single_selection = True,
693 edit_callback = edit,
694 delete_callback = delete,
695 refresh_callback = refresh,
696 middle_extra_button = (
697 u'PDF',
698 _('Create if necessary, and show the corresponding invoice PDF'),
699 show_pdf
700 ),
701 right_extra_button = (
702 _('Unbill'),
703 _('Select and remove items from a bill.'),
704 remove_items
705 ),
706 list_tooltip_callback = get_tooltip
707 )
708
709
710 from Gnumed.wxGladeWidgets import wxgBillEAPnl
711
712 -class cBillEAPnl(wxgBillEAPnl.wxgBillEAPnl, gmEditArea.cGenericEditAreaMixin):
713
729
730
731
732
733
734
735
737 validity = True
738
739
740 if not self._PRW_close_date.is_valid_timestamp(allow_empty = False):
741 self._PRW_close_date.SetFocus()
742
743 return validity
744
748
750 self.data['close_date'] = self._PRW_close_date.GetData()
751 self.data['apply_vat'] = self._CHBOX_vat_applies.GetValue()
752 self.data['comment'] = self._TCTRL_comment.GetValue()
753 self.data.save()
754 return True
755
758
760 self._refresh_as_new()
761
789
790
791
802
817
818
819
820
822
823 if bill_item is not None:
824 if bill_item.is_in_use:
825 gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit already invoiced bill item.'), beep = True)
826 return False
827
828 ea = cBillItemEAPnl(parent = parent, id = -1)
829 ea.data = bill_item
830 ea.mode = gmTools.coalesce(bill_item, 'new', 'edit')
831 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = single_entry)
832 dlg.SetTitle(gmTools.coalesce(bill_item, _('Adding new bill item'), _('Editing bill item')))
833 if dlg.ShowModal() == wx.ID_OK:
834 dlg.Destroy()
835 return True
836 dlg.Destroy()
837 return False
838
840
841 if parent is None:
842 parent = wx.GetApp().GetTopWindow()
843
844 def edit(item=None):
845 return edit_bill_item(parent = parent, bill_item = item, single_entry = (item is not None))
846
847 def delete(item):
848 if item.is_in_use is not None:
849 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete already invoiced bill items.'), beep = True)
850 return False
851 gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item'])
852 return True
853
854 def get_tooltip(item):
855 if item is None:
856 return None
857 return item.format()
858
859 def refresh(lctrl):
860 b_items = gmBilling.get_bill_items(pk_patient = pk_patient)
861 items = [ [
862 gmDateTime.pydt_strftime(b['date_to_bill'], '%Y %b %d', accuracy = gmDateTime.acc_days),
863 b['unit_count'],
864 u'%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], u'', u' - %s')),
865 b['currency'],
866 u'%s (%s %s %s%s%s)' % (
867 b['total_amount'],
868 b['unit_count'],
869 gmTools.u_multiply,
870 b['net_amount_per_unit'],
871 gmTools.u_multiply,
872 b['amount_multiplier']
873 ),
874 u'%s (%s%%)' % (
875 b['vat'],
876 b['vat_multiplier'] * 100
877 ),
878 u'%s (%s)' % (b['catalog_short'], b['catalog_version']),
879 b['pk_bill_item']
880 ] for b in b_items ]
881 lctrl.set_string_items(items)
882 lctrl.set_data(b_items)
883
884 gmListWidgets.get_choices_from_list (
885 parent = parent,
886
887 caption = _('Showing bill items.'),
888 columns = [_('Date'), _('Count'), _('Description'), _('$__replace_by_your_currency_symbol')[:-len('__replace_by_your_currency_symbol')], _('Value'), _('VAT'), _('Catalog'), u'#'],
889 single_selection = True,
890 new_callback = edit,
891 edit_callback = edit,
892 delete_callback = delete,
893 refresh_callback = refresh,
894 list_tooltip_callback = get_tooltip
895 )
896
897
899 """A list for managing a patient's bill items.
900
901 Does NOT act on/listen to the current patient.
902 """
922
923
924
925 - def refresh(self, *args, **kwargs):
926 if self.__identity is None:
927 self._LCTRL_items.set_string_items()
928 return
929
930 b_items = gmBilling.get_bill_items(pk_patient = self.__identity.ID, non_invoiced_only = self.__show_non_invoiced_only)
931 items = [ [
932 gmDateTime.pydt_strftime(b['date_to_bill'], '%Y %b %d', accuracy = gmDateTime.acc_days),
933 b['unit_count'],
934 u'%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], u'', u' - %s')),
935 b['currency'],
936 b['total_amount'],
937 u'%s (%s%%)' % (
938 b['vat'],
939 b['vat_multiplier'] * 100
940 ),
941 u'%s (%s)' % (b['catalog_short'], b['catalog_version']),
942 u'%s %s %s %s %s' % (
943 b['unit_count'],
944 gmTools.u_multiply,
945 b['net_amount_per_unit'],
946 gmTools.u_multiply,
947 b['amount_multiplier']
948 ),
949 gmTools.coalesce(b['pk_bill'], gmTools.u_diameter),
950 b['pk_encounter_to_bill'],
951 b['pk_bill_item']
952 ] for b in b_items ]
953
954 self._LCTRL_items.set_string_items(items = items)
955 self._LCTRL_items.set_column_widths()
956 self._LCTRL_items.set_data(data = b_items)
957
958
959
961 self._LCTRL_items.set_columns(columns = [
962 _('Charge date'),
963 _('Count'),
964 _('Description'),
965 _('$__replace_by_your_currency_symbol')[:-len('__replace_by_your_currency_symbol')],
966 _('Value'),
967 _('VAT'),
968 _('Catalog'),
969 _('Count %s Value %s Factor') % (gmTools.u_multiply, gmTools.u_multiply),
970 _('Invoice'),
971 _('Encounter'),
972 u'#'
973 ])
974 self._LCTRL_items.item_tooltip_callback = self._get_item_tooltip
975
976
977
978
979
980 self.left_extra_button = (
981 _('Invoice selected items'),
982 _('Create invoice from selected items.'),
983 self._invoice_selected_items
984 )
985 self.middle_extra_button = (
986 _('Bills'),
987 _('Browse bills of this patient.'),
988 self._browse_bills
989 )
990 self.right_extra_button = (
991 _('Billables'),
992 _('Browse list of billables.'),
993 self._browse_billables
994 )
995
997 return edit_bill_item(parent = self, bill_item = None, single_entry = False)
998
1000 return edit_bill_item(parent = self, bill_item = bill_item, single_entry = True)
1001
1003 if item['pk_bill'] is not None:
1004 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete already invoiced bill items.'), beep = True)
1005 return False
1006 go_ahead = gmGuiHelpers.gm_show_question (
1007 _( 'Do you really want to delete this\n'
1008 'bill item from the patient ?'),
1009 _('Deleting bill item')
1010 )
1011 if not go_ahead:
1012 return False
1013 gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item'])
1014 return True
1015
1020
1023
1043
1047
1050
1051
1052
1054 return self.__identity
1055
1059
1060 identity = property(_get_identity, _set_identity)
1061
1063 return self.__show_non_invoiced_only
1064
1066 self.__show_non_invoiced_only = value
1067 self.refresh()
1068
1069 show_non_invoiced_only = property(_get_show_non_invoiced_only, _set_show_non_invoiced_only)
1070
1071
1072 from Gnumed.wxGladeWidgets import wxgBillItemEAPnl
1073
1074 -class cBillItemEAPnl(wxgBillItemEAPnl.wxgBillItemEAPnl, gmEditArea.cGenericEditAreaMixin):
1075
1093
1097
1098
1099
1101
1102 validity = True
1103
1104 if self._TCTRL_factor.GetValue().strip() == u'':
1105 validity = False
1106 self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = False)
1107 self._TCTRL_factor.SetFocus()
1108 else:
1109 converted, factor = gmTools.input2decimal(self._TCTRL_factor.GetValue())
1110 if not converted:
1111 validity = False
1112 self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = False)
1113 self._TCTRL_factor.SetFocus()
1114 else:
1115 self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = True)
1116
1117 if self._TCTRL_amount.GetValue().strip() == u'':
1118 validity = False
1119 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False)
1120 self._TCTRL_amount.SetFocus()
1121 else:
1122 converted, factor = gmTools.input2decimal(self._TCTRL_amount.GetValue())
1123 if not converted:
1124 validity = False
1125 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False)
1126 self._TCTRL_amount.SetFocus()
1127 else:
1128 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = True)
1129
1130 if self._TCTRL_count.GetValue().strip() == u'':
1131 validity = False
1132 self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = False)
1133 self._TCTRL_count.SetFocus()
1134 else:
1135 converted, factor = gmTools.input2decimal(self._TCTRL_count.GetValue())
1136 if not converted:
1137 validity = False
1138 self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = False)
1139 self._TCTRL_count.SetFocus()
1140 else:
1141 self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = True)
1142
1143 if self._PRW_date.is_valid_timestamp(allow_empty = True):
1144 self._PRW_date.display_as_valid(True)
1145 else:
1146 validity = False
1147 self._PRW_date.display_as_valid(False)
1148 self._PRW_date.SetFocus()
1149
1150 if self._PRW_encounter.GetData() is None:
1151 validity = False
1152 self._PRW_encounter.display_as_valid(False)
1153 self._PRW_encounter.SetFocus()
1154 else:
1155 self._PRW_encounter.display_as_valid(True)
1156
1157 if self._PRW_billable.GetData() is None:
1158 validity = False
1159 self._PRW_billable.display_as_valid(False)
1160 self._PRW_billable.SetFocus()
1161 else:
1162 self._PRW_billable.display_as_valid(True)
1163
1164 return validity
1165
1181
1190
1203
1205 self._PRW_billable.SetText()
1206 self._TCTRL_count.SetValue(u'1')
1207 self._TCTRL_amount.SetValue(u'')
1208 self._TCTRL_comment.SetValue(u'')
1209
1210 self._PRW_billable.Enable()
1211 self._PRW_billable.SetFocus()
1212
1214 self._PRW_billable.set_from_pk(self.data['pk_billable'])
1215 self._PRW_encounter.SetData(self.data['pk_encounter_to_bill'])
1216 self._PRW_date.SetData(data = self.data['raw_date_to_bill'])
1217 self._TCTRL_count.SetValue(u'%s' % self.data['unit_count'])
1218 self._TCTRL_amount.SetValue(u'%s' % self.data['net_amount_per_unit'])
1219 self._LBL_currency.SetLabel(self.data['currency'])
1220 self._TCTRL_factor.SetValue(u'%s' % self.data['amount_multiplier'])
1221 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['item_detail'], u''))
1222
1223 self._PRW_billable.Disable()
1224 self._PRW_date.SetFocus()
1225
1227 if item is None:
1228 return
1229 if self._TCTRL_amount.GetValue().strip() != u'':
1230 return
1231 val = u'%s' % self._PRW_billable.GetData(as_instance = True)['raw_amount']
1232 wx.CallAfter(self._TCTRL_amount.SetValue, val)
1233
1234
1235
1236
1237 from Gnumed.wxGladeWidgets import wxgBillingPluginPnl
1238
1239 -class cBillingPluginPnl(wxgBillingPluginPnl.wxgBillingPluginPnl, gmRegetMixin.cRegetOnPaintMixin):
1245
1247 self._PNL_bill_items.identity = None
1248 self._CHBOX_show_non_invoiced_only.SetValue(1)
1249 self._PRW_billable.SetText(u'', None)
1250 self._TCTRL_factor.SetValue(u'1.0')
1251 self._TCTRL_factor.Disable()
1252 self._TCTRL_details.SetValue(u'')
1253 self._TCTRL_details.Disable()
1254
1255
1256
1264
1266 wx.CallAfter(self.__reset_ui)
1267
1269 wx.CallAfter(self._schedule_data_reget)
1270
1272 wx.CallAfter(self._schedule_data_reget)
1273
1276
1301
1303 if billable is None:
1304 self._TCTRL_factor.Disable()
1305 self._TCTRL_details.Disable()
1306 self._BTN_insert_item.Disable()
1307 else:
1308 self._TCTRL_factor.Enable()
1309 self._TCTRL_details.Enable()
1310 self._BTN_insert_item.Enable()
1311
1312
1313
1317
1318
1319
1320 if __name__ == '__main__':
1321
1322 if len(sys.argv) < 2:
1323 sys.exit()
1324
1325 if sys.argv[1] != 'test':
1326 sys.exit()
1327
1328 from Gnumed.pycommon import gmI18N
1329 gmI18N.activate_locale()
1330 gmI18N.install_domain(domain = 'gnumed')
1331
1332
1333 app = wx.PyWidgetTester(size = (600, 600))
1334
1335
1336 app.MainLoop()
1337