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 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
432
433 -def edit_bill(parent=None, bill=None, single_entry=False):
449
450
525
526
528
529 bill_patient_not_active = False
530
531 curr_pat = gmPerson.gmCurrentPatient()
532 if curr_pat.connected:
533
534
535
536 if curr_pat.ID != bill['pk_patient']:
537 bill_patient_not_active = True
538 else:
539 bill_patient_not_active = True
540
541
542
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
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
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
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
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
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
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
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
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
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
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
812
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
829 if len(bill.bill_items) == 0:
830 gmBilling.delete_bill(pk_bill = bill['pk_bill'])
831
832 return True
833
834
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
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
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
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
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
982
983
984
985
986
988 validity = True
989
990
991 if not self._PRW_close_date.is_valid_timestamp(empty_is_valid = False):
992 self._PRW_close_date.SetFocus()
993
994
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
1004
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
1014
1016 self._refresh_as_new()
1017
1047
1048
1049
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
1078
1079
1080
1081
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
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
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
1160 """A list for managing a patient's bill items.
1161
1162 Does NOT act on/listen to the current patient.
1163 """
1183
1184
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
1220
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
1237
1238
1239
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
1259
1262
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
1281
1284
1304
1308
1311
1312
1313
1315 return self.__identity
1316
1320
1321 identity = property(_get_identity, _set_identity)
1322
1324 return self.__show_non_invoiced_only
1325
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
1354
1359
1360
1361
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
1443
1452
1465
1474
1487
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
1497 if self._PRW_billable.GetData() is None:
1498 wx.CallAfter(self._TCTRL_amount.SetValue, '')
1499
1500
1501
1502
1503 from Gnumed.wxGladeWidgets import wxgBillingPluginPnl
1504
1505 -class cBillingPluginPnl(wxgBillingPluginPnl.wxgBillingPluginPnl, gmRegetMixin.cRegetOnPaintMixin):
1511
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
1522
1530
1533
1535 self._schedule_data_reget()
1536
1538 self._schedule_data_reget()
1539
1542
1573
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
1585
1589
1590
1591
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
1608 app.MainLoop()
1609