1
2 """GNUmed billing handling widgets."""
3
4
5 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
6 __license__ = "GPL v2 or later"
7
8 import logging
9 import sys
10
11
12 import wx
13
14
15 if __name__ == '__main__':
16 sys.path.insert(0, '../../')
17 from Gnumed.pycommon import gmTools
18 from Gnumed.pycommon import gmDateTime
19 from Gnumed.pycommon import gmMatchProvider
20 from Gnumed.pycommon import gmDispatcher
21 from Gnumed.pycommon import gmPG2
22 from Gnumed.pycommon import gmCfg
23 from Gnumed.pycommon import gmPrinting
24 from Gnumed.pycommon import gmNetworkTools
25
26 from Gnumed.business import gmBilling
27 from Gnumed.business import gmPerson
28 from Gnumed.business import gmStaff
29 from Gnumed.business import gmDocuments
30 from Gnumed.business import gmPraxis
31 from Gnumed.business import gmForms
32 from Gnumed.business import gmDemographicRecord
33
34 from Gnumed.wxpython import gmListWidgets
35 from Gnumed.wxpython import gmRegetMixin
36 from Gnumed.wxpython import gmPhraseWheel
37 from Gnumed.wxpython import gmGuiHelpers
38 from Gnumed.wxpython import gmEditArea
39 from Gnumed.wxpython import gmPersonContactWidgets
40 from Gnumed.wxpython import gmPatSearchWidgets
41 from Gnumed.wxpython import gmMacro
42 from Gnumed.wxpython import gmFormWidgets
43 from Gnumed.wxpython import gmDocumentWidgets
44 from Gnumed.wxpython import gmDataPackWidgets
45
46
47 _log = logging.getLogger('gm.ui')
48
49
51 ea = cBillableEAPnl(parent, -1)
52 ea.data = billable
53 ea.mode = gmTools.coalesce(billable, 'new', 'edit')
54 dlg = gmEditArea.cGenericEditAreaDlg2 (
55 parent = parent,
56 id = -1,
57 edit_area = ea,
58 single_entry = gmTools.bool2subst((billable is None), False, True)
59 )
60 dlg.SetTitle(gmTools.coalesce(billable, _('Adding new billable'), _('Editing billable')))
61 if dlg.ShowModal() == wx.ID_OK:
62 dlg.Destroy()
63 return True
64 dlg.Destroy()
65 return False
66
67
76
77 def delete(billable):
78 if billable.is_in_use:
79 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete this billable item. It is in use.'), beep = True)
80 return False
81 return gmBilling.delete_billable(pk_billable = billable['pk_billable'])
82
83 def get_tooltip(item):
84 if item is None:
85 return None
86 return item.format()
87
88 def refresh(lctrl):
89 billables = gmBilling.get_billables()
90 items = [ [
91 b['billable_code'],
92 b['billable_description'],
93 '%(currency)s%(raw_amount)s' % b,
94 '%s (%s)' % (b['catalog_short'], b['catalog_version']),
95 gmTools.coalesce(b['comment'], ''),
96 b['pk_billable']
97 ] for b in billables ]
98 lctrl.set_string_items(items)
99 lctrl.set_data(billables)
100
101 def manage_data_packs(billable):
102 gmDataPackWidgets.manage_data_packs(parent = parent)
103 return True
104
105 def browse_catalogs(billable):
106 dbcfg = gmCfg.cCfgSQL()
107 url = dbcfg.get2 (
108 option = 'external.urls.schedules_of_fees',
109 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
110 bias = 'user',
111 default = 'http://www.e-bis.de/goae/defaultFrame.htm'
112 )
113 gmNetworkTools.open_url_in_browser(url = url)
114 return False
115
116 msg = _('\nThese are the items for billing registered with GNUmed.\n')
117
118 gmListWidgets.get_choices_from_list (
119 parent = parent,
120 msg = msg,
121 caption = _('Showing billable items.'),
122 columns = [_('Code'), _('Description'), _('Value'), _('Catalog'), _('Comment'), '#'],
123 single_selection = True,
124 new_callback = edit,
125 edit_callback = edit,
126 delete_callback = delete,
127 refresh_callback = refresh,
128 middle_extra_button = (
129 _('Data packs'),
130 _('Browse and install billing catalog (schedule of fees) data packs'),
131 manage_data_packs
132 ),
133 right_extra_button = (
134 _('Catalogs (WWW)'),
135 _('Browse billing catalogs (schedules of fees) on the web'),
136 browse_catalogs
137 ),
138 list_tooltip_callback = get_tooltip
139 )
140
141
143
145 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
146 query = """
147 SELECT -- DISTINCT ON (label)
148 r_vb.pk_billable
149 AS data,
150 r_vb.billable_code || ': ' || r_vb.billable_description || ' (' || r_vb.catalog_short || ' - ' || r_vb.catalog_version || ')'
151 AS list_label,
152 r_vb.billable_code || ' (' || r_vb.catalog_short || ' - ' || r_vb.catalog_version || ')'
153 AS field_label
154 FROM
155 ref.v_billables r_vb
156 WHERE
157 r_vb.active
158 AND (
159 r_vb.billable_code %(fragment_condition)s
160 OR
161 r_vb.billable_description %(fragment_condition)s
162 )
163 ORDER BY list_label
164 LIMIT 20
165 """
166 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query)
167 mp.setThresholds(1, 2, 4)
168 self.matcher = mp
169
172
178
180 val = '%s (%s - %s)' % (
181 instance['billable_code'],
182 instance['catalog_short'],
183 instance['catalog_version']
184 )
185 self.SetText(value = val, data = instance['pk_billable'])
186
189
190
191 from Gnumed.wxGladeWidgets import wxgBillableEAPnl
192
193 -class cBillableEAPnl(wxgBillableEAPnl.wxgBillableEAPnl, gmEditArea.cGenericEditAreaMixin):
194
210
211
212
213
214
215
216
217
219
220 validity = True
221
222 vat = self._TCTRL_vat.GetValue().strip()
223 if vat == '':
224 self.display_tctrl_as_valid(tctrl = self._TCTRL_vat, valid = True)
225 else:
226 success, vat = gmTools.input2decimal(initial = vat)
227 if success:
228 self.display_tctrl_as_valid(tctrl = self._TCTRL_vat, valid = True)
229 else:
230 validity = False
231 self.display_tctrl_as_valid(tctrl = self._TCTRL_vat, valid = False)
232 self.status_message = _('VAT must be empty or a number.')
233 self._TCTRL_vat.SetFocus()
234
235 currency = self._TCTRL_currency.GetValue().strip()
236 if currency == '':
237 validity = False
238 self.display_tctrl_as_valid(tctrl = self._TCTRL_currency, valid = False)
239 self.status_message = _('Currency is missing.')
240 self._TCTRL_currency.SetFocus()
241 else:
242 self.display_tctrl_as_valid(tctrl = self._TCTRL_currency, valid = True)
243
244 success, val = gmTools.input2decimal(initial = self._TCTRL_amount.GetValue())
245 if success:
246 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = True)
247 else:
248 validity = False
249 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False)
250 self.status_message = _('Value is missing.')
251 self._TCTRL_amount.SetFocus()
252
253 if self._TCTRL_description.GetValue().strip() == '':
254 validity = False
255 self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = False)
256 self.status_message = _('Description is missing.')
257 self._TCTRL_description.SetFocus()
258 else:
259 self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = True)
260
261 if self._PRW_coding_system.GetData() is None:
262 validity = False
263 self._PRW_coding_system.display_as_valid(False)
264 self.status_message = _('Coding system is missing.')
265 self._PRW_coding_system.SetFocus()
266 else:
267 self._PRW_coding_system.display_as_valid(True)
268
269 if self._TCTRL_code.GetValue().strip() == '':
270 validity = False
271 self.display_tctrl_as_valid(tctrl = self._TCTRL_code, valid = False)
272 self.status_message = _('Code is missing.')
273 self._TCTRL_code.SetFocus()
274 else:
275 self.display_tctrl_as_valid(tctrl = self._TCTRL_code, valid = True)
276
277 return validity
278
309
324
336
338 self._refresh_as_new()
339
341 self._TCTRL_code.SetValue(self.data['billable_code'])
342 self._TCTRL_code.Enable(False)
343 self._PRW_coding_system.SetText('%s (%s)' % (self.data['catalog_short'], self.data['catalog_version']), self.data['pk_data_source'])
344 self._PRW_coding_system.Enable(False)
345 self._TCTRL_description.SetValue(self.data['billable_description'])
346 self._TCTRL_amount.SetValue('%s' % self.data['raw_amount'])
347 self._TCTRL_currency.SetValue(self.data['currency'])
348 self._TCTRL_vat.SetValue('%s' % (self.data['vat_multiplier'] * 100))
349 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
350 self._CHBOX_active.SetValue(self.data['active'])
351
352 self._TCTRL_description.SetFocus()
353
354
355
356
357
389
391
392 dbcfg = gmCfg.cCfgSQL()
393 if with_vat:
394 option = 'form_templates.invoice_with_vat'
395 else:
396 option = 'form_templates.invoice_no_vat'
397
398 template = dbcfg.get2 (
399 option = option,
400 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
401 bias = 'user'
402 )
403
404 if template is None:
405 template = configure_invoice_template(parent = parent, with_vat = with_vat)
406 if template is None:
407 gmGuiHelpers.gm_show_error (
408 aMessage = _('There is no invoice template configured.'),
409 aTitle = _('Getting invoice template')
410 )
411 return None
412 else:
413 try:
414 name, ver = template.split(' - ')
415 except:
416 _log.exception('problem splitting invoice template name [%s]', template)
417 gmDispatcher.send(signal = 'statustext', msg = _('Problem loading invoice template.'), beep = True)
418 return None
419 template = gmForms.get_form_template(name_long = name, external_version = ver)
420 if template is None:
421 gmGuiHelpers.gm_show_error (
422 aMessage = _('Cannot load invoice template [%s - %s]') % (name, ver),
423 aTitle = _('Getting invoice template')
424 )
425 return None
426
427 return template
428
429
430
431
432 -def edit_bill(parent=None, bill=None, single_entry=False):
448
497
498
500
501 bill_patient_not_active = False
502
503 curr_pat = gmPerson.gmCurrentPatient()
504 if curr_pat.connected:
505
506
507
508 if curr_pat.ID != bill['pk_patient']:
509 bill_patient_not_active = True
510 else:
511 bill_patient_not_active = True
512
513
514
515 if bill_patient_not_active:
516 activate_patient = gmGuiHelpers.gm_show_question (
517 title = _('Creating invoice'),
518 question = _(
519 'Cannot find an existing invoice PDF for this bill.\n'
520 '\n'
521 'Active patient: %s\n'
522 'Patient on bill: #%s\n'
523 '\n'
524 'Activate patient on bill so invoice PDF can be created ?'
525 ) % (
526 gmTools.coalesce(curr_pat.ID, '', '#%s'),
527 bill['pk_patient']
528 )
529 )
530 if not activate_patient:
531 return False
532 if not gmPatSearchWidgets.set_active_patient(patient = bill['pk_patient']):
533 gmGuiHelpers.gm_show_error (
534 aTitle = _('Creating invoice'),
535 aMessage = _('Cannot activate patient #%s.') % bill['pk_patient']
536 )
537 return False
538
539 if None in [ bill['close_date'], bill['pk_receiver_address'], bill['apply_vat'] ]:
540 edit_bill(parent = parent, bill = bill, single_entry = True)
541
542 if bill['close_date'] is None:
543 _log.error('cannot create invoice from bill, bill not closed')
544 gmGuiHelpers.gm_show_warning (
545 aTitle = _('Creating invoice'),
546 aMessage = _(
547 'Cannot create invoice from bill.\n'
548 '\n'
549 'The bill is not closed.'
550 )
551 )
552 return False
553
554 if bill['pk_receiver_address'] is None:
555 _log.error('cannot create invoice from bill, lacking receiver address')
556 gmGuiHelpers.gm_show_warning (
557 aTitle = _('Creating invoice'),
558 aMessage = _(
559 'Cannot create invoice from bill.\n'
560 '\n'
561 'There is no receiver address.'
562 )
563 )
564 return False
565
566 if bill['apply_vat'] is None:
567 _log.error('cannot create invoice from bill, apply_vat undecided')
568 gmGuiHelpers.gm_show_warning (
569 aTitle = _('Creating invoice'),
570 aMessage = _(
571 'Cannot create invoice from bill.\n'
572 '\n'
573 'You must decide on whether to apply VAT.'
574 )
575 )
576 return False
577
578
579 template = get_invoice_template(parent = parent, with_vat = bill['apply_vat'])
580 if template is None:
581 gmGuiHelpers.gm_show_warning (
582 aTitle = _('Creating invoice'),
583 aMessage = _(
584 'Cannot create invoice from bill\n'
585 'without an invoice template.'
586 )
587 )
588 return False
589
590
591 try:
592 invoice = template.instantiate()
593 except KeyError:
594 _log.exception('cannot instantiate invoice template [%s]', template)
595 gmGuiHelpers.gm_show_error (
596 aMessage = _('Invalid invoice template [%s - %s (%s)]') % (name, ver, template['engine']),
597 aTitle = _('Printing medication list')
598 )
599 return False
600
601 ph = gmMacro.gmPlaceholderHandler()
602
603 ph.set_cache_value('bill', bill)
604 invoice.substitute_placeholders(data_source = ph)
605 ph.unset_cache_value('bill')
606 pdf_name = invoice.generate_output()
607 if pdf_name is None:
608 gmGuiHelpers.gm_show_error (
609 aMessage = _('Error generating invoice PDF.'),
610 aTitle = _('Creating invoice')
611 )
612 return False
613
614
615 if keep_a_copy:
616 files2import = []
617 files2import.extend(invoice.final_output_filenames)
618 files2import.extend(invoice.re_editable_filenames)
619 doc = gmDocumentWidgets.save_files_as_new_document (
620 parent = parent,
621 filenames = files2import,
622 document_type = template['instance_type'],
623 review_as_normal = True,
624 reference = bill['invoice_id'],
625 pk_org_unit = gmPraxis.gmCurrentPraxisBranch()['pk_org_unit']
626 )
627 bill['pk_doc'] = doc['pk_doc']
628 bill.save()
629
630 if not print_it:
631 return True
632
633
634 printed = gmPrinting.print_files(filenames = [pdf_name], jobtype = 'invoice')
635 if not printed:
636 gmGuiHelpers.gm_show_error (
637 aMessage = _('Error printing the invoice.'),
638 aTitle = _('Printing invoice')
639 )
640 return True
641
642 return True
643
644
646
647 if parent is None:
648 parent = wx.GetApp().GetTopWindow()
649
650 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
651 parent, -1,
652 caption = _('Deleting bill'),
653 question = _(
654 'When deleting the bill [%s]\n'
655 'do you want to keep its items (effectively \"unbilling\" them)\n'
656 'or do you want to also delete the bill items from the patient ?\n'
657 ) % bill['invoice_id'],
658 button_defs = [
659 {'label': _('Delete + keep'), 'tooltip': _('Delete the bill but keep ("unbill") its items.'), 'default': True},
660 {'label': _('Delete all'), 'tooltip': _('Delete both the bill and its items from the patient.')}
661 ],
662 show_checkbox = True,
663 checkbox_msg = _('Also remove invoice PDF'),
664 checkbox_tooltip = _('Also remove the invoice PDF from the document archive (because it will not correspond to the bill anymore).')
665 )
666 button_pressed = dlg.ShowModal()
667 delete_invoice = dlg.checkbox_is_checked()
668 dlg.Destroy()
669
670 if button_pressed == wx.ID_CANCEL:
671 return False
672
673 delete_items = (button_pressed == wx.ID_NO)
674
675 if delete_invoice:
676 if bill['pk_doc'] is not None:
677 gmDocuments.delete_document (
678 document_id = bill['pk_doc'],
679 encounter_id = gmPerson.cPatient(aPK_obj = bill['pk_patient']).emr.active_encounter['pk_encounter']
680 )
681
682 items = bill['pk_bill_items']
683 success = gmBilling.delete_bill(pk_bill = bill['pk_bill'])
684 if delete_items:
685 for item in items:
686 gmBilling.delete_bill_item(pk_bill_item = item)
687
688 return success
689
690
692
693 if bill is None:
694 return False
695
696 list_data = bill.bill_items
697 if len(list_data) == 0:
698 return False
699
700 if parent is None:
701 parent = wx.GetApp().GetTopWindow()
702
703 list_items = [ [
704 gmDateTime.pydt_strftime(b['date_to_bill'], '%Y %b %d', accuracy = gmDateTime.acc_days),
705 b['unit_count'],
706 '%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], '', ' - %s')),
707 '%(curr)s %(total_val)s (%(count)s %(x)s %(unit_val)s%(x)s%(val_multiplier)s)' % {
708 'curr': b['currency'],
709 'total_val': b['total_amount'],
710 'count': b['unit_count'],
711 'x': gmTools.u_multiply,
712 'unit_val': b['net_amount_per_unit'],
713 'val_multiplier': b['amount_multiplier']
714 },
715 '%(curr)s%(vat)s (%(perc_vat)s%%)' % {
716 'vat': b['vat'],
717 'curr': b['currency'],
718 'perc_vat': b['vat_multiplier'] * 100
719 },
720 '%s (%s)' % (b['catalog_short'], b['catalog_version']),
721 b['pk_bill_item']
722 ] for b in list_data ]
723
724 msg = _('Select the items you want to remove from bill [%s]:\n') % bill['invoice_id']
725 items2remove = gmListWidgets.get_choices_from_list (
726 parent = parent,
727 msg = msg,
728 caption = _('Removing items from bill'),
729 columns = [_('Date'), _('Count'), _('Description'), _('Value'), _('VAT'), _('Catalog'), '#'],
730 single_selection = False,
731 choices = list_items,
732 data = list_data
733 )
734
735 if items2remove is None:
736 return False
737
738 if len(items2remove) == len(list_items):
739 gmGuiHelpers.gm_show_info (
740 title = _('Removing items from bill'),
741 info = _(
742 'Cannot remove all items from a bill because\n'
743 'GNUmed does not support empty bills.\n'
744 '\n'
745 'You must delete the bill itself if you want to\n'
746 'remove all items (at which point you can opt to\n'
747 'keep the items and only delete the bill).'
748 )
749 )
750 return False
751
752 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
753 parent, -1,
754 caption = _('Removing items from bill'),
755 question = _(
756 '%s items selected from bill [%s]\n'
757 '\n'
758 'Do you want to only remove the selected items\n'
759 'from the bill ("unbill" them) or do you want\n'
760 'to delete them entirely from the patient ?\n'
761 '\n'
762 'Note that neither action is reversible.'
763 ) % (
764 len(items2remove),
765 bill['invoice_id']
766 ),
767 button_defs = [
768 {'label': _('"Unbill"'), 'tooltip': _('Only "unbill" items (remove from bill but do not delete from patient).'), 'default': True},
769 {'label': _('Delete'), 'tooltip': _('Completely delete items from the patient.')}
770 ],
771 show_checkbox = True,
772 checkbox_msg = _('Also remove invoice PDF'),
773 checkbox_tooltip = _('Also remove the invoice PDF from the document archive (because it will not correspond to the bill anymore).')
774 )
775 button_pressed = dlg.ShowModal()
776 delete_invoice = dlg.checkbox_is_checked()
777 dlg.Destroy()
778
779 if button_pressed == wx.ID_CANCEL:
780 return False
781
782
783
784 pk_patient = bill['pk_patient']
785
786 for item in items2remove:
787 item['pk_bill'] = None
788 item.save()
789 if button_pressed == wx.ID_NO:
790 gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item'])
791
792 if delete_invoice:
793 if bill['pk_doc'] is not None:
794 gmDocuments.delete_document (
795 document_id = bill['pk_doc'],
796 encounter_id = gmPerson.cPatient(aPK_obj = pk_patient).emr.active_encounter['pk_encounter']
797 )
798
799
800 if len(bill.bill_items) == 0:
801 gmBilling.delete_bill(pk_bill = bill['pk_bill'])
802
803 return True
804
805
807
808 if parent is None:
809 parent = wx.GetApp().GetTopWindow()
810
811
812 def show_pdf(bill):
813 if bill is None:
814 return False
815
816
817 invoice = bill.invoice
818 if invoice is not None:
819 success, msg = invoice.parts[-1].display_via_mime()
820 if not success:
821 gmGuiHelpers.gm_show_error(aMessage = msg, aTitle = _('Displaying invoice'))
822 return False
823
824
825 create_it = gmGuiHelpers.gm_show_question (
826 title = _('Displaying invoice'),
827 question = _(
828 'Cannot find an existing\n'
829 'invoice PDF for this bill.\n'
830 '\n'
831 'Do you want to create one ?'
832 ),
833 )
834 if not create_it:
835 return False
836
837
838 if not bill.set_missing_address_from_default():
839 gmGuiHelpers.gm_show_warning (
840 aTitle = _('Creating invoice'),
841 aMessage = _(
842 'There is no pre-configured billing address.\n'
843 '\n'
844 'Select the address you want to send the bill to.'
845 )
846 )
847 edit_bill(parent = parent, bill = bill, single_entry = True)
848 if bill['pk_receiver_address'] is None:
849 return False
850 if bill['close_date'] is None:
851 bill['close_date'] = gmDateTime.pydt_now_here()
852 bill.save()
853
854 return create_invoice_from_bill(parent = parent, bill = bill, print_it = True, keep_a_copy = True)
855
856 def edit(bill):
857 return edit_bill(parent = parent, bill = bill, single_entry = True)
858
859 def delete(bill):
860 return delete_bill(parent = parent, bill = bill)
861
862 def remove_items(bill):
863 return remove_items_from_bill(parent = parent, bill = bill)
864
865 def get_tooltip(item):
866 if item is None:
867 return None
868 return item.format()
869
870 def refresh(lctrl):
871 if patient is None:
872 bills = gmBilling.get_bills()
873 else:
874 bills = gmBilling.get_bills(pk_patient = patient.ID)
875 items = []
876 for b in bills:
877 if b['close_date'] is None:
878 close_date = _('<open>')
879 else:
880 close_date = gmDateTime.pydt_strftime(b['close_date'], '%Y %b %d')
881 if b['total_amount'] is None:
882 amount = _('no items on bill')
883 else:
884 amount = gmTools.bool2subst (
885 b['apply_vat'],
886 _('%(currency)s%(total_amount_with_vat)s (with %(percent_vat)s%% VAT)') % b,
887 '%(currency)s%(total_amount)s' % b,
888 _('without VAT: %(currency)s%(total_amount)s / with %(percent_vat)s%% VAT: %(currency)s%(total_amount_with_vat)s') % b
889 )
890 items.append ([
891 close_date,
892 b['invoice_id'],
893 amount,
894 gmTools.coalesce(b['comment'], '')
895 ])
896 lctrl.set_string_items(items)
897 lctrl.set_data(bills)
898
899 return gmListWidgets.get_choices_from_list (
900 parent = parent,
901 caption = _('Showing bills.'),
902 columns = [_('Close date'), _('Invoice ID'), _('Value'), _('Comment')],
903 single_selection = True,
904 edit_callback = edit,
905 delete_callback = delete,
906 refresh_callback = refresh,
907 middle_extra_button = (
908 'PDF',
909 _('Create if necessary, and show the corresponding invoice PDF'),
910 show_pdf
911 ),
912 right_extra_button = (
913 _('Unbill'),
914 _('Select and remove items from a bill.'),
915 remove_items
916 ),
917 list_tooltip_callback = get_tooltip
918 )
919
920
921 from Gnumed.wxGladeWidgets import wxgBillEAPnl
922
923 -class cBillEAPnl(wxgBillEAPnl.wxgBillEAPnl, gmEditArea.cGenericEditAreaMixin):
924
926
927 try:
928 data = kwargs['bill']
929 del kwargs['bill']
930 except KeyError:
931 data = None
932
933 wxgBillEAPnl.wxgBillEAPnl.__init__(self, *args, **kwargs)
934 gmEditArea.cGenericEditAreaMixin.__init__(self)
935
936 self.mode = 'new'
937 self.data = data
938 if data is not None:
939 self.mode = 'edit'
940
941 self._3state2bool = {
942 wx.CHK_UNCHECKED: False,
943 wx.CHK_CHECKED: True,
944 wx.CHK_UNDETERMINED: None
945 }
946 self.bool_to_3state = {
947 False: wx.CHK_UNCHECKED,
948 True: wx.CHK_CHECKED,
949 None: wx.CHK_UNDETERMINED
950 }
951
952
953
954
955
956
957
959 validity = True
960
961
962 if not self._PRW_close_date.is_valid_timestamp(allow_empty = False):
963 self._PRW_close_date.SetFocus()
964
965
966 if self._CHBOX_vat_applies.ThreeStateValue == wx.CHK_UNDETERMINED:
967 self._CHBOX_vat_applies.SetFocus()
968 self._CHBOX_vat_applies.SetBackgroundColour('yellow')
969
970 return validity
971
975
977 self.data['close_date'] = self._PRW_close_date.GetData()
978 self.data['apply_vat'] = self._3state2bool[self._CHBOX_vat_applies.ThreeStateValue]
979 self.data['comment'] = self._TCTRL_comment.GetValue()
980 self.data.save()
981 return True
982
985
987 self._refresh_as_new()
988
1018
1019
1020
1022 if self._CHBOX_vat_applies.ThreeStateValue == wx.CHK_CHECKED:
1023 tmp = '%s %%(currency)s%%(total_vat)s %s %s %%(currency)s%%(total_amount_with_vat)s' % (
1024 gmTools.u_corresponds_to,
1025 gmTools.u_arrow2right,
1026 gmTools.u_sum,
1027 )
1028 self._TCTRL_value_with_vat.SetValue(tmp % self.data)
1029 return
1030 if self._CHBOX_vat_applies.ThreeStateValue == wx.CHK_UNDETERMINED:
1031 self._TCTRL_value_with_vat.SetValue('?')
1032 return
1033 self._TCTRL_value_with_vat.SetValue('')
1034
1049
1050
1051
1052
1054
1055 if bill_item is not None:
1056 if bill_item.is_in_use:
1057 gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit already invoiced bill item.'), beep = True)
1058 return False
1059
1060 ea = cBillItemEAPnl(parent, -1)
1061 ea.data = bill_item
1062 ea.mode = gmTools.coalesce(bill_item, 'new', 'edit')
1063 dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea, single_entry = single_entry)
1064 dlg.SetTitle(gmTools.coalesce(bill_item, _('Adding new bill item'), _('Editing bill item')))
1065 if dlg.ShowModal() == wx.ID_OK:
1066 dlg.Destroy()
1067 return True
1068 dlg.Destroy()
1069 return False
1070
1078
1079 def delete(item):
1080 if item.is_in_use is not None:
1081 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete already invoiced bill items.'), beep = True)
1082 return False
1083 gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item'])
1084 return True
1085
1086 def get_tooltip(item):
1087 if item is None:
1088 return None
1089 return item.format()
1090
1091 def refresh(lctrl):
1092 b_items = gmBilling.get_bill_items(pk_patient = pk_patient)
1093 items = [ [
1094 gmDateTime.pydt_strftime(b['date_to_bill'], '%Y %b %d', accuracy = gmDateTime.acc_days),
1095 b['unit_count'],
1096 '%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], '', ' - %s')),
1097 b['currency'],
1098 '%s (%s %s %s%s%s)' % (
1099 b['total_amount'],
1100 b['unit_count'],
1101 gmTools.u_multiply,
1102 b['net_amount_per_unit'],
1103 gmTools.u_multiply,
1104 b['amount_multiplier']
1105 ),
1106 '%s (%s%%)' % (
1107 b['vat'],
1108 b['vat_multiplier'] * 100
1109 ),
1110 '%s (%s)' % (b['catalog_short'], b['catalog_version']),
1111 b['pk_bill_item']
1112 ] for b in b_items ]
1113 lctrl.set_string_items(items)
1114 lctrl.set_data(b_items)
1115
1116 gmListWidgets.get_choices_from_list (
1117 parent = parent,
1118
1119 caption = _('Showing bill items.'),
1120 columns = [_('Date'), _('Count'), _('Description'), _('$__replace_by_your_currency_symbol')[:-len('__replace_by_your_currency_symbol')], _('Value'), _('VAT'), _('Catalog'), '#'],
1121 single_selection = True,
1122 new_callback = edit,
1123 edit_callback = edit,
1124 delete_callback = delete,
1125 refresh_callback = refresh,
1126 list_tooltip_callback = get_tooltip
1127 )
1128
1129
1131 """A list for managing a patient's bill items.
1132
1133 Does NOT act on/listen to the current patient.
1134 """
1154
1155
1156
1157 - def refresh(self, *args, **kwargs):
1158 if self.__identity is None:
1159 self._LCTRL_items.set_string_items()
1160 return
1161
1162 b_items = gmBilling.get_bill_items(pk_patient = self.__identity.ID, non_invoiced_only = self.__show_non_invoiced_only)
1163 items = [ [
1164 gmDateTime.pydt_strftime(b['date_to_bill'], '%Y %b %d', accuracy = gmDateTime.acc_days),
1165 b['unit_count'],
1166 '%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], '', ' - %s')),
1167 b['currency'],
1168 b['total_amount'],
1169 '%s (%s%%)' % (
1170 b['vat'],
1171 b['vat_multiplier'] * 100
1172 ),
1173 '%s (%s)' % (b['catalog_short'], b['catalog_version']),
1174 '%s %s %s %s %s' % (
1175 b['unit_count'],
1176 gmTools.u_multiply,
1177 b['net_amount_per_unit'],
1178 gmTools.u_multiply,
1179 b['amount_multiplier']
1180 ),
1181 gmTools.coalesce(b['pk_bill'], gmTools.u_diameter),
1182 b['pk_encounter_to_bill'],
1183 b['pk_bill_item']
1184 ] for b in b_items ]
1185
1186 self._LCTRL_items.set_string_items(items = items)
1187 self._LCTRL_items.set_column_widths()
1188 self._LCTRL_items.set_data(data = b_items)
1189
1190
1191
1193 self._LCTRL_items.set_columns(columns = [
1194 _('Charge date'),
1195 _('Count'),
1196 _('Description'),
1197 _('$__replace_by_your_currency_symbol')[:-len('__replace_by_your_currency_symbol')],
1198 _('Value'),
1199 _('VAT'),
1200 _('Catalog'),
1201 _('Count %s Value %s Factor') % (gmTools.u_multiply, gmTools.u_multiply),
1202 _('Invoice'),
1203 _('Encounter'),
1204 '#'
1205 ])
1206 self._LCTRL_items.item_tooltip_callback = self._get_item_tooltip
1207
1208
1209
1210
1211
1212 self.left_extra_button = (
1213 _('Invoice selected items'),
1214 _('Create invoice from selected items.'),
1215 self._invoice_selected_items
1216 )
1217 self.middle_extra_button = (
1218 _('Bills'),
1219 _('Browse bills of this patient.'),
1220 self._browse_bills
1221 )
1222 self.right_extra_button = (
1223 _('Billables'),
1224 _('Browse list of billables.'),
1225 self._browse_billables
1226 )
1227
1230
1233
1235 if item['pk_bill'] is not None:
1236 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete already invoiced bill items.'), beep = True)
1237 return False
1238 go_ahead = gmGuiHelpers.gm_show_question (
1239 _( 'Do you really want to delete this\n'
1240 'bill item from the patient ?'),
1241 _('Deleting bill item')
1242 )
1243 if not go_ahead:
1244 return False
1245 gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item'])
1246 return True
1247
1252
1255
1275
1279
1282
1283
1284
1286 return self.__identity
1287
1291
1292 identity = property(_get_identity, _set_identity)
1293
1295 return self.__show_non_invoiced_only
1296
1298 self.__show_non_invoiced_only = value
1299 self.refresh()
1300
1301 show_non_invoiced_only = property(_get_show_non_invoiced_only, _set_show_non_invoiced_only)
1302
1303
1304 from Gnumed.wxGladeWidgets import wxgBillItemEAPnl
1305
1306 -class cBillItemEAPnl(wxgBillItemEAPnl.wxgBillItemEAPnl, gmEditArea.cGenericEditAreaMixin):
1307
1325
1330
1331
1332
1334
1335 validity = True
1336
1337 if self._TCTRL_factor.GetValue().strip() == '':
1338 validity = False
1339 self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = False)
1340 self._TCTRL_factor.SetFocus()
1341 else:
1342 converted, factor = gmTools.input2decimal(self._TCTRL_factor.GetValue())
1343 if not converted:
1344 validity = False
1345 self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = False)
1346 self._TCTRL_factor.SetFocus()
1347 else:
1348 self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = True)
1349
1350 if self._TCTRL_amount.GetValue().strip() == '':
1351 validity = False
1352 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False)
1353 self._TCTRL_amount.SetFocus()
1354 else:
1355 converted, factor = gmTools.input2decimal(self._TCTRL_amount.GetValue())
1356 if not converted:
1357 validity = False
1358 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False)
1359 self._TCTRL_amount.SetFocus()
1360 else:
1361 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = True)
1362
1363 if self._TCTRL_count.GetValue().strip() == '':
1364 validity = False
1365 self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = False)
1366 self._TCTRL_count.SetFocus()
1367 else:
1368 converted, factor = gmTools.input2decimal(self._TCTRL_count.GetValue())
1369 if not converted:
1370 validity = False
1371 self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = False)
1372 self._TCTRL_count.SetFocus()
1373 else:
1374 self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = True)
1375
1376 if self._PRW_date.is_valid_timestamp(allow_empty = True):
1377 self._PRW_date.display_as_valid(True)
1378 else:
1379 validity = False
1380 self._PRW_date.display_as_valid(False)
1381 self._PRW_date.SetFocus()
1382
1383 if self._PRW_encounter.GetData() is None:
1384 validity = False
1385 self._PRW_encounter.display_as_valid(False)
1386 self._PRW_encounter.SetFocus()
1387 else:
1388 self._PRW_encounter.display_as_valid(True)
1389
1390 if self._PRW_billable.GetData() is None:
1391 validity = False
1392 self._PRW_billable.display_as_valid(False)
1393 self._PRW_billable.SetFocus()
1394 else:
1395 self._PRW_billable.display_as_valid(True)
1396
1397 return validity
1398
1414
1423
1436
1445
1458
1460 if item is None:
1461 return
1462 if self._TCTRL_amount.GetValue().strip() != '':
1463 return
1464 val = '%s' % self._PRW_billable.GetData(as_instance = True)['raw_amount']
1465 wx.CallAfter(self._TCTRL_amount.SetValue, val)
1466
1468 if self._PRW_billable.GetData() is None:
1469 wx.CallAfter(self._TCTRL_amount.SetValue, '')
1470
1471
1472
1473
1474 from Gnumed.wxGladeWidgets import wxgBillingPluginPnl
1475
1476 -class cBillingPluginPnl(wxgBillingPluginPnl.wxgBillingPluginPnl, gmRegetMixin.cRegetOnPaintMixin):
1482
1484 self._PNL_bill_items.identity = None
1485 self._CHBOX_show_non_invoiced_only.SetValue(1)
1486 self._PRW_billable.SetText('', None)
1487 self._TCTRL_factor.SetValue('1.0')
1488 self._TCTRL_factor.Disable()
1489 self._TCTRL_details.SetValue('')
1490 self._TCTRL_details.Disable()
1491
1492
1493
1501
1504
1506 self._schedule_data_reget()
1507
1509 self._schedule_data_reget()
1510
1513
1544
1546 if billable is None:
1547 self._TCTRL_factor.Disable()
1548 self._TCTRL_details.Disable()
1549 self._BTN_insert_item.Disable()
1550 else:
1551 self._TCTRL_factor.Enable()
1552 self._TCTRL_details.Enable()
1553 self._BTN_insert_item.Enable()
1554
1555
1556
1560
1561
1562
1563 if __name__ == '__main__':
1564
1565 if len(sys.argv) < 2:
1566 sys.exit()
1567
1568 if sys.argv[1] != 'test':
1569 sys.exit()
1570
1571 from Gnumed.pycommon import gmI18N
1572 gmI18N.activate_locale()
1573 gmI18N.install_domain(domain = 'gnumed')
1574
1575
1576 app = wx.PyWidgetTester(size = (600, 600))
1577
1578 app.MainLoop()
1579