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 date_generated = gmDateTime.pydt_now_here()
655 )
656 bill['pk_doc'] = doc['pk_doc']
657 bill.save()
658
659 if not print_it:
660 return True
661
662
663 _cfg = gmCfg2.gmCfgData()
664 printed = gmPrinting.print_files(filenames = [pdf_name], jobtype = 'invoice', verbose = _cfg.get(option = 'debug'))
665 if not printed:
666 gmGuiHelpers.gm_show_error (
667 aMessage = _('Error printing the invoice.'),
668 aTitle = _('Printing invoice')
669 )
670 return True
671
672 return True
673
674
676
677 if parent is None:
678 parent = wx.GetApp().GetTopWindow()
679
680 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
681 parent, -1,
682 caption = _('Deleting bill'),
683 question = _(
684 'When deleting the bill [%s]\n'
685 'do you want to keep its items (effectively \"unbilling\" them)\n'
686 'or do you want to also delete the bill items from the patient ?\n'
687 ) % bill['invoice_id'],
688 button_defs = [
689 {'label': _('Delete + keep'), 'tooltip': _('Delete the bill but keep ("unbill") its items.'), 'default': True},
690 {'label': _('Delete all'), 'tooltip': _('Delete both the bill and its items from the patient.')}
691 ],
692 show_checkbox = True,
693 checkbox_msg = _('Also remove invoice PDF'),
694 checkbox_tooltip = _('Also remove the invoice PDF from the document archive (because it will not correspond to the bill anymore).')
695 )
696 button_pressed = dlg.ShowModal()
697 delete_invoice = dlg.checkbox_is_checked()
698 dlg.DestroyLater()
699
700 if button_pressed == wx.ID_CANCEL:
701 return False
702
703 delete_items = (button_pressed == wx.ID_NO)
704
705 if delete_invoice:
706 if bill['pk_doc'] is not None:
707 gmDocuments.delete_document (
708 document_id = bill['pk_doc'],
709 encounter_id = gmPerson.cPatient(aPK_obj = bill['pk_patient']).emr.active_encounter['pk_encounter']
710 )
711
712 items = bill['pk_bill_items']
713 success = gmBilling.delete_bill(pk_bill = bill['pk_bill'])
714 if delete_items:
715 for item in items:
716 gmBilling.delete_bill_item(pk_bill_item = item)
717
718 return success
719
720
722
723 if bill is None:
724 return False
725
726 list_data = bill.bill_items
727 if len(list_data) == 0:
728 return False
729
730 if parent is None:
731 parent = wx.GetApp().GetTopWindow()
732
733 list_items = [ [
734 gmDateTime.pydt_strftime(b['date_to_bill'], '%Y %b %d', accuracy = gmDateTime.acc_days),
735 b['unit_count'],
736 '%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], '', ' - %s')),
737 '%(curr)s %(total_val)s (%(count)s %(x)s %(unit_val)s%(x)s%(val_multiplier)s)' % {
738 'curr': b['currency'],
739 'total_val': b['total_amount'],
740 'count': b['unit_count'],
741 'x': gmTools.u_multiply,
742 'unit_val': b['net_amount_per_unit'],
743 'val_multiplier': b['amount_multiplier']
744 },
745 '%(curr)s%(vat)s (%(perc_vat)s%%)' % {
746 'vat': b['vat'],
747 'curr': b['currency'],
748 'perc_vat': b['vat_multiplier'] * 100
749 },
750 '%s (%s)' % (b['catalog_short'], b['catalog_version']),
751 b['pk_bill_item']
752 ] for b in list_data ]
753
754 msg = _('Select the items you want to remove from bill [%s]:\n') % bill['invoice_id']
755 items2remove = gmListWidgets.get_choices_from_list (
756 parent = parent,
757 msg = msg,
758 caption = _('Removing items from bill'),
759 columns = [_('Date'), _('Count'), _('Description'), _('Value'), _('VAT'), _('Catalog'), '#'],
760 single_selection = False,
761 choices = list_items,
762 data = list_data
763 )
764
765 if items2remove is None:
766 return False
767
768 if len(items2remove) == len(list_items):
769 gmGuiHelpers.gm_show_info (
770 title = _('Removing items from bill'),
771 info = _(
772 'Cannot remove all items from a bill because\n'
773 'GNUmed does not support empty bills.\n'
774 '\n'
775 'You must delete the bill itself if you want to\n'
776 'remove all items (at which point you can opt to\n'
777 'keep the items and only delete the bill).'
778 )
779 )
780 return False
781
782 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
783 parent, -1,
784 caption = _('Removing items from bill'),
785 question = _(
786 '%s items selected from bill [%s]\n'
787 '\n'
788 'Do you want to only remove the selected items\n'
789 'from the bill ("unbill" them) or do you want\n'
790 'to delete them entirely from the patient ?\n'
791 '\n'
792 'Note that neither action is reversible.'
793 ) % (
794 len(items2remove),
795 bill['invoice_id']
796 ),
797 button_defs = [
798 {'label': _('"Unbill"'), 'tooltip': _('Only "unbill" items (remove from bill but do not delete from patient).'), 'default': True},
799 {'label': _('Delete'), 'tooltip': _('Completely delete items from the patient.')}
800 ],
801 show_checkbox = True,
802 checkbox_msg = _('Also remove invoice PDF'),
803 checkbox_tooltip = _('Also remove the invoice PDF from the document archive (because it will not correspond to the bill anymore).')
804 )
805 button_pressed = dlg.ShowModal()
806 delete_invoice = dlg.checkbox_is_checked()
807 dlg.DestroyLater()
808
809 if button_pressed == wx.ID_CANCEL:
810 return False
811
812
813
814 pk_patient = bill['pk_patient']
815
816 for item in items2remove:
817 item['pk_bill'] = None
818 item.save()
819 if button_pressed == wx.ID_NO:
820 gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item'])
821
822 if delete_invoice:
823 if bill['pk_doc'] is not None:
824 gmDocuments.delete_document (
825 document_id = bill['pk_doc'],
826 encounter_id = gmPerson.cPatient(aPK_obj = pk_patient).emr.active_encounter['pk_encounter']
827 )
828
829
830 if len(bill.bill_items) == 0:
831 gmBilling.delete_bill(pk_bill = bill['pk_bill'])
832
833 return True
834
835
837
838 if parent is None:
839 parent = wx.GetApp().GetTopWindow()
840
841
842 def show_pdf(bill):
843 if bill is None:
844 return False
845
846
847 invoice = bill.invoice
848 if invoice is not None:
849 success, msg = invoice.parts[-1].display_via_mime()
850 if not success:
851 gmGuiHelpers.gm_show_error(aMessage = msg, aTitle = _('Displaying invoice'))
852 return False
853
854
855 create_it = gmGuiHelpers.gm_show_question (
856 title = _('Displaying invoice'),
857 question = _(
858 'Cannot find an existing\n'
859 'invoice PDF for this bill.\n'
860 '\n'
861 'Do you want to create one ?'
862 ),
863 )
864 if not create_it:
865 return False
866
867
868 if not bill.set_missing_address_from_default():
869 gmGuiHelpers.gm_show_warning (
870 aTitle = _('Creating invoice'),
871 aMessage = _(
872 'There is no pre-configured billing address.\n'
873 '\n'
874 'Select the address you want to send the bill to.'
875 )
876 )
877 edit_bill(parent = parent, bill = bill, single_entry = True)
878 if bill['pk_receiver_address'] is None:
879 return False
880 if bill['close_date'] is None:
881 bill['close_date'] = gmDateTime.pydt_now_here()
882 bill.save()
883
884 return create_invoice_from_bill(parent = parent, bill = bill, print_it = True, keep_a_copy = True)
885
886 def edit(bill):
887 return edit_bill(parent = parent, bill = bill, single_entry = True)
888
889 def delete(bill):
890 return delete_bill(parent = parent, bill = bill)
891
892 def remove_items(bill):
893 return remove_items_from_bill(parent = parent, bill = bill)
894
895 def get_tooltip(item):
896 if item is None:
897 return None
898 return item.format()
899
900 def refresh(lctrl):
901 if patient is None:
902 bills = gmBilling.get_bills()
903 else:
904 bills = gmBilling.get_bills(pk_patient = patient.ID)
905 items = []
906 for b in bills:
907 if b['close_date'] is None:
908 close_date = _('<open>')
909 else:
910 close_date = gmDateTime.pydt_strftime(b['close_date'], '%Y %b %d')
911 if b['total_amount'] is None:
912 amount = _('no items on bill')
913 else:
914 amount = gmTools.bool2subst (
915 b['apply_vat'],
916 _('%(currency)s%(total_amount_with_vat)s (with %(percent_vat)s%% VAT)') % b,
917 '%(currency)s%(total_amount)s' % b,
918 _('without VAT: %(currency)s%(total_amount)s / with %(percent_vat)s%% VAT: %(currency)s%(total_amount_with_vat)s') % b
919 )
920 items.append ([
921 close_date,
922 b['invoice_id'],
923 amount,
924 gmTools.coalesce(b['comment'], '')
925 ])
926 lctrl.set_string_items(items)
927 lctrl.set_data(bills)
928
929 return gmListWidgets.get_choices_from_list (
930 parent = parent,
931 caption = _('Showing bills.'),
932 columns = [_('Close date'), _('Invoice ID'), _('Value'), _('Comment')],
933 single_selection = True,
934 edit_callback = edit,
935 delete_callback = delete,
936 refresh_callback = refresh,
937 middle_extra_button = (
938 'PDF',
939 _('Create if necessary, and show the corresponding invoice PDF'),
940 show_pdf
941 ),
942 right_extra_button = (
943 _('Unbill'),
944 _('Select and remove items from a bill.'),
945 remove_items
946 ),
947 list_tooltip_callback = get_tooltip
948 )
949
950
951 from Gnumed.wxGladeWidgets import wxgBillEAPnl
952
953 -class cBillEAPnl(wxgBillEAPnl.wxgBillEAPnl, gmEditArea.cGenericEditAreaMixin):
954
956
957 try:
958 data = kwargs['bill']
959 del kwargs['bill']
960 except KeyError:
961 data = None
962
963 wxgBillEAPnl.wxgBillEAPnl.__init__(self, *args, **kwargs)
964 gmEditArea.cGenericEditAreaMixin.__init__(self)
965
966 self.mode = 'new'
967 self.data = data
968 if data is not None:
969 self.mode = 'edit'
970
971 self._3state2bool = {
972 wx.CHK_UNCHECKED: False,
973 wx.CHK_CHECKED: True,
974 wx.CHK_UNDETERMINED: None
975 }
976 self.bool_to_3state = {
977 False: wx.CHK_UNCHECKED,
978 True: wx.CHK_CHECKED,
979 None: wx.CHK_UNDETERMINED
980 }
981
982
983
984
985
986
987
989 validity = True
990
991
992 if not self._PRW_close_date.is_valid_timestamp(empty_is_valid = False):
993 self._PRW_close_date.SetFocus()
994
995
996 if self._CHBOX_vat_applies.ThreeStateValue == wx.CHK_UNDETERMINED:
997 self._CHBOX_vat_applies.SetFocus()
998 self._CHBOX_vat_applies.SetBackgroundColour('yellow')
999
1000 return validity
1001
1005
1007 self.data['close_date'] = self._PRW_close_date.GetData()
1008 self.data['apply_vat'] = self._3state2bool[self._CHBOX_vat_applies.ThreeStateValue]
1009 self.data['comment'] = self._TCTRL_comment.GetValue()
1010 self.data.save()
1011 return True
1012
1015
1017 self._refresh_as_new()
1018
1048
1049
1050
1052 if self._CHBOX_vat_applies.ThreeStateValue == wx.CHK_CHECKED:
1053 tmp = '%s %%(currency)s%%(total_vat)s %s %s %%(currency)s%%(total_amount_with_vat)s' % (
1054 gmTools.u_corresponds_to,
1055 gmTools.u_arrow2right,
1056 gmTools.u_sum,
1057 )
1058 self._TCTRL_value_with_vat.SetValue(tmp % self.data)
1059 return
1060 if self._CHBOX_vat_applies.ThreeStateValue == wx.CHK_UNDETERMINED:
1061 self._TCTRL_value_with_vat.SetValue('?')
1062 return
1063 self._TCTRL_value_with_vat.SetValue('')
1064
1079
1080
1081
1082
1084
1085 if bill_item is not None:
1086 if bill_item.is_in_use:
1087 gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit already invoiced bill item.'), beep = True)
1088 return False
1089
1090 ea = cBillItemEAPnl(parent, -1)
1091 ea.data = bill_item
1092 ea.mode = gmTools.coalesce(bill_item, 'new', 'edit')
1093 dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea, single_entry = single_entry)
1094 dlg.SetTitle(gmTools.coalesce(bill_item, _('Adding new bill item'), _('Editing bill item')))
1095 if dlg.ShowModal() == wx.ID_OK:
1096 dlg.DestroyLater()
1097 return True
1098 dlg.DestroyLater()
1099 return False
1100
1108
1109 def delete(item):
1110 if item.is_in_use is not None:
1111 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete already invoiced bill items.'), beep = True)
1112 return False
1113 gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item'])
1114 return True
1115
1116 def get_tooltip(item):
1117 if item is None:
1118 return None
1119 return item.format()
1120
1121 def refresh(lctrl):
1122 b_items = gmBilling.get_bill_items(pk_patient = pk_patient)
1123 items = [ [
1124 gmDateTime.pydt_strftime(b['date_to_bill'], '%Y %b %d', accuracy = gmDateTime.acc_days),
1125 b['unit_count'],
1126 '%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], '', ' - %s')),
1127 b['currency'],
1128 '%s (%s %s %s%s%s)' % (
1129 b['total_amount'],
1130 b['unit_count'],
1131 gmTools.u_multiply,
1132 b['net_amount_per_unit'],
1133 gmTools.u_multiply,
1134 b['amount_multiplier']
1135 ),
1136 '%s (%s%%)' % (
1137 b['vat'],
1138 b['vat_multiplier'] * 100
1139 ),
1140 '%s (%s)' % (b['catalog_short'], b['catalog_version']),
1141 b['pk_bill_item']
1142 ] for b in b_items ]
1143 lctrl.set_string_items(items)
1144 lctrl.set_data(b_items)
1145
1146 gmListWidgets.get_choices_from_list (
1147 parent = parent,
1148
1149 caption = _('Showing bill items.'),
1150 columns = [_('Date'), _('Count'), _('Description'), _('$__replace_by_your_currency_symbol')[:-len('__replace_by_your_currency_symbol')], _('Value'), _('VAT'), _('Catalog'), '#'],
1151 single_selection = True,
1152 new_callback = edit,
1153 edit_callback = edit,
1154 delete_callback = delete,
1155 refresh_callback = refresh,
1156 list_tooltip_callback = get_tooltip
1157 )
1158
1159
1161 """A list for managing a patient's bill items.
1162
1163 Does NOT act on/listen to the current patient.
1164 """
1184
1185
1186
1187 - def refresh(self, *args, **kwargs):
1188 if self.__identity is None:
1189 self._LCTRL_items.set_string_items()
1190 return
1191
1192 b_items = gmBilling.get_bill_items(pk_patient = self.__identity.ID, non_invoiced_only = self.__show_non_invoiced_only)
1193 items = [ [
1194 gmDateTime.pydt_strftime(b['date_to_bill'], '%Y %b %d', accuracy = gmDateTime.acc_days),
1195 b['unit_count'],
1196 '%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], '', ' - %s')),
1197 b['currency'],
1198 b['total_amount'],
1199 '%s (%s%%)' % (
1200 b['vat'],
1201 b['vat_multiplier'] * 100
1202 ),
1203 '%s (%s)' % (b['catalog_short'], b['catalog_version']),
1204 '%s %s %s %s %s' % (
1205 b['unit_count'],
1206 gmTools.u_multiply,
1207 b['net_amount_per_unit'],
1208 gmTools.u_multiply,
1209 b['amount_multiplier']
1210 ),
1211 gmTools.coalesce(b['pk_bill'], gmTools.u_diameter),
1212 b['pk_encounter_to_bill'],
1213 b['pk_bill_item']
1214 ] for b in b_items ]
1215
1216 self._LCTRL_items.set_string_items(items = items)
1217 self._LCTRL_items.set_column_widths()
1218 self._LCTRL_items.set_data(data = b_items)
1219
1220
1221
1223 self._LCTRL_items.set_columns(columns = [
1224 _('Charge date'),
1225 _('Count'),
1226 _('Description'),
1227 _('$__replace_by_your_currency_symbol')[:-len('__replace_by_your_currency_symbol')],
1228 _('Value'),
1229 _('VAT'),
1230 _('Catalog'),
1231 _('Count %s Value %s Factor') % (gmTools.u_multiply, gmTools.u_multiply),
1232 _('Invoice'),
1233 _('Encounter'),
1234 '#'
1235 ])
1236 self._LCTRL_items.item_tooltip_callback = self._get_item_tooltip
1237
1238
1239
1240
1241
1242 self.left_extra_button = (
1243 _('Invoice selected items'),
1244 _('Create invoice from selected items.'),
1245 self._invoice_selected_items
1246 )
1247 self.middle_extra_button = (
1248 _('Bills'),
1249 _('Browse bills of this patient.'),
1250 self._browse_bills
1251 )
1252 self.right_extra_button = (
1253 _('Billables'),
1254 _('Browse list of billables.'),
1255 self._browse_billables
1256 )
1257
1260
1263
1265 if item['pk_bill'] is not None:
1266 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete already invoiced bill items.'), beep = True)
1267 return False
1268 go_ahead = gmGuiHelpers.gm_show_question (
1269 _( 'Do you really want to delete this\n'
1270 'bill item from the patient ?'),
1271 _('Deleting bill item')
1272 )
1273 if not go_ahead:
1274 return False
1275 gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item'])
1276 return True
1277
1282
1285
1305
1309
1312
1313
1314
1316 return self.__identity
1317
1321
1322 identity = property(_get_identity, _set_identity)
1323
1325 return self.__show_non_invoiced_only
1326
1328 self.__show_non_invoiced_only = value
1329 self.refresh()
1330
1331 show_non_invoiced_only = property(_get_show_non_invoiced_only, _set_show_non_invoiced_only)
1332
1333
1334 from Gnumed.wxGladeWidgets import wxgBillItemEAPnl
1335
1336 -class cBillItemEAPnl(wxgBillItemEAPnl.wxgBillItemEAPnl, gmEditArea.cGenericEditAreaMixin):
1337
1355
1360
1361
1362
1364
1365 validity = True
1366
1367 if self._TCTRL_factor.GetValue().strip() == '':
1368 validity = False
1369 self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = False)
1370 self._TCTRL_factor.SetFocus()
1371 else:
1372 converted, factor = gmTools.input2decimal(self._TCTRL_factor.GetValue())
1373 if not converted:
1374 validity = False
1375 self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = False)
1376 self._TCTRL_factor.SetFocus()
1377 else:
1378 self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = True)
1379
1380 if self._TCTRL_amount.GetValue().strip() == '':
1381 validity = False
1382 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False)
1383 self._TCTRL_amount.SetFocus()
1384 else:
1385 converted, factor = gmTools.input2decimal(self._TCTRL_amount.GetValue())
1386 if not converted:
1387 validity = False
1388 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False)
1389 self._TCTRL_amount.SetFocus()
1390 else:
1391 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = True)
1392
1393 if self._TCTRL_count.GetValue().strip() == '':
1394 validity = False
1395 self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = False)
1396 self._TCTRL_count.SetFocus()
1397 else:
1398 converted, factor = gmTools.input2decimal(self._TCTRL_count.GetValue())
1399 if not converted:
1400 validity = False
1401 self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = False)
1402 self._TCTRL_count.SetFocus()
1403 else:
1404 self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = True)
1405
1406 if self._PRW_date.is_valid_timestamp(empty_is_valid = True):
1407 self._PRW_date.display_as_valid(True)
1408 else:
1409 validity = False
1410 self._PRW_date.display_as_valid(False)
1411 self._PRW_date.SetFocus()
1412
1413 if self._PRW_encounter.GetData() is None:
1414 validity = False
1415 self._PRW_encounter.display_as_valid(False)
1416 self._PRW_encounter.SetFocus()
1417 else:
1418 self._PRW_encounter.display_as_valid(True)
1419
1420 if self._PRW_billable.GetData() is None:
1421 validity = False
1422 self._PRW_billable.display_as_valid(False)
1423 self._PRW_billable.SetFocus()
1424 else:
1425 self._PRW_billable.display_as_valid(True)
1426
1427 return validity
1428
1444
1453
1466
1475
1488
1490 if item is None:
1491 return
1492 if self._TCTRL_amount.GetValue().strip() != '':
1493 return
1494 val = '%s' % self._PRW_billable.GetData(as_instance = True)['raw_amount']
1495 wx.CallAfter(self._TCTRL_amount.SetValue, val)
1496
1498 if self._PRW_billable.GetData() is None:
1499 wx.CallAfter(self._TCTRL_amount.SetValue, '')
1500
1501
1502
1503
1504 from Gnumed.wxGladeWidgets import wxgBillingPluginPnl
1505
1506 -class cBillingPluginPnl(wxgBillingPluginPnl.wxgBillingPluginPnl, gmRegetMixin.cRegetOnPaintMixin):
1512
1514 self._PNL_bill_items.identity = None
1515 self._CHBOX_show_non_invoiced_only.SetValue(1)
1516 self._PRW_billable.SetText('', None)
1517 self._TCTRL_factor.SetValue('1.0')
1518 self._TCTRL_factor.Disable()
1519 self._TCTRL_details.SetValue('')
1520 self._TCTRL_details.Disable()
1521
1522
1523
1531
1534
1536 self._schedule_data_reget()
1537
1539 self._schedule_data_reget()
1540
1543
1574
1576 if billable is None:
1577 self._TCTRL_factor.Disable()
1578 self._TCTRL_details.Disable()
1579 self._BTN_insert_item.Disable()
1580 else:
1581 self._TCTRL_factor.Enable()
1582 self._TCTRL_details.Enable()
1583 self._BTN_insert_item.Enable()
1584
1585
1586
1590
1591
1592
1593
1594 if __name__ == '__main__':
1595
1596 if len(sys.argv) < 2:
1597 sys.exit()
1598
1599 if sys.argv[1] != 'test':
1600 sys.exit()
1601
1602 from Gnumed.pycommon import gmI18N
1603 gmI18N.activate_locale()
1604 gmI18N.install_domain(domain = 'gnumed')
1605
1606
1607 app = wx.PyWidgetTester(size = (600, 600))
1608
1609 app.MainLoop()
1610