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 = parent, id = -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
69
70 if parent is None:
71 parent = wx.GetApp().GetTopWindow()
72
73
74 def edit(billable=None):
75 return edit_billable(parent = parent, billable = billable)
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 u'%(currency)s%(raw_amount)s' % b,
94 u'%s (%s)' % (b['catalog_short'], b['catalog_version']),
95 gmTools.coalesce(b['comment'], u''),
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 = u'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'), u'#'],
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 = u"""
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 = u'%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
212
213
214
215
216
217
218
219
221
222 validity = True
223
224 vat = self._TCTRL_vat.GetValue().strip()
225 if vat == u'':
226 self.display_tctrl_as_valid(tctrl = self._TCTRL_vat, valid = True)
227 else:
228 success, vat = gmTools.input2decimal(initial = vat)
229 if success:
230 self.display_tctrl_as_valid(tctrl = self._TCTRL_vat, valid = True)
231 else:
232 validity = False
233 self.display_tctrl_as_valid(tctrl = self._TCTRL_vat, valid = False)
234 self.status_message = _('VAT must be empty or a number.')
235 self._TCTRL_vat.SetFocus()
236
237 currency = self._TCTRL_currency.GetValue().strip()
238 if currency == u'':
239 validity = False
240 self.display_tctrl_as_valid(tctrl = self._TCTRL_currency, valid = False)
241 self.status_message = _('Currency is missing.')
242 self._TCTRL_currency.SetFocus()
243 else:
244 self.display_tctrl_as_valid(tctrl = self._TCTRL_currency, valid = True)
245
246 success, val = gmTools.input2decimal(initial = self._TCTRL_amount.GetValue())
247 if success:
248 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = True)
249 else:
250 validity = False
251 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False)
252 self.status_message = _('Value is missing.')
253 self._TCTRL_amount.SetFocus()
254
255 if self._TCTRL_description.GetValue().strip() == u'':
256 validity = False
257 self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = False)
258 self.status_message = _('Description is missing.')
259 self._TCTRL_description.SetFocus()
260 else:
261 self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = True)
262
263 if self._PRW_coding_system.GetData() is None:
264 validity = False
265 self._PRW_coding_system.display_as_valid(False)
266 self.status_message = _('Coding system is missing.')
267 self._PRW_coding_system.SetFocus()
268 else:
269 self._PRW_coding_system.display_as_valid(True)
270
271 if self._TCTRL_code.GetValue().strip() == u'':
272 validity = False
273 self.display_tctrl_as_valid(tctrl = self._TCTRL_code, valid = False)
274 self.status_message = _('Code is missing.')
275 self._TCTRL_code.SetFocus()
276 else:
277 self.display_tctrl_as_valid(tctrl = self._TCTRL_code, valid = True)
278
279 return validity
280
311
326
338
340 self._refresh_as_new()
341
343 self._TCTRL_code.SetValue(self.data['billable_code'])
344 self._TCTRL_code.Enable(False)
345 self._PRW_coding_system.SetText(u'%s (%s)' % (self.data['catalog_short'], self.data['catalog_version']), self.data['pk_data_source'])
346 self._PRW_coding_system.Enable(False)
347 self._TCTRL_description.SetValue(self.data['billable_description'])
348 self._TCTRL_amount.SetValue(u'%s' % self.data['raw_amount'])
349 self._TCTRL_currency.SetValue(self.data['currency'])
350 self._TCTRL_vat.SetValue(u'%s' % (self.data['vat_multiplier'] * 100))
351 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], u''))
352 self._CHBOX_active.SetValue(self.data['active'])
353
354 self._TCTRL_description.SetFocus()
355
356
357
358
359
391
393
394 dbcfg = gmCfg.cCfgSQL()
395 if with_vat:
396 option = u'form_templates.invoice_with_vat'
397 else:
398 option = u'form_templates.invoice_no_vat'
399
400 template = dbcfg.get2 (
401 option = option,
402 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
403 bias = 'user'
404 )
405
406 if template is None:
407 template = configure_invoice_template(parent = parent, with_vat = with_vat)
408 if template is None:
409 gmGuiHelpers.gm_show_error (
410 aMessage = _('There is no invoice template configured.'),
411 aTitle = _('Getting invoice template')
412 )
413 return None
414 else:
415 try:
416 name, ver = template.split(u' - ')
417 except:
418 _log.exception('problem splitting invoice template name [%s]', template)
419 gmDispatcher.send(signal = 'statustext', msg = _('Problem loading invoice template.'), beep = True)
420 return None
421 template = gmForms.get_form_template(name_long = name, external_version = ver)
422 if template is None:
423 gmGuiHelpers.gm_show_error (
424 aMessage = _('Cannot load invoice template [%s - %s]') % (name, ver),
425 aTitle = _('Getting invoice template')
426 )
427 return None
428
429 return template
430
431
432
433
434 -def edit_bill(parent=None, bill=None, single_entry=False):
450
452
453 if len(bill_items) == 0:
454 return None
455
456 item = bill_items[0]
457 currency = item['currency']
458 vat = item['vat_multiplier']
459 pat = item['pk_patient']
460
461
462 has_errors = False
463 for item in bill_items:
464 if (item['currency'] != currency) or (
465 item['vat_multiplier'] != vat) or (
466 item['pk_patient'] != pat
467 ):
468 msg = _(
469 'All items to be included with a bill must\n'
470 'coincide on currency, VAT, and patient.\n'
471 '\n'
472 'This item does not:\n'
473 '\n'
474 '%s\n'
475 ) % item.format()
476 has_errors = True
477
478 if item['pk_bill'] is not None:
479 msg = _(
480 'This item is already invoiced:\n'
481 '\n'
482 '%s\n'
483 '\n'
484 'Cannot put it on a second bill.'
485 ) % item.format()
486 has_errors = True
487
488 if has_errors:
489 gmGuiHelpers.gm_show_warning(aTitle = _('Checking invoice items'), aMessage = msg)
490 return None
491
492
493 bill = gmBilling.create_bill(invoice_id = gmBilling.get_invoice_id(pk_patient = pat))
494 _log.info('created bill [%s]', bill['invoice_id'])
495 bill.add_items(items = bill_items)
496 bill.set_missing_address_from_default()
497
498 return bill
499
501
502 bill_patient_not_active = False
503
504 curr_pat = gmPerson.gmCurrentPatient()
505 if curr_pat.connected:
506
507
508
509 if curr_pat.ID != bill['pk_patient']:
510 bill_patient_not_active = True
511 else:
512 bill_patient_not_active = True
513
514
515
516 if bill_patient_not_active:
517 activate_patient = gmGuiHelpers.gm_show_question (
518 title = _('Creating invoice'),
519 question = _(
520 'Cannot find an existing invoice PDF for this bill.\n'
521 '\n'
522 'Active patient: %s\n'
523 'Patient on bill: #%s\n'
524 '\n'
525 'Activate patient on bill so invoice PDF can be created ?'
526 ) % (
527 gmTools.coalesce(curr_pat.ID, u'', u'#%s'),
528 bill['pk_patient']
529 )
530 )
531 if not activate_patient:
532 return False
533 if not gmPatSearchWidgets.set_active_patient(patient = bill['pk_patient']):
534 gmGuiHelpers.gm_show_error (
535 aTitle = _('Creating invoice'),
536 aMessage = _('Cannot activate patient #%s.') % bill['pk_patient']
537 )
538 return False
539
540 if None in [ bill['close_date'], bill['pk_receiver_address'] ]:
541 edit_bill(parent = parent, bill = bill, single_entry = True)
542
543 if bill['close_date'] is None:
544 _log.error('cannot create invoice from bill, bill not closed')
545 gmGuiHelpers.gm_show_warning (
546 aTitle = _('Creating invoice'),
547 aMessage = _(
548 'Cannot create invoice from bill.\n'
549 '\n'
550 'The bill is not closed.'
551 )
552 )
553 return False
554
555 if bill['pk_receiver_address'] is None:
556 _log.error('cannot create invoice from bill, lacking receiver address')
557 gmGuiHelpers.gm_show_warning (
558 aTitle = _('Creating invoice'),
559 aMessage = _(
560 'Cannot create invoice from bill.\n'
561 '\n'
562 'There is no receiver address.'
563 )
564 )
565 return False
566
567
568 template = get_invoice_template(parent = parent, with_vat = bill['apply_vat'])
569 if template is None:
570 gmGuiHelpers.gm_show_warning (
571 aTitle = _('Creating invoice'),
572 aMessage = _(
573 'Cannot create invoice from bill\n'
574 'without an invoice template.'
575 )
576 )
577 return False
578
579
580 try:
581 invoice = template.instantiate()
582 except KeyError:
583 _log.exception('cannot instantiate invoice template [%s]', template)
584 gmGuiHelpers.gm_show_error (
585 aMessage = _('Invalid invoice template [%s - %s (%s)]') % (name, ver, template['engine']),
586 aTitle = _('Printing medication list')
587 )
588 return False
589
590 ph = gmMacro.gmPlaceholderHandler()
591
592 ph.set_cache_value('bill', bill)
593 invoice.substitute_placeholders(data_source = ph)
594 ph.unset_cache_value('bill')
595 pdf_name = invoice.generate_output()
596 if pdf_name is None:
597 gmGuiHelpers.gm_show_error (
598 aMessage = _('Error generating invoice PDF.'),
599 aTitle = _('Creating invoice')
600 )
601 return False
602
603
604 if keep_a_copy:
605 files2import = []
606 files2import.extend(invoice.final_output_filenames)
607 files2import.extend(invoice.re_editable_filenames)
608 doc = gmDocumentWidgets.save_files_as_new_document (
609 parent = parent,
610 filenames = files2import,
611 document_type = template['instance_type'],
612 review_as_normal = True,
613 reference = bill['invoice_id']
614 )
615 bill['pk_doc'] = doc['pk_doc']
616 bill.save()
617
618 if not print_it:
619 return True
620
621
622 printed = gmPrinting.print_files(filenames = [pdf_name], jobtype = 'invoice')
623 if not printed:
624 gmGuiHelpers.gm_show_error (
625 aMessage = _('Error printing the invoice.'),
626 aTitle = _('Printing invoice')
627 )
628 return True
629
630 return True
631
632
634
635 if parent is None:
636 parent = wx.GetApp().GetTopWindow()
637
638 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
639 parent, -1,
640 caption = _('Deleting bill'),
641 question = _(
642 'When deleting the bill [%s]\n'
643 'do you want to keep its items (effectively \"unbilling\" them)\n'
644 'or do you want to also delete the bill items from the patient ?\n'
645 ) % bill['invoice_id'],
646 button_defs = [
647 {'label': _('Delete + keep'), 'tooltip': _('Delete the bill but keep ("unbill") its items.'), 'default': True},
648 {'label': _('Delete all'), 'tooltip': _('Delete both the bill and its items from the patient.')}
649 ],
650 show_checkbox = True,
651 checkbox_msg = _('Also remove invoice PDF'),
652 checkbox_tooltip = _('Also remove the invoice PDF from the document archive (because it will not correspond to the bill anymore).')
653 )
654 button_pressed = dlg.ShowModal()
655 delete_invoice = dlg.checkbox_is_checked()
656 dlg.Destroy()
657
658 if button_pressed == wx.ID_CANCEL:
659 return False
660
661 if button_pressed == wx.ID_YES:
662 for item in bill.bill_items:
663 item['pk_bill'] = None
664 item.save()
665
666 if button_pressed == wx.ID_NO:
667 for item in bill.bill_items:
668 item['pk_bill'] = None
669 item.save()
670 gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item'])
671
672 if delete_invoice:
673 if bill['pk_doc'] is not None:
674 gmDocuments.delete_document (
675 document_id = bill['pk_doc'],
676 encounter_id = gmPerson.cPatient(aPK_obj = bill['pk_patient']).emr.active_encounter['pk_encounter']
677 )
678
679 return gmBilling.delete_bill(pk_bill = bill['pk_bill'])
680
681
683
684 if bill is None:
685 return False
686
687 list_data = bill.bill_items
688 if len(list_data) == 0:
689 return False
690
691 if parent is None:
692 parent = wx.GetApp().GetTopWindow()
693
694 list_items = [ [
695 gmDateTime.pydt_strftime(b['date_to_bill'], '%Y %b %d', accuracy = gmDateTime.acc_days),
696 b['unit_count'],
697 u'%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], u'', u' - %s')),
698 u'%(curr)s %(total_val)s (%(count)s %(x)s %(unit_val)s%(x)s%(val_multiplier)s)' % {
699 'curr': b['currency'],
700 'total_val': b['total_amount'],
701 'count': b['unit_count'],
702 'x': gmTools.u_multiply,
703 'unit_val': b['net_amount_per_unit'],
704 'val_multiplier': b['amount_multiplier']
705 },
706 u'%(curr)s%(vat)s (%(perc_vat)s%%)' % {
707 'vat': b['vat'],
708 'curr': b['currency'],
709 'perc_vat': b['vat_multiplier'] * 100
710 },
711 u'%s (%s)' % (b['catalog_short'], b['catalog_version']),
712 b['pk_bill_item']
713 ] for b in list_data ]
714
715 msg = _('Select the items you want to remove from bill [%s]:\n') % bill['invoice_id']
716 items2remove = gmListWidgets.get_choices_from_list (
717 parent = parent,
718 msg = msg,
719 caption = _('Removing items from bill'),
720 columns = [_('Date'), _('Count'), _('Description'), _('Value'), _('VAT'), _('Catalog'), u'#'],
721 single_selection = False,
722 choices = list_items,
723 data = list_data
724 )
725
726 if items2remove is None:
727 return False
728
729 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
730 parent, -1,
731 caption = _('Removing items from bill'),
732 question = _(
733 '%s items selected from bill [%s]\n'
734 '\n'
735 'Do you want to only remove the selected items\n'
736 'from the bill ("unbill" them) or do you want\n'
737 'to delete them entirely from the patient ?\n'
738 '\n'
739 'Note that neither action is reversible.'
740 ) % (
741 len(items2remove),
742 bill['invoice_id']
743 ),
744 button_defs = [
745 {'label': _('"Unbill"'), 'tooltip': _('Only "unbill" items (remove from bill but do not delete from patient).'), 'default': True},
746 {'label': _('Delete'), 'tooltip': _('Completely delete items from the patient.')}
747 ],
748 show_checkbox = True,
749 checkbox_msg = _('Also remove invoice PDF'),
750 checkbox_tooltip = _('Also remove the invoice PDF from the document archive (because it will not correspond to the bill anymore).')
751 )
752 button_pressed = dlg.ShowModal()
753 delete_invoice = dlg.checkbox_is_checked()
754 dlg.Destroy()
755
756 if button_pressed == wx.ID_CANCEL:
757 return False
758
759
760
761 pk_patient = bill['pk_patient']
762
763 for item in items2remove:
764 item['pk_bill'] = None
765 item.save()
766 if button_pressed == wx.ID_NO:
767 gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item'])
768
769 if delete_invoice:
770 if bill['pk_doc'] is not None:
771 gmDocuments.delete_document (
772 document_id = bill['pk_doc'],
773 encounter_id = gmPerson.cPatient(aPK_obj = pk_patient).emr.active_encounter['pk_encounter']
774 )
775
776
777 if len(bill.bill_items) == 0:
778 gmBilling.delete_bill(pk_bill = bill['pk_bill'])
779
780 return True
781
783
784 if parent is None:
785 parent = wx.GetApp().GetTopWindow()
786
787
788 def show_pdf(bill):
789 if bill is None:
790 return False
791
792
793 invoice = bill.invoice
794 if invoice is not None:
795 success, msg = invoice.parts[-1].display_via_mime()
796 if not success:
797 gmGuiHelpers.gm_show_error(aMessage = msg, aTitle = _('Displaying invoice'))
798 return False
799
800
801 create_it = gmGuiHelpers.gm_show_question (
802 title = _('Displaying invoice'),
803 question = _(
804 'Cannot find an existing\n'
805 'invoice PDF for this bill.\n'
806 '\n'
807 'Do you want to create one ?'
808 ),
809 )
810 if not create_it:
811 return False
812
813
814 if not bill.set_missing_address_from_default():
815 gmGuiHelpers.gm_show_warning (
816 aTitle = _('Creating invoice'),
817 aMessage = _(
818 'There is no pre-configured billing address.\n'
819 '\n'
820 'Select the address you want to send the bill to.'
821 )
822 )
823 edit_bill(parent = parent, bill = bill, single_entry = True)
824 if bill['pk_receiver_address'] is None:
825 return False
826 if bill['close_date'] is None:
827 bill['close_date'] = gmDateTime.pydt_now_here()
828 bill.save()
829
830 return create_invoice_from_bill(parent = parent, bill = bill, print_it = True, keep_a_copy = True)
831
832 def edit(bill):
833 return edit_bill(parent = parent, bill = bill, single_entry = True)
834
835 def delete(bill):
836 return delete_bill(parent = parent, bill = bill)
837
838 def remove_items(bill):
839 return remove_items_from_bill(parent = parent, bill = bill)
840
841 def get_tooltip(item):
842 if item is None:
843 return None
844 return item.format()
845
846 def refresh(lctrl):
847 if patient is None:
848 bills = gmBilling.get_bills()
849 else:
850 bills = gmBilling.get_bills(pk_patient = patient.ID)
851 items = []
852 for b in bills:
853 if b['close_date'] is None:
854 close_date = _('<open>')
855 else:
856 close_date = gmDateTime.pydt_strftime(b['close_date'], '%Y %b %d')
857 if b['total_amount'] is None:
858 amount = _('no items on bill')
859 else:
860 amount = gmTools.bool2subst (
861 b['apply_vat'],
862 _('%(currency)s%(total_amount_with_vat)s (with %(percent_vat)s%% VAT)') % b,
863 u'%(currency)s%(total_amount)s' % b
864 )
865 items.append ([
866 close_date,
867 b['invoice_id'],
868 amount,
869 gmTools.coalesce(b['comment'], u'')
870 ])
871 lctrl.set_string_items(items)
872 lctrl.set_data(bills)
873
874 return gmListWidgets.get_choices_from_list (
875 parent = parent,
876 caption = _('Showing bills.'),
877 columns = [_('Close date'), _('Invoice ID'), _('Value'), _('Comment')],
878 single_selection = True,
879 edit_callback = edit,
880 delete_callback = delete,
881 refresh_callback = refresh,
882 middle_extra_button = (
883 u'PDF',
884 _('Create if necessary, and show the corresponding invoice PDF'),
885 show_pdf
886 ),
887 right_extra_button = (
888 _('Unbill'),
889 _('Select and remove items from a bill.'),
890 remove_items
891 ),
892 list_tooltip_callback = get_tooltip
893 )
894
895
896 from Gnumed.wxGladeWidgets import wxgBillEAPnl
897
898 -class cBillEAPnl(wxgBillEAPnl.wxgBillEAPnl, gmEditArea.cGenericEditAreaMixin):
899
915
916
917
918
919
920
921
923 validity = True
924
925
926 if not self._PRW_close_date.is_valid_timestamp(allow_empty = False):
927 self._PRW_close_date.SetFocus()
928
929 return validity
930
934
936 self.data['close_date'] = self._PRW_close_date.GetData()
937 self.data['apply_vat'] = self._CHBOX_vat_applies.GetValue()
938 self.data['comment'] = self._TCTRL_comment.GetValue()
939 self.data.save()
940 return True
941
944
946 self._refresh_as_new()
947
975
976
977
988
1003
1004
1005
1006
1008
1009 if bill_item is not None:
1010 if bill_item.is_in_use:
1011 gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit already invoiced bill item.'), beep = True)
1012 return False
1013
1014 ea = cBillItemEAPnl(parent = parent, id = -1)
1015 ea.data = bill_item
1016 ea.mode = gmTools.coalesce(bill_item, 'new', 'edit')
1017 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = single_entry)
1018 dlg.SetTitle(gmTools.coalesce(bill_item, _('Adding new bill item'), _('Editing bill item')))
1019 if dlg.ShowModal() == wx.ID_OK:
1020 dlg.Destroy()
1021 return True
1022 dlg.Destroy()
1023 return False
1024
1026
1027 if parent is None:
1028 parent = wx.GetApp().GetTopWindow()
1029
1030 def edit(item=None):
1031 return edit_bill_item(parent = parent, bill_item = item, single_entry = (item is not None))
1032
1033 def delete(item):
1034 if item.is_in_use is not None:
1035 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete already invoiced bill items.'), beep = True)
1036 return False
1037 gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item'])
1038 return True
1039
1040 def get_tooltip(item):
1041 if item is None:
1042 return None
1043 return item.format()
1044
1045 def refresh(lctrl):
1046 b_items = gmBilling.get_bill_items(pk_patient = pk_patient)
1047 items = [ [
1048 gmDateTime.pydt_strftime(b['date_to_bill'], '%Y %b %d', accuracy = gmDateTime.acc_days),
1049 b['unit_count'],
1050 u'%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], u'', u' - %s')),
1051 b['currency'],
1052 u'%s (%s %s %s%s%s)' % (
1053 b['total_amount'],
1054 b['unit_count'],
1055 gmTools.u_multiply,
1056 b['net_amount_per_unit'],
1057 gmTools.u_multiply,
1058 b['amount_multiplier']
1059 ),
1060 u'%s (%s%%)' % (
1061 b['vat'],
1062 b['vat_multiplier'] * 100
1063 ),
1064 u'%s (%s)' % (b['catalog_short'], b['catalog_version']),
1065 b['pk_bill_item']
1066 ] for b in b_items ]
1067 lctrl.set_string_items(items)
1068 lctrl.set_data(b_items)
1069
1070 gmListWidgets.get_choices_from_list (
1071 parent = parent,
1072
1073 caption = _('Showing bill items.'),
1074 columns = [_('Date'), _('Count'), _('Description'), _('$__replace_by_your_currency_symbol')[:-len('__replace_by_your_currency_symbol')], _('Value'), _('VAT'), _('Catalog'), u'#'],
1075 single_selection = True,
1076 new_callback = edit,
1077 edit_callback = edit,
1078 delete_callback = delete,
1079 refresh_callback = refresh,
1080 list_tooltip_callback = get_tooltip
1081 )
1082
1083
1085 """A list for managing a patient's bill items.
1086
1087 Does NOT act on/listen to the current patient.
1088 """
1108
1109
1110
1111 - def refresh(self, *args, **kwargs):
1112 if self.__identity is None:
1113 self._LCTRL_items.set_string_items()
1114 return
1115
1116 b_items = gmBilling.get_bill_items(pk_patient = self.__identity.ID, non_invoiced_only = self.__show_non_invoiced_only)
1117 items = [ [
1118 gmDateTime.pydt_strftime(b['date_to_bill'], '%Y %b %d', accuracy = gmDateTime.acc_days),
1119 b['unit_count'],
1120 u'%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], u'', u' - %s')),
1121 b['currency'],
1122 b['total_amount'],
1123 u'%s (%s%%)' % (
1124 b['vat'],
1125 b['vat_multiplier'] * 100
1126 ),
1127 u'%s (%s)' % (b['catalog_short'], b['catalog_version']),
1128 u'%s %s %s %s %s' % (
1129 b['unit_count'],
1130 gmTools.u_multiply,
1131 b['net_amount_per_unit'],
1132 gmTools.u_multiply,
1133 b['amount_multiplier']
1134 ),
1135 gmTools.coalesce(b['pk_bill'], gmTools.u_diameter),
1136 b['pk_encounter_to_bill'],
1137 b['pk_bill_item']
1138 ] for b in b_items ]
1139
1140 self._LCTRL_items.set_string_items(items = items)
1141 self._LCTRL_items.set_column_widths()
1142 self._LCTRL_items.set_data(data = b_items)
1143
1144
1145
1147 self._LCTRL_items.set_columns(columns = [
1148 _('Charge date'),
1149 _('Count'),
1150 _('Description'),
1151 _('$__replace_by_your_currency_symbol')[:-len('__replace_by_your_currency_symbol')],
1152 _('Value'),
1153 _('VAT'),
1154 _('Catalog'),
1155 _('Count %s Value %s Factor') % (gmTools.u_multiply, gmTools.u_multiply),
1156 _('Invoice'),
1157 _('Encounter'),
1158 u'#'
1159 ])
1160 self._LCTRL_items.item_tooltip_callback = self._get_item_tooltip
1161
1162
1163
1164
1165
1166 self.left_extra_button = (
1167 _('Invoice selected items'),
1168 _('Create invoice from selected items.'),
1169 self._invoice_selected_items
1170 )
1171 self.middle_extra_button = (
1172 _('Bills'),
1173 _('Browse bills of this patient.'),
1174 self._browse_bills
1175 )
1176 self.right_extra_button = (
1177 _('Billables'),
1178 _('Browse list of billables.'),
1179 self._browse_billables
1180 )
1181
1183 return edit_bill_item(parent = self, bill_item = None, single_entry = False)
1184
1186 return edit_bill_item(parent = self, bill_item = bill_item, single_entry = True)
1187
1189 if item['pk_bill'] is not None:
1190 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete already invoiced bill items.'), beep = True)
1191 return False
1192 go_ahead = gmGuiHelpers.gm_show_question (
1193 _( 'Do you really want to delete this\n'
1194 'bill item from the patient ?'),
1195 _('Deleting bill item')
1196 )
1197 if not go_ahead:
1198 return False
1199 gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item'])
1200 return True
1201
1206
1209
1229
1233
1236
1237
1238
1240 return self.__identity
1241
1245
1246 identity = property(_get_identity, _set_identity)
1247
1249 return self.__show_non_invoiced_only
1250
1252 self.__show_non_invoiced_only = value
1253 self.refresh()
1254
1255 show_non_invoiced_only = property(_get_show_non_invoiced_only, _set_show_non_invoiced_only)
1256
1257
1258 from Gnumed.wxGladeWidgets import wxgBillItemEAPnl
1259
1260 -class cBillItemEAPnl(wxgBillItemEAPnl.wxgBillItemEAPnl, gmEditArea.cGenericEditAreaMixin):
1261
1279
1283
1284
1285
1287
1288 validity = True
1289
1290 if self._TCTRL_factor.GetValue().strip() == u'':
1291 validity = False
1292 self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = False)
1293 self._TCTRL_factor.SetFocus()
1294 else:
1295 converted, factor = gmTools.input2decimal(self._TCTRL_factor.GetValue())
1296 if not converted:
1297 validity = False
1298 self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = False)
1299 self._TCTRL_factor.SetFocus()
1300 else:
1301 self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = True)
1302
1303 if self._TCTRL_amount.GetValue().strip() == u'':
1304 validity = False
1305 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False)
1306 self._TCTRL_amount.SetFocus()
1307 else:
1308 converted, factor = gmTools.input2decimal(self._TCTRL_amount.GetValue())
1309 if not converted:
1310 validity = False
1311 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False)
1312 self._TCTRL_amount.SetFocus()
1313 else:
1314 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = True)
1315
1316 if self._TCTRL_count.GetValue().strip() == u'':
1317 validity = False
1318 self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = False)
1319 self._TCTRL_count.SetFocus()
1320 else:
1321 converted, factor = gmTools.input2decimal(self._TCTRL_count.GetValue())
1322 if not converted:
1323 validity = False
1324 self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = False)
1325 self._TCTRL_count.SetFocus()
1326 else:
1327 self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = True)
1328
1329 if self._PRW_date.is_valid_timestamp(allow_empty = True):
1330 self._PRW_date.display_as_valid(True)
1331 else:
1332 validity = False
1333 self._PRW_date.display_as_valid(False)
1334 self._PRW_date.SetFocus()
1335
1336 if self._PRW_encounter.GetData() is None:
1337 validity = False
1338 self._PRW_encounter.display_as_valid(False)
1339 self._PRW_encounter.SetFocus()
1340 else:
1341 self._PRW_encounter.display_as_valid(True)
1342
1343 if self._PRW_billable.GetData() is None:
1344 validity = False
1345 self._PRW_billable.display_as_valid(False)
1346 self._PRW_billable.SetFocus()
1347 else:
1348 self._PRW_billable.display_as_valid(True)
1349
1350 return validity
1351
1367
1376
1389
1391 self._PRW_billable.SetText()
1392 self._TCTRL_count.SetValue(u'1')
1393 self._TCTRL_amount.SetValue(u'')
1394 self._TCTRL_comment.SetValue(u'')
1395
1396 self._PRW_billable.Enable()
1397 self._PRW_billable.SetFocus()
1398
1400 self._PRW_billable.set_from_pk(self.data['pk_billable'])
1401 self._PRW_encounter.SetData(self.data['pk_encounter_to_bill'])
1402 self._PRW_date.SetData(data = self.data['raw_date_to_bill'])
1403 self._TCTRL_count.SetValue(u'%s' % self.data['unit_count'])
1404 self._TCTRL_amount.SetValue(u'%s' % self.data['net_amount_per_unit'])
1405 self._LBL_currency.SetLabel(self.data['currency'])
1406 self._TCTRL_factor.SetValue(u'%s' % self.data['amount_multiplier'])
1407 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['item_detail'], u''))
1408
1409 self._PRW_billable.Disable()
1410 self._PRW_date.SetFocus()
1411
1413 if item is None:
1414 return
1415 if self._TCTRL_amount.GetValue().strip() != u'':
1416 return
1417 val = u'%s' % self._PRW_billable.GetData(as_instance = True)['raw_amount']
1418 wx.CallAfter(self._TCTRL_amount.SetValue, val)
1419
1420
1421
1422
1423 from Gnumed.wxGladeWidgets import wxgBillingPluginPnl
1424
1425 -class cBillingPluginPnl(wxgBillingPluginPnl.wxgBillingPluginPnl, gmRegetMixin.cRegetOnPaintMixin):
1431
1433 self._PNL_bill_items.identity = None
1434 self._CHBOX_show_non_invoiced_only.SetValue(1)
1435 self._PRW_billable.SetText(u'', None)
1436 self._TCTRL_factor.SetValue(u'1.0')
1437 self._TCTRL_factor.Disable()
1438 self._TCTRL_details.SetValue(u'')
1439 self._TCTRL_details.Disable()
1440
1441
1442
1450
1452 wx.CallAfter(self.__reset_ui)
1453
1455 wx.CallAfter(self._schedule_data_reget)
1456
1458 wx.CallAfter(self._schedule_data_reget)
1459
1462
1487
1489 if billable is None:
1490 self._TCTRL_factor.Disable()
1491 self._TCTRL_details.Disable()
1492 self._BTN_insert_item.Disable()
1493 else:
1494 self._TCTRL_factor.Enable()
1495 self._TCTRL_details.Enable()
1496 self._BTN_insert_item.Enable()
1497
1498
1499
1503
1504
1505
1506 if __name__ == '__main__':
1507
1508 if len(sys.argv) < 2:
1509 sys.exit()
1510
1511 if sys.argv[1] != 'test':
1512 sys.exit()
1513
1514 from Gnumed.pycommon import gmI18N
1515 gmI18N.activate_locale()
1516 gmI18N.install_domain(domain = 'gnumed')
1517
1518
1519 app = wx.PyWidgetTester(size = (600, 600))
1520
1521
1522 app.MainLoop()
1523