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 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
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
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
144
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
173
179
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
190
191
192 from Gnumed.wxGladeWidgets import wxgBillableEAPnl
193
194 -class cBillableEAPnl(wxgBillableEAPnl.wxgBillableEAPnl, gmEditArea.cGenericEditAreaMixin):
195
211
212
213
214
215
216
217
218
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
310
325
337
339 self._refresh_as_new()
340
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
358
390
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:
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
432
433 -def edit_bill(parent=None, bill=None, single_entry=False):
449
498
499
501
502 bill_patient_not_active = False
503
504 curr_pat = gmPerson.gmCurrentPatient()
505 if curr_pat.connected:
506
507
508
509 if curr_pat.ID != bill['pk_patient']:
510 bill_patient_not_active = True
511 else:
512 bill_patient_not_active = True
513
514
515
516 if bill_patient_not_active:
517 activate_patient = gmGuiHelpers.gm_show_question (
518 title = _('Creating invoice'),
519 question = _(
520 'Cannot find an existing invoice PDF for this bill.\n'
521 '\n'
522 'Active patient: %s\n'
523 'Patient on bill: #%s\n'
524 '\n'
525 'Activate patient on bill so invoice PDF can be created ?'
526 ) % (
527 gmTools.coalesce(curr_pat.ID, '', '#%s'),
528 bill['pk_patient']
529 )
530 )
531 if not activate_patient:
532 return False
533 if not gmPatSearchWidgets.set_active_patient(patient = bill['pk_patient']):
534 gmGuiHelpers.gm_show_error (
535 aTitle = _('Creating invoice'),
536 aMessage = _('Cannot activate patient #%s.') % bill['pk_patient']
537 )
538 return False
539
540 if None in [ bill['close_date'], bill['pk_receiver_address'], bill['apply_vat'] ]:
541 edit_bill(parent = parent, bill = bill, single_entry = True)
542
543 if bill['close_date'] is None:
544 _log.error('cannot create invoice from bill, bill not closed')
545 gmGuiHelpers.gm_show_warning (
546 aTitle = _('Creating invoice'),
547 aMessage = _(
548 'Cannot create invoice from bill.\n'
549 '\n'
550 'The bill is not closed.'
551 )
552 )
553 return False
554
555 if bill['pk_receiver_address'] is None:
556 _log.error('cannot create invoice from bill, lacking receiver address')
557 gmGuiHelpers.gm_show_warning (
558 aTitle = _('Creating invoice'),
559 aMessage = _(
560 'Cannot create invoice from bill.\n'
561 '\n'
562 'There is no receiver address.'
563 )
564 )
565 return False
566
567 if bill['apply_vat'] is None:
568 _log.error('cannot create invoice from bill, apply_vat undecided')
569 gmGuiHelpers.gm_show_warning (
570 aTitle = _('Creating invoice'),
571 aMessage = _(
572 'Cannot create invoice from bill.\n'
573 '\n'
574 'You must decide on whether to apply VAT.'
575 )
576 )
577 return False
578
579
580 template = get_invoice_template(parent = parent, with_vat = bill['apply_vat'])
581 if template is None:
582 gmGuiHelpers.gm_show_warning (
583 aTitle = _('Creating invoice'),
584 aMessage = _(
585 'Cannot create invoice from bill\n'
586 'without an invoice template.'
587 )
588 )
589 return False
590
591
592 try:
593 invoice = template.instantiate()
594 except KeyError:
595 _log.exception('cannot instantiate invoice template [%s]', template)
596 gmGuiHelpers.gm_show_error (
597 aMessage = _('Invalid invoice template [%s - %s (%s)]') % (name, ver, template['engine']),
598 aTitle = _('Printing medication list')
599 )
600 return False
601
602 ph = gmMacro.gmPlaceholderHandler()
603
604 ph.set_cache_value('bill', bill)
605 invoice.substitute_placeholders(data_source = ph)
606 ph.unset_cache_value('bill')
607 pdf_name = invoice.generate_output()
608 if pdf_name is None:
609 gmGuiHelpers.gm_show_error (
610 aMessage = _('Error generating invoice PDF.'),
611 aTitle = _('Creating invoice')
612 )
613 return False
614
615
616 if keep_a_copy:
617 files2import = []
618 files2import.extend(invoice.final_output_filenames)
619 files2import.extend(invoice.re_editable_filenames)
620 doc = gmDocumentWidgets.save_files_as_new_document (
621 parent = parent,
622 filenames = files2import,
623 document_type = template['instance_type'],
624 review_as_normal = True,
625 reference = bill['invoice_id'],
626 pk_org_unit = gmPraxis.gmCurrentPraxisBranch()['pk_org_unit']
627 )
628 bill['pk_doc'] = doc['pk_doc']
629 bill.save()
630
631 if not print_it:
632 return True
633
634
635 _cfg = gmCfg2.gmCfgData()
636 printed = gmPrinting.print_files(filenames = [pdf_name], jobtype = 'invoice', verbose = _cfg.get(option = 'debug'))
637 if not printed:
638 gmGuiHelpers.gm_show_error (
639 aMessage = _('Error printing the invoice.'),
640 aTitle = _('Printing invoice')
641 )
642 return True
643
644 return True
645
646
648
649 if parent is None:
650 parent = wx.GetApp().GetTopWindow()
651
652 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
653 parent, -1,
654 caption = _('Deleting bill'),
655 question = _(
656 'When deleting the bill [%s]\n'
657 'do you want to keep its items (effectively \"unbilling\" them)\n'
658 'or do you want to also delete the bill items from the patient ?\n'
659 ) % bill['invoice_id'],
660 button_defs = [
661 {'label': _('Delete + keep'), 'tooltip': _('Delete the bill but keep ("unbill") its items.'), 'default': True},
662 {'label': _('Delete all'), 'tooltip': _('Delete both the bill and its items from the patient.')}
663 ],
664 show_checkbox = True,
665 checkbox_msg = _('Also remove invoice PDF'),
666 checkbox_tooltip = _('Also remove the invoice PDF from the document archive (because it will not correspond to the bill anymore).')
667 )
668 button_pressed = dlg.ShowModal()
669 delete_invoice = dlg.checkbox_is_checked()
670 dlg.DestroyLater()
671
672 if button_pressed == wx.ID_CANCEL:
673 return False
674
675 delete_items = (button_pressed == wx.ID_NO)
676
677 if delete_invoice:
678 if bill['pk_doc'] is not None:
679 gmDocuments.delete_document (
680 document_id = bill['pk_doc'],
681 encounter_id = gmPerson.cPatient(aPK_obj = bill['pk_patient']).emr.active_encounter['pk_encounter']
682 )
683
684 items = bill['pk_bill_items']
685 success = gmBilling.delete_bill(pk_bill = bill['pk_bill'])
686 if delete_items:
687 for item in items:
688 gmBilling.delete_bill_item(pk_bill_item = item)
689
690 return success
691
692
694
695 if bill is None:
696 return False
697
698 list_data = bill.bill_items
699 if len(list_data) == 0:
700 return False
701
702 if parent is None:
703 parent = wx.GetApp().GetTopWindow()
704
705 list_items = [ [
706 gmDateTime.pydt_strftime(b['date_to_bill'], '%Y %b %d', accuracy = gmDateTime.acc_days),
707 b['unit_count'],
708 '%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], '', ' - %s')),
709 '%(curr)s %(total_val)s (%(count)s %(x)s %(unit_val)s%(x)s%(val_multiplier)s)' % {
710 'curr': b['currency'],
711 'total_val': b['total_amount'],
712 'count': b['unit_count'],
713 'x': gmTools.u_multiply,
714 'unit_val': b['net_amount_per_unit'],
715 'val_multiplier': b['amount_multiplier']
716 },
717 '%(curr)s%(vat)s (%(perc_vat)s%%)' % {
718 'vat': b['vat'],
719 'curr': b['currency'],
720 'perc_vat': b['vat_multiplier'] * 100
721 },
722 '%s (%s)' % (b['catalog_short'], b['catalog_version']),
723 b['pk_bill_item']
724 ] for b in list_data ]
725
726 msg = _('Select the items you want to remove from bill [%s]:\n') % bill['invoice_id']
727 items2remove = gmListWidgets.get_choices_from_list (
728 parent = parent,
729 msg = msg,
730 caption = _('Removing items from bill'),
731 columns = [_('Date'), _('Count'), _('Description'), _('Value'), _('VAT'), _('Catalog'), '#'],
732 single_selection = False,
733 choices = list_items,
734 data = list_data
735 )
736
737 if items2remove is None:
738 return False
739
740 if len(items2remove) == len(list_items):
741 gmGuiHelpers.gm_show_info (
742 title = _('Removing items from bill'),
743 info = _(
744 'Cannot remove all items from a bill because\n'
745 'GNUmed does not support empty bills.\n'
746 '\n'
747 'You must delete the bill itself if you want to\n'
748 'remove all items (at which point you can opt to\n'
749 'keep the items and only delete the bill).'
750 )
751 )
752 return False
753
754 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
755 parent, -1,
756 caption = _('Removing items from bill'),
757 question = _(
758 '%s items selected from bill [%s]\n'
759 '\n'
760 'Do you want to only remove the selected items\n'
761 'from the bill ("unbill" them) or do you want\n'
762 'to delete them entirely from the patient ?\n'
763 '\n'
764 'Note that neither action is reversible.'
765 ) % (
766 len(items2remove),
767 bill['invoice_id']
768 ),
769 button_defs = [
770 {'label': _('"Unbill"'), 'tooltip': _('Only "unbill" items (remove from bill but do not delete from patient).'), 'default': True},
771 {'label': _('Delete'), 'tooltip': _('Completely delete items from the patient.')}
772 ],
773 show_checkbox = True,
774 checkbox_msg = _('Also remove invoice PDF'),
775 checkbox_tooltip = _('Also remove the invoice PDF from the document archive (because it will not correspond to the bill anymore).')
776 )
777 button_pressed = dlg.ShowModal()
778 delete_invoice = dlg.checkbox_is_checked()
779 dlg.DestroyLater()
780
781 if button_pressed == wx.ID_CANCEL:
782 return False
783
784
785
786 pk_patient = bill['pk_patient']
787
788 for item in items2remove:
789 item['pk_bill'] = None
790 item.save()
791 if button_pressed == wx.ID_NO:
792 gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item'])
793
794 if delete_invoice:
795 if bill['pk_doc'] is not None:
796 gmDocuments.delete_document (
797 document_id = bill['pk_doc'],
798 encounter_id = gmPerson.cPatient(aPK_obj = pk_patient).emr.active_encounter['pk_encounter']
799 )
800
801
802 if len(bill.bill_items) == 0:
803 gmBilling.delete_bill(pk_bill = bill['pk_bill'])
804
805 return True
806
807
809
810 if parent is None:
811 parent = wx.GetApp().GetTopWindow()
812
813
814 def show_pdf(bill):
815 if bill is None:
816 return False
817
818
819 invoice = bill.invoice
820 if invoice is not None:
821 success, msg = invoice.parts[-1].display_via_mime()
822 if not success:
823 gmGuiHelpers.gm_show_error(aMessage = msg, aTitle = _('Displaying invoice'))
824 return False
825
826
827 create_it = gmGuiHelpers.gm_show_question (
828 title = _('Displaying invoice'),
829 question = _(
830 'Cannot find an existing\n'
831 'invoice PDF for this bill.\n'
832 '\n'
833 'Do you want to create one ?'
834 ),
835 )
836 if not create_it:
837 return False
838
839
840 if not bill.set_missing_address_from_default():
841 gmGuiHelpers.gm_show_warning (
842 aTitle = _('Creating invoice'),
843 aMessage = _(
844 'There is no pre-configured billing address.\n'
845 '\n'
846 'Select the address you want to send the bill to.'
847 )
848 )
849 edit_bill(parent = parent, bill = bill, single_entry = True)
850 if bill['pk_receiver_address'] is None:
851 return False
852 if bill['close_date'] is None:
853 bill['close_date'] = gmDateTime.pydt_now_here()
854 bill.save()
855
856 return create_invoice_from_bill(parent = parent, bill = bill, print_it = True, keep_a_copy = True)
857
858 def edit(bill):
859 return edit_bill(parent = parent, bill = bill, single_entry = True)
860
861 def delete(bill):
862 return delete_bill(parent = parent, bill = bill)
863
864 def remove_items(bill):
865 return remove_items_from_bill(parent = parent, bill = bill)
866
867 def get_tooltip(item):
868 if item is None:
869 return None
870 return item.format()
871
872 def refresh(lctrl):
873 if patient is None:
874 bills = gmBilling.get_bills()
875 else:
876 bills = gmBilling.get_bills(pk_patient = patient.ID)
877 items = []
878 for b in bills:
879 if b['close_date'] is None:
880 close_date = _('<open>')
881 else:
882 close_date = gmDateTime.pydt_strftime(b['close_date'], '%Y %b %d')
883 if b['total_amount'] is None:
884 amount = _('no items on bill')
885 else:
886 amount = gmTools.bool2subst (
887 b['apply_vat'],
888 _('%(currency)s%(total_amount_with_vat)s (with %(percent_vat)s%% VAT)') % b,
889 '%(currency)s%(total_amount)s' % b,
890 _('without VAT: %(currency)s%(total_amount)s / with %(percent_vat)s%% VAT: %(currency)s%(total_amount_with_vat)s') % b
891 )
892 items.append ([
893 close_date,
894 b['invoice_id'],
895 amount,
896 gmTools.coalesce(b['comment'], '')
897 ])
898 lctrl.set_string_items(items)
899 lctrl.set_data(bills)
900
901 return gmListWidgets.get_choices_from_list (
902 parent = parent,
903 caption = _('Showing bills.'),
904 columns = [_('Close date'), _('Invoice ID'), _('Value'), _('Comment')],
905 single_selection = True,
906 edit_callback = edit,
907 delete_callback = delete,
908 refresh_callback = refresh,
909 middle_extra_button = (
910 'PDF',
911 _('Create if necessary, and show the corresponding invoice PDF'),
912 show_pdf
913 ),
914 right_extra_button = (
915 _('Unbill'),
916 _('Select and remove items from a bill.'),
917 remove_items
918 ),
919 list_tooltip_callback = get_tooltip
920 )
921
922
923 from Gnumed.wxGladeWidgets import wxgBillEAPnl
924
925 -class cBillEAPnl(wxgBillEAPnl.wxgBillEAPnl, gmEditArea.cGenericEditAreaMixin):
926
928
929 try:
930 data = kwargs['bill']
931 del kwargs['bill']
932 except KeyError:
933 data = None
934
935 wxgBillEAPnl.wxgBillEAPnl.__init__(self, *args, **kwargs)
936 gmEditArea.cGenericEditAreaMixin.__init__(self)
937
938 self.mode = 'new'
939 self.data = data
940 if data is not None:
941 self.mode = 'edit'
942
943 self._3state2bool = {
944 wx.CHK_UNCHECKED: False,
945 wx.CHK_CHECKED: True,
946 wx.CHK_UNDETERMINED: None
947 }
948 self.bool_to_3state = {
949 False: wx.CHK_UNCHECKED,
950 True: wx.CHK_CHECKED,
951 None: wx.CHK_UNDETERMINED
952 }
953
954
955
956
957
958
959
961 validity = True
962
963
964 if not self._PRW_close_date.is_valid_timestamp(empty_is_valid = False):
965 self._PRW_close_date.SetFocus()
966
967
968 if self._CHBOX_vat_applies.ThreeStateValue == wx.CHK_UNDETERMINED:
969 self._CHBOX_vat_applies.SetFocus()
970 self._CHBOX_vat_applies.SetBackgroundColour('yellow')
971
972 return validity
973
977
979 self.data['close_date'] = self._PRW_close_date.GetData()
980 self.data['apply_vat'] = self._3state2bool[self._CHBOX_vat_applies.ThreeStateValue]
981 self.data['comment'] = self._TCTRL_comment.GetValue()
982 self.data.save()
983 return True
984
987
989 self._refresh_as_new()
990
1020
1021
1022
1024 if self._CHBOX_vat_applies.ThreeStateValue == wx.CHK_CHECKED:
1025 tmp = '%s %%(currency)s%%(total_vat)s %s %s %%(currency)s%%(total_amount_with_vat)s' % (
1026 gmTools.u_corresponds_to,
1027 gmTools.u_arrow2right,
1028 gmTools.u_sum,
1029 )
1030 self._TCTRL_value_with_vat.SetValue(tmp % self.data)
1031 return
1032 if self._CHBOX_vat_applies.ThreeStateValue == wx.CHK_UNDETERMINED:
1033 self._TCTRL_value_with_vat.SetValue('?')
1034 return
1035 self._TCTRL_value_with_vat.SetValue('')
1036
1051
1052
1053
1054
1056
1057 if bill_item is not None:
1058 if bill_item.is_in_use:
1059 gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit already invoiced bill item.'), beep = True)
1060 return False
1061
1062 ea = cBillItemEAPnl(parent, -1)
1063 ea.data = bill_item
1064 ea.mode = gmTools.coalesce(bill_item, 'new', 'edit')
1065 dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea, single_entry = single_entry)
1066 dlg.SetTitle(gmTools.coalesce(bill_item, _('Adding new bill item'), _('Editing bill item')))
1067 if dlg.ShowModal() == wx.ID_OK:
1068 dlg.DestroyLater()
1069 return True
1070 dlg.DestroyLater()
1071 return False
1072
1080
1081 def delete(item):
1082 if item.is_in_use is not None:
1083 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete already invoiced bill items.'), beep = True)
1084 return False
1085 gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item'])
1086 return True
1087
1088 def get_tooltip(item):
1089 if item is None:
1090 return None
1091 return item.format()
1092
1093 def refresh(lctrl):
1094 b_items = gmBilling.get_bill_items(pk_patient = pk_patient)
1095 items = [ [
1096 gmDateTime.pydt_strftime(b['date_to_bill'], '%Y %b %d', accuracy = gmDateTime.acc_days),
1097 b['unit_count'],
1098 '%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], '', ' - %s')),
1099 b['currency'],
1100 '%s (%s %s %s%s%s)' % (
1101 b['total_amount'],
1102 b['unit_count'],
1103 gmTools.u_multiply,
1104 b['net_amount_per_unit'],
1105 gmTools.u_multiply,
1106 b['amount_multiplier']
1107 ),
1108 '%s (%s%%)' % (
1109 b['vat'],
1110 b['vat_multiplier'] * 100
1111 ),
1112 '%s (%s)' % (b['catalog_short'], b['catalog_version']),
1113 b['pk_bill_item']
1114 ] for b in b_items ]
1115 lctrl.set_string_items(items)
1116 lctrl.set_data(b_items)
1117
1118 gmListWidgets.get_choices_from_list (
1119 parent = parent,
1120
1121 caption = _('Showing bill items.'),
1122 columns = [_('Date'), _('Count'), _('Description'), _('$__replace_by_your_currency_symbol')[:-len('__replace_by_your_currency_symbol')], _('Value'), _('VAT'), _('Catalog'), '#'],
1123 single_selection = True,
1124 new_callback = edit,
1125 edit_callback = edit,
1126 delete_callback = delete,
1127 refresh_callback = refresh,
1128 list_tooltip_callback = get_tooltip
1129 )
1130
1131
1133 """A list for managing a patient's bill items.
1134
1135 Does NOT act on/listen to the current patient.
1136 """
1156
1157
1158
1159 - def refresh(self, *args, **kwargs):
1160 if self.__identity is None:
1161 self._LCTRL_items.set_string_items()
1162 return
1163
1164 b_items = gmBilling.get_bill_items(pk_patient = self.__identity.ID, non_invoiced_only = self.__show_non_invoiced_only)
1165 items = [ [
1166 gmDateTime.pydt_strftime(b['date_to_bill'], '%Y %b %d', accuracy = gmDateTime.acc_days),
1167 b['unit_count'],
1168 '%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], '', ' - %s')),
1169 b['currency'],
1170 b['total_amount'],
1171 '%s (%s%%)' % (
1172 b['vat'],
1173 b['vat_multiplier'] * 100
1174 ),
1175 '%s (%s)' % (b['catalog_short'], b['catalog_version']),
1176 '%s %s %s %s %s' % (
1177 b['unit_count'],
1178 gmTools.u_multiply,
1179 b['net_amount_per_unit'],
1180 gmTools.u_multiply,
1181 b['amount_multiplier']
1182 ),
1183 gmTools.coalesce(b['pk_bill'], gmTools.u_diameter),
1184 b['pk_encounter_to_bill'],
1185 b['pk_bill_item']
1186 ] for b in b_items ]
1187
1188 self._LCTRL_items.set_string_items(items = items)
1189 self._LCTRL_items.set_column_widths()
1190 self._LCTRL_items.set_data(data = b_items)
1191
1192
1193
1195 self._LCTRL_items.set_columns(columns = [
1196 _('Charge date'),
1197 _('Count'),
1198 _('Description'),
1199 _('$__replace_by_your_currency_symbol')[:-len('__replace_by_your_currency_symbol')],
1200 _('Value'),
1201 _('VAT'),
1202 _('Catalog'),
1203 _('Count %s Value %s Factor') % (gmTools.u_multiply, gmTools.u_multiply),
1204 _('Invoice'),
1205 _('Encounter'),
1206 '#'
1207 ])
1208 self._LCTRL_items.item_tooltip_callback = self._get_item_tooltip
1209
1210
1211
1212
1213
1214 self.left_extra_button = (
1215 _('Invoice selected items'),
1216 _('Create invoice from selected items.'),
1217 self._invoice_selected_items
1218 )
1219 self.middle_extra_button = (
1220 _('Bills'),
1221 _('Browse bills of this patient.'),
1222 self._browse_bills
1223 )
1224 self.right_extra_button = (
1225 _('Billables'),
1226 _('Browse list of billables.'),
1227 self._browse_billables
1228 )
1229
1232
1235
1237 if item['pk_bill'] is not None:
1238 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete already invoiced bill items.'), beep = True)
1239 return False
1240 go_ahead = gmGuiHelpers.gm_show_question (
1241 _( 'Do you really want to delete this\n'
1242 'bill item from the patient ?'),
1243 _('Deleting bill item')
1244 )
1245 if not go_ahead:
1246 return False
1247 gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item'])
1248 return True
1249
1254
1257
1277
1281
1284
1285
1286
1288 return self.__identity
1289
1293
1294 identity = property(_get_identity, _set_identity)
1295
1297 return self.__show_non_invoiced_only
1298
1300 self.__show_non_invoiced_only = value
1301 self.refresh()
1302
1303 show_non_invoiced_only = property(_get_show_non_invoiced_only, _set_show_non_invoiced_only)
1304
1305
1306 from Gnumed.wxGladeWidgets import wxgBillItemEAPnl
1307
1308 -class cBillItemEAPnl(wxgBillItemEAPnl.wxgBillItemEAPnl, gmEditArea.cGenericEditAreaMixin):
1309
1327
1332
1333
1334
1336
1337 validity = True
1338
1339 if self._TCTRL_factor.GetValue().strip() == '':
1340 validity = False
1341 self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = False)
1342 self._TCTRL_factor.SetFocus()
1343 else:
1344 converted, factor = gmTools.input2decimal(self._TCTRL_factor.GetValue())
1345 if not converted:
1346 validity = False
1347 self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = False)
1348 self._TCTRL_factor.SetFocus()
1349 else:
1350 self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = True)
1351
1352 if self._TCTRL_amount.GetValue().strip() == '':
1353 validity = False
1354 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False)
1355 self._TCTRL_amount.SetFocus()
1356 else:
1357 converted, factor = gmTools.input2decimal(self._TCTRL_amount.GetValue())
1358 if not converted:
1359 validity = False
1360 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False)
1361 self._TCTRL_amount.SetFocus()
1362 else:
1363 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = True)
1364
1365 if self._TCTRL_count.GetValue().strip() == '':
1366 validity = False
1367 self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = False)
1368 self._TCTRL_count.SetFocus()
1369 else:
1370 converted, factor = gmTools.input2decimal(self._TCTRL_count.GetValue())
1371 if not converted:
1372 validity = False
1373 self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = False)
1374 self._TCTRL_count.SetFocus()
1375 else:
1376 self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = True)
1377
1378 if self._PRW_date.is_valid_timestamp(empty_is_valid = True):
1379 self._PRW_date.display_as_valid(True)
1380 else:
1381 validity = False
1382 self._PRW_date.display_as_valid(False)
1383 self._PRW_date.SetFocus()
1384
1385 if self._PRW_encounter.GetData() is None:
1386 validity = False
1387 self._PRW_encounter.display_as_valid(False)
1388 self._PRW_encounter.SetFocus()
1389 else:
1390 self._PRW_encounter.display_as_valid(True)
1391
1392 if self._PRW_billable.GetData() is None:
1393 validity = False
1394 self._PRW_billable.display_as_valid(False)
1395 self._PRW_billable.SetFocus()
1396 else:
1397 self._PRW_billable.display_as_valid(True)
1398
1399 return validity
1400
1416
1425
1438
1447
1460
1462 if item is None:
1463 return
1464 if self._TCTRL_amount.GetValue().strip() != '':
1465 return
1466 val = '%s' % self._PRW_billable.GetData(as_instance = True)['raw_amount']
1467 wx.CallAfter(self._TCTRL_amount.SetValue, val)
1468
1470 if self._PRW_billable.GetData() is None:
1471 wx.CallAfter(self._TCTRL_amount.SetValue, '')
1472
1473
1474
1475
1476 from Gnumed.wxGladeWidgets import wxgBillingPluginPnl
1477
1478 -class cBillingPluginPnl(wxgBillingPluginPnl.wxgBillingPluginPnl, gmRegetMixin.cRegetOnPaintMixin):
1484
1486 self._PNL_bill_items.identity = None
1487 self._CHBOX_show_non_invoiced_only.SetValue(1)
1488 self._PRW_billable.SetText('', None)
1489 self._TCTRL_factor.SetValue('1.0')
1490 self._TCTRL_factor.Disable()
1491 self._TCTRL_details.SetValue('')
1492 self._TCTRL_details.Disable()
1493
1494
1495
1503
1506
1508 self._schedule_data_reget()
1509
1511 self._schedule_data_reget()
1512
1515
1546
1548 if billable is None:
1549 self._TCTRL_factor.Disable()
1550 self._TCTRL_details.Disable()
1551 self._BTN_insert_item.Disable()
1552 else:
1553 self._TCTRL_factor.Enable()
1554 self._TCTRL_details.Enable()
1555 self._BTN_insert_item.Enable()
1556
1557
1558
1562
1563
1564
1565 if __name__ == '__main__':
1566
1567 if len(sys.argv) < 2:
1568 sys.exit()
1569
1570 if sys.argv[1] != 'test':
1571 sys.exit()
1572
1573 from Gnumed.pycommon import gmI18N
1574 gmI18N.activate_locale()
1575 gmI18N.install_domain(domain = 'gnumed')
1576
1577
1578 app = wx.PyWidgetTester(size = (600, 600))
1579
1580 app.MainLoop()
1581