| Home | Trees | Indices | Help |
|
|---|
|
|
1 """GNUmed billing handling widgets.
2 """
3 #================================================================
4 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
5 __license__ = "GPL v2 or later"
6
7 import logging
8 import sys
9
10
11 import wx
12
13
14 if __name__ == '__main__':
15 sys.path.insert(0, '../../')
16 from Gnumed.pycommon import gmTools
17 from Gnumed.pycommon import gmDateTime
18 from Gnumed.pycommon import gmMatchProvider
19 from Gnumed.pycommon import gmDispatcher
20 from Gnumed.pycommon import gmPG2
21 from Gnumed.pycommon import gmCfg
22 from Gnumed.pycommon import gmPrinting
23 from Gnumed.pycommon import gmNetworkTools
24
25 from Gnumed.business import gmBilling
26 from Gnumed.business import gmPerson
27 from Gnumed.business import gmStaff
28 from Gnumed.business import gmDocuments
29 from Gnumed.business import gmSurgery
30 from Gnumed.business import gmForms
31 from Gnumed.business import gmDemographicRecord
32
33 from Gnumed.wxpython import gmListWidgets
34 from Gnumed.wxpython import gmRegetMixin
35 from Gnumed.wxpython import gmPhraseWheel
36 from Gnumed.wxpython import gmGuiHelpers
37 from Gnumed.wxpython import gmEditArea
38 from Gnumed.wxpython import gmPersonContactWidgets
39 from Gnumed.wxpython import gmMacro
40 from Gnumed.wxpython import gmFormWidgets
41 from Gnumed.wxpython import gmDocumentWidgets
42 from Gnumed.wxpython import gmDataPackWidgets
43
44
45 _log = logging.getLogger('gm.ui')
46
47 #================================================================
49
50 if parent is None:
51 parent = wx.GetApp().GetTopWindow()
52 #------------------------------------------------------------
53 # def edit(substance=None):
54 # return edit_consumable_substance(parent = parent, substance = substance, single_entry = (substance is not None))
55 #------------------------------------------------------------
56 def delete(billable):
57 if billable.is_in_use:
58 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete this billable item. It is in use.'), beep = True)
59 return False
60 return gmBilling.delete_billable(pk_billable = billable['pk_billable'])
61 #------------------------------------------------------------
62 def get_tooltip(item):
63 if item is None:
64 return None
65 return item.format()
66 #------------------------------------------------------------
67 def refresh(lctrl):
68 billables = gmBilling.get_billables()
69 items = [ [
70 b['billable_code'],
71 b['billable_description'],
72 u'%s %s' % (b['raw_amount'], b['currency']),
73 u'%s (%s)' % (b['catalog_short'], b['catalog_version']),
74 gmTools.coalesce(b['comment'], u''),
75 b['pk_billable']
76 ] for b in billables ]
77 lctrl.set_string_items(items)
78 lctrl.set_data(billables)
79 #------------------------------------------------------------
80 def manage_data_packs(billable):
81 gmDataPackWidgets.manage_data_packs(parent = parent)
82 return True
83 #------------------------------------------------------------
84 def browse_catalogs(billable):
85 dbcfg = gmCfg.cCfgSQL()
86 url = dbcfg.get2 (
87 option = 'external.urls.schedules_of_fees',
88 workplace = gmSurgery.gmCurrentPractice().active_workplace,
89 bias = 'user',
90 default = u'http://www.e-bis.de/goae/defaultFrame.htm'
91 )
92 gmNetworkTools.open_url_in_browser(url = url)
93 return False
94 #------------------------------------------------------------
95 msg = _('\nThese are the items for billing registered with GNUmed.\n')
96
97 gmListWidgets.get_choices_from_list (
98 parent = parent,
99 msg = msg,
100 caption = _('Showing billable items.'),
101 columns = [_('Code'), _('Description'), _('Value'), _('Catalog'), _('Comment'), u'#'],
102 single_selection = True,
103 #new_callback = edit,
104 #edit_callback = edit,
105 delete_callback = delete,
106 refresh_callback = refresh,
107 middle_extra_button = (
108 _('Data packs'),
109 _('Browse and install billing catalog (schedule of fees) data packs'),
110 manage_data_packs
111 ),
112 right_extra_button = (
113 _('Catalogs (WWW)'),
114 _('Browse billing catalogs (schedules of fees) on the web'),
115 browse_catalogs
116 ),
117 list_tooltip_callback = get_tooltip
118 )
119
120 #================================================================
122
124 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
125 query = u"""
126 SELECT -- DISTINCT ON (label)
127 r_vb.pk_billable
128 AS data,
129 r_vb.billable_code || ': ' || r_vb.billable_description || ' (' || r_vb.catalog_short || ' - ' || r_vb.catalog_version || ')'
130 AS list_label,
131 r_vb.billable_code || ' (' || r_vb.catalog_short || ' - ' || r_vb.catalog_version || ')'
132 AS field_label
133 FROM
134 ref.v_billables r_vb
135 WHERE
136 r_vb.active
137 AND (
138 r_vb.billable_code %(fragment_condition)s
139 OR
140 r_vb.billable_description %(fragment_condition)s
141 )
142 ORDER BY list_label
143 LIMIT 20
144 """
145 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query)
146 mp.setThresholds(1, 2, 4)
147 self.matcher = mp
148 #------------------------------------------------------------
151 #------------------------------------------------------------
153 if self.GetData() is None:
154 return None
155 billable = gmBilling.cBillable(aPK_obj = self._data.values()[0]['data'])
156 return billable.format()
157 #------------------------------------------------------------
159 val = u'%s (%s - %s)' % (
160 instance['billable_code'],
161 instance['catalog_short'],
162 instance['catalog_version']
163 )
164 self.SetText(value = val, data = instance['pk_billable'])
165 #------------------------------------------------------------
168
169 #================================================================
170 # invoice related widgets
171 #----------------------------------------------------------------
173
174 if parent is None:
175 parent = wx.GetApp().GetTopWindow()
176
177 template = gmFormWidgets.manage_form_templates (
178 parent = parent,
179 template_types = ['invoice']
180 )
181
182 if template is None:
183 gmDispatcher.send(signal = 'statustext', msg = _('No invoice template configured.'), beep = True)
184 return None
185
186 if template['engine'] != u'L':
187 gmDispatcher.send(signal = 'statustext', msg = _('No invoice template configured.'), beep = True)
188 return None
189
190 if with_vat:
191 option = u'form_templates.invoice_with_vat'
192 else:
193 option = u'form_templates.invoice_no_vat'
194
195 dbcfg = gmCfg.cCfgSQL()
196 dbcfg.set (
197 workplace = gmSurgery.gmCurrentPractice().active_workplace,
198 option = option,
199 value = u'%s - %s' % (template['name_long'], template['external_version'])
200 )
201
202 return template
203 #----------------------------------------------------------------
205
206 dbcfg = gmCfg.cCfgSQL()
207 if with_vat:
208 option = u'form_templates.invoice_with_vat'
209 else:
210 option = u'form_templates.invoice_no_vat'
211
212 template = dbcfg.get2 (
213 option = option,
214 workplace = gmSurgery.gmCurrentPractice().active_workplace,
215 bias = 'user'
216 )
217
218 if template is None:
219 template = configure_invoice_template(parent = parent, with_vat = with_vat)
220 if template is None:
221 gmGuiHelpers.gm_show_error (
222 aMessage = _('There is no invoice template configured.'),
223 aTitle = _('Getting invoice template')
224 )
225 return None
226 else:
227 try:
228 name, ver = template.split(u' - ')
229 except:
230 _log.exception('problem splitting invoice template name [%s]', template)
231 gmDispatcher.send(signal = 'statustext', msg = _('Problem loading invoice template.'), beep = True)
232 return None
233 template = gmForms.get_form_template(name_long = name, external_version = ver)
234 if template is None:
235 gmGuiHelpers.gm_show_error (
236 aMessage = _('Cannot load invoice template [%s - %s]') % (name, ver),
237 aTitle = _('Getting invoice template')
238 )
239 return None
240
241 return template
242
243 #================================================================
244 # per-patient bill related widgets
245 #----------------------------------------------------------------
247
248 if bill is None:
249 # manually creating bills is not yet supported
250 return
251
252 ea = cBillEAPnl(parent = parent, id = -1)
253 ea.data = bill
254 ea.mode = gmTools.coalesce(bill, 'new', 'edit')
255 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = single_entry)
256 dlg.SetTitle(gmTools.coalesce(bill, _('Adding new bill'), _('Editing bill')))
257 if dlg.ShowModal() == wx.ID_OK:
258 dlg.Destroy()
259 return True
260 dlg.Destroy()
261 return False
262 #----------------------------------------------------------------
264
265 if len(bill_items) == 0:
266 return None
267
268 item = bill_items[0]
269 currency = item['currency']
270 vat = item['vat_multiplier']
271 pat = item['pk_patient']
272
273 # check item consistency
274 has_errors = False
275 for item in bill_items:
276 if (item['currency'] != currency) or (
277 item['vat_multiplier'] != vat) or (
278 item['pk_patient'] != pat
279 ):
280 msg = _(
281 'All items to be included with a bill must\n'
282 'coincide on currency, VAT, and patient.\n'
283 '\n'
284 'This item does not:\n'
285 '\n'
286 '%s\n'
287 ) % item.format()
288 has_errors = True
289
290 if item['pk_bill'] is not None:
291 msg = _(
292 'This item is already invoiced:\n'
293 '\n'
294 '%s\n'
295 '\n'
296 'Cannot put it on a second bill.'
297 ) % item.format()
298 has_errors = True
299
300 if has_errors:
301 gmGuiHelpers.gm_show_warning(aTitle = _('Checking invoice items'), aMessage = msg)
302 return None
303
304 # create bill
305 bill = gmBilling.create_bill(invoice_id = gmBilling.get_invoice_id(pk_patient = pat))
306 _log.info('created bill [%s]', bill['invoice_id'])
307 bill.add_items(items = bill_items)
308 bill.set_missing_address_from_default()
309
310 return bill
311 #----------------------------------------------------------------
313
314 if None in [ bill['close_date'], bill['pk_receiver_address'] ]:
315 edit_bill(parent = parent, bill = bill, single_entry = True)
316 # cannot invoice open bills
317 if bill['close_date'] is None:
318 _log.error('cannot create invoice from bill, bill not closed')
319 gmGuiHelpers.gm_show_warning (
320 aTitle = _('Creating invoice'),
321 aMessage = _(
322 'Cannot create invoice from bill.\n'
323 '\n'
324 'The bill is not closed.'
325 )
326 )
327 return False
328 # cannot create invoice if no receiver address
329 if bill['pk_receiver_address'] is None:
330 _log.error('cannot create invoice from bill, lacking receiver address')
331 gmGuiHelpers.gm_show_warning (
332 aTitle = _('Creating invoice'),
333 aMessage = _(
334 'Cannot create invoice from bill.\n'
335 '\n'
336 'There is no receiver address.'
337 )
338 )
339 return False
340
341 # find template
342 template = get_invoice_template(parent = parent, with_vat = bill['apply_vat'])
343 if template is None:
344 gmGuiHelpers.gm_show_warning (
345 aTitle = _('Creating invoice'),
346 aMessage = _(
347 'Cannot create invoice from bill\n'
348 'without an invoice template.'
349 )
350 )
351 return False
352
353 # process template
354 try:
355 invoice = template.instantiate()
356 except KeyError:
357 _log.exception('cannot instantiate invoice template [%s]', template)
358 gmGuiHelpers.gm_show_error (
359 aMessage = _('Invalid invoice template [%s - %s (%s)]') % (name, ver, template['engine']),
360 aTitle = _('Printing medication list')
361 )
362 return False
363
364 ph = gmMacro.gmPlaceholderHandler()
365 #ph.debug = True
366 ph.set_cache_value('bill', bill)
367 invoice.substitute_placeholders(data_source = ph)
368 ph.unset_cache_value('bill')
369 pdf_name = invoice.generate_output()
370 if pdf_name is None:
371 gmGuiHelpers.gm_show_error (
372 aMessage = _('Error generating invoice PDF.'),
373 aTitle = _('Creating invoice')
374 )
375 return False
376
377 # keep a copy
378 if keep_a_copy:
379 files2import = []
380 files2import.extend(invoice.final_output_filenames)
381 files2import.extend(invoice.re_editable_filenames)
382 doc = gmDocumentWidgets.save_files_as_new_document (
383 parent = parent,
384 filenames = files2import,
385 document_type = template['instance_type'],
386 review_as_normal = True,
387 reference = bill['invoice_id']
388 )
389 bill['pk_doc'] = doc['pk_doc']
390 bill.save()
391
392 if not print_it:
393 return True
394
395 # print template
396 printed = gmPrinting.print_files(filenames = [pdf_name], jobtype = 'invoice')
397 if not printed:
398 gmGuiHelpers.gm_show_error (
399 aMessage = _('Error printing the invoice.'),
400 aTitle = _('Printing invoice')
401 )
402 return True
403
404 return True
405
406 #----------------------------------------------------------------
408
409 if parent is None:
410 parent = wx.GetApp().GetTopWindow()
411
412 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
413 parent, -1,
414 caption = _('Deleting bill'),
415 question = _(
416 'When deleting the bill [%s]\n'
417 'do you want to keep its items (effectively \"unbilling\" them)\n'
418 'or do you want to also delete the bill items from the patient ?\n'
419 ) % bill['invoice_id'],
420 button_defs = [
421 {'label': _('Delete + keep'), 'tooltip': _('Delete the bill but keep ("unbill") its items.'), 'default': True},
422 {'label': _('Delete all'), 'tooltip': _('Delete both the bill and its items from the patient.')}
423 ],
424 show_checkbox = True,
425 checkbox_msg = _('Also remove invoice PDF'),
426 checkbox_tooltip = _('Also remove the invoice PDF from the document archive (because it will not correspond to the bill anymore).')
427 )
428 button_pressed = dlg.ShowModal()
429 delete_invoice = dlg.checkbox_is_checked()
430 dlg.Destroy()
431
432 if button_pressed == wx.ID_CANCEL:
433 return False
434
435 if button_pressed == wx.ID_YES:
436 for item in bill.bill_items:
437 item['pk_bill'] = None
438 item.save()
439
440 if button_pressed == wx.ID_NO:
441 for item in bill.bill_items:
442 item['pk_bill'] = None
443 item.save()
444 gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item'])
445
446 if delete_invoice:
447 if bill['pk_doc'] is not None:
448 gmDocuments.delete_document (
449 document_id = bill['pk_doc'],
450 encounter_id = gmPerson.cPatient(aPK_obj = bill['pk_patient']).emr.active_encounter['pk_encounter']
451 )
452
453 return gmBilling.delete_bill(pk_bill = bill['pk_bill'])
454
455 #----------------------------------------------------------------
457
458 if bill is None:
459 return False
460
461 list_data = bill.bill_items
462 if len(list_data) == 0:
463 return False
464
465 if parent is None:
466 parent = wx.GetApp().GetTopWindow()
467
468 list_items = [ [
469 gmDateTime.pydt_strftime(b['date_to_bill'], '%x', accuracy = gmDateTime.acc_days),
470 b['unit_count'],
471 u'%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], u'', u' - %s')),
472 u'%s %s (%s %s %s%s%s)' % (
473 b['total_amount'],
474 b['currency'],
475 b['unit_count'],
476 gmTools.u_multiply,
477 b['net_amount_per_unit'],
478 gmTools.u_multiply,
479 b['amount_multiplier']
480 ),
481 u'%s %s (%s%%)' % (
482 b['vat'],
483 b['currency'],
484 b['vat_multiplier'] * 100
485 ),
486 u'%s (%s)' % (b['catalog_short'], b['catalog_version']),
487 b['pk_bill_item']
488 ] for b in list_data ]
489
490 msg = _('Select the items you want to remove from bill [%s]:\n') % bill['invoice_id']
491 items2remove = gmListWidgets.get_choices_from_list (
492 parent = parent,
493 msg = msg,
494 caption = _('Removing items from bill'),
495 columns = [_('Date'), _('Count'), _('Description'), _('Value'), _('VAT'), _('Catalog'), u'#'],
496 single_selection = False,
497 choices = list_items,
498 data = list_data
499 )
500
501 if items2remove is None:
502 return False
503
504 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
505 parent, -1,
506 caption = _('Removing items from bill'),
507 question = _(
508 '%s items selected from bill [%s]\n'
509 '\n'
510 'Do you want to only remove the selected items\n'
511 'from the bill ("unbill" them) or do you want\n'
512 'to delete them entirely from the patient ?\n'
513 '\n'
514 'Note that neither action is reversible.'
515 ) % (
516 len(items2remove),
517 bill['invoice_id']
518 ),
519 button_defs = [
520 {'label': _('"Unbill"'), 'tooltip': _('Only "unbill" items (remove from bill but do not delete from patient).'), 'default': True},
521 {'label': _('Delete'), 'tooltip': _('Completely delete items from the patient.')}
522 ],
523 show_checkbox = True,
524 checkbox_msg = _('Also remove invoice PDF'),
525 checkbox_tooltip = _('Also remove the invoice PDF from the document archive (because it will not correspond to the bill anymore).')
526 )
527 button_pressed = dlg.ShowModal()
528 delete_invoice = dlg.checkbox_is_checked()
529 dlg.Destroy()
530
531 if button_pressed == wx.ID_CANCEL:
532 return False
533
534 # remember this because unlinking/deleting the items
535 # will remove the patient PK from the bill
536 pk_patient = bill['pk_patient']
537
538 for item in items2remove:
539 item['pk_bill'] = None
540 item.save()
541 if button_pressed == wx.ID_NO:
542 gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item'])
543
544 if delete_invoice:
545 if bill['pk_doc'] is not None:
546 gmDocuments.delete_document (
547 document_id = bill['pk_doc'],
548 encounter_id = gmPerson.cPatient(aPK_obj = pk_patient).emr.active_encounter['pk_encounter']
549 )
550
551 # delete bill, too, if empty
552 if len(bill.bill_items) == 0:
553 gmBilling.delete_bill(pk_bill = bill['pk_bill'])
554
555 return True
556 #----------------------------------------------------------------
558
559 if parent is None:
560 parent = wx.GetApp().GetTopWindow()
561
562 #------------------------------------------------------------
563 def show_pdf(bill):
564 if bill is None:
565 return False
566
567 # find invoice
568 invoice = bill.invoice
569 if invoice is not None:
570 success, msg = invoice.parts[-1].display_via_mime()
571 if not success:
572 gmGuiHelpers.gm_show_error(aMessage = msg, aTitle = _('Displaying invoice'))
573 return False
574
575 # create it ?
576 create_it = gmGuiHelpers.gm_show_question (
577 title = _('Displaying invoice'),
578 question = _(
579 'Cannot find an existing\n'
580 'invoice PDF for this bill.\n'
581 '\n'
582 'Do you want to create one ?'
583 ),
584 )
585 if not create_it:
586 return False
587
588 # prepare invoicing
589 if not bill.set_missing_address_from_default():
590 gmGuiHelpers.gm_show_warning (
591 aTitle = _('Creating invoice'),
592 aMessage = _(
593 'There is no pre-configured billing address.\n'
594 '\n'
595 'Select the address you want to send the bill to.'
596 )
597 )
598 edit_bill(parent = parent, bill = bill, single_entry = True)
599 if bill['pk_receiver_address'] is None:
600 return False
601 if bill['close_date'] is None:
602 bill['close_date'] = gmDateTime.pydt_now_here()
603 bill.save()
604
605 return create_invoice_from_bill(parent = parent, bill = bill, print_it = True, keep_a_copy = True)
606 #------------------------------------------------------------
607 def edit(bill):
608 return edit_bill(parent = parent, bill = bill, single_entry = True)
609 #------------------------------------------------------------
610 def delete(bill):
611 return delete_bill(parent = parent, bill = bill)
612 #------------------------------------------------------------
613 def remove_items(bill):
614 return remove_items_from_bill(parent = parent, bill = bill)
615 #------------------------------------------------------------
616 def get_tooltip(item):
617 if item is None:
618 return None
619 return item.format()
620 #------------------------------------------------------------
621 def refresh(lctrl):
622 if patient is None:
623 bills = gmBilling.get_bills()
624 else:
625 bills = gmBilling.get_bills(pk_patient = patient.ID)
626 items = []
627 for b in bills:
628 if b['close_date'] is None:
629 close_date = _('<open>')
630 else:
631 close_date = gmDateTime.pydt_strftime(b['close_date'], '%Y %b %d')
632 items.append([
633 close_date,
634 b['invoice_id'],
635 gmTools.bool2subst (
636 b['apply_vat'],
637 _('%s %s (with %s%% VAT)') % (b['total_amount_with_vat'], b['currency'], b['percent_vat']),
638 u'%s %s' % (b['total_amount'], b['currency'])
639 )
640 ])
641 lctrl.set_string_items(items)
642 lctrl.set_data(bills)
643 #------------------------------------------------------------
644 return gmListWidgets.get_choices_from_list (
645 parent = parent,
646 caption = _('Showing bills.'),
647 columns = [_('Close date'), _('Invoice ID'), _('Value')],
648 single_selection = True,
649 edit_callback = edit,
650 delete_callback = delete,
651 refresh_callback = refresh,
652 middle_extra_button = (
653 u'PDF',
654 _('Create if necessary, and show the corresponding invoice PDF'),
655 show_pdf
656 ),
657 right_extra_button = (
658 _('Unbill'),
659 _('Select and remove items from a bill.'),
660 remove_items
661 ),
662 list_tooltip_callback = get_tooltip
663 )
664
665 #----------------------------------------------------------------
666 from Gnumed.wxGladeWidgets import wxgBillEAPnl
667
669
671
672 try:
673 data = kwargs['bill']
674 del kwargs['bill']
675 except KeyError:
676 data = None
677
678 wxgBillEAPnl.wxgBillEAPnl.__init__(self, *args, **kwargs)
679 gmEditArea.cGenericEditAreaMixin.__init__(self)
680
681 self.mode = 'new'
682 self.data = data
683 if data is not None:
684 self.mode = 'edit'
685
686 # self.__init_ui()
687 #----------------------------------------------------------------
688 # def __init_ui(self):
689 #----------------------------------------------------------------
690 # generic Edit Area mixin API
691 #----------------------------------------------------------------
693 validity = True
694
695 # flag but do not count as wrong
696 if not self._PRW_close_date.is_valid_timestamp(allow_empty = False):
697 self._PRW_close_date.SetFocus()
698
699 return validity
700 #----------------------------------------------------------------
704 #----------------------------------------------------------------
706 self.data['close_date'] = self._PRW_close_date.GetData()
707 self.data['apply_vat'] = self._CHBOX_vat_applies.GetValue()
708 self.data.save()
709 return True
710 #----------------------------------------------------------------
713 #----------------------------------------------------------------
716 #----------------------------------------------------------------
718 self._TCTRL_invoice_id.SetValue(self.data['invoice_id'])
719 self._PRW_close_date.SetText(data = self.data['close_date'])
720
721 self.data.set_missing_address_from_default()
722 if self.data['pk_receiver_address'] is None:
723 self._TCTRL_address.SetValue(u'')
724 else:
725 adr = self.data.address
726 self._TCTRL_address.SetValue(adr.format(single_line = True, show_type = False))
727
728 self._TCTRL_value.SetValue(u'%s %s' % (
729 self.data['total_amount'],
730 self.data['currency']
731 ))
732 self._CHBOX_vat_applies.SetValue(self.data['apply_vat'])
733 self._CHBOX_vat_applies.SetLabel(_('&VAT applies (%s%%)') % self.data['percent_vat'])
734 if self.data['apply_vat']:
735 self._TCTRL_value_with_vat.SetValue(u'%s %s %s %s %s %s %s' % (
736 gmTools.u_corresponds_to,
737 self.data['total_vat'],
738 self.data['currency'],
739 gmTools.u_right_arrow,
740 gmTools.u_sum,
741 self.data['total_amount_with_vat'],
742 self.data['currency']
743 ))
744 else:
745 self._TCTRL_value_with_vat.SetValue(u'')
746
747 self._PRW_close_date.SetFocus()
748 #----------------------------------------------------------------
749 # event handling
750 #----------------------------------------------------------------
752 if self._CHBOX_vat_applies.GetValue():
753 self._TCTRL_value_with_vat.SetValue(u'%s %s %s %s %s %s %s' % (
754 gmTools.u_corresponds_to,
755 self.data['total_vat'],
756 self.data['currency'],
757 gmTools.u_right_arrow,
758 gmTools.u_sum,
759 self.data['total_amount_with_vat'],
760 self.data['currency']
761 ))
762 return
763 self._TCTRL_value_with_vat.SetValue(u'')
764 #----------------------------------------------------------------
775
776 #================================================================
777 # per-patient bill items related widgets
778 #----------------------------------------------------------------
780
781 if bill_item is not None:
782 if bill_item.is_in_use:
783 gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit already invoiced bill item.'), beep = True)
784 return False
785
786 ea = cBillItemEAPnl(parent = parent, id = -1)
787 ea.data = bill_item
788 ea.mode = gmTools.coalesce(bill_item, 'new', 'edit')
789 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = single_entry)
790 dlg.SetTitle(gmTools.coalesce(bill_item, _('Adding new bill item'), _('Editing bill item')))
791 if dlg.ShowModal() == wx.ID_OK:
792 dlg.Destroy()
793 return True
794 dlg.Destroy()
795 return False
796 #----------------------------------------------------------------
798
799 if parent is None:
800 parent = wx.GetApp().GetTopWindow()
801 #------------------------------------------------------------
802 def edit(item=None):
803 return edit_bill_item(parent = parent, bill_item = item, single_entry = (item is not None))
804 #------------------------------------------------------------
805 def delete(item):
806 if item.is_in_use is not None:
807 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete already invoiced bill items.'), beep = True)
808 return False
809 gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item'])
810 return True
811 #------------------------------------------------------------
812 def get_tooltip(item):
813 if item is None:
814 return None
815 return item.format()
816 #------------------------------------------------------------
817 def refresh(lctrl):
818 b_items = gmBilling.get_bill_items(pk_patient = pk_patient)
819 items = [ [
820 gmDateTime.pydt_strftime(b['date_to_bill'], '%x', accuracy = gmDateTime.acc_days),
821 b['unit_count'],
822 u'%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], u'', u' - %s')),
823 u'%s %s (%s %s %s%s%s)' % (
824 b['total_amount'],
825 b['currency'],
826 b['unit_count'],
827 gmTools.u_multiply,
828 b['net_amount_per_unit'],
829 gmTools.u_multiply,
830 b['amount_multiplier']
831 ),
832 u'%s %s (%s%%)' % (
833 b['vat'],
834 b['currency'],
835 b['vat_multiplier'] * 100
836 ),
837 u'%s (%s)' % (b['catalog_short'], b['catalog_version']),
838 b['pk_bill_item']
839 ] for b in b_items ]
840 lctrl.set_string_items(items)
841 lctrl.set_data(b_items)
842 #------------------------------------------------------------
843 gmListWidgets.get_choices_from_list (
844 parent = parent,
845 #msg = msg,
846 caption = _('Showing bill items.'),
847 columns = [_('Date'), _('Count'), _('Description'), _('Value'), _('VAT'), _('Catalog'), u'#'],
848 single_selection = True,
849 new_callback = edit,
850 edit_callback = edit,
851 delete_callback = delete,
852 refresh_callback = refresh,
853 list_tooltip_callback = get_tooltip
854 )
855
856 #------------------------------------------------------------
858 """A list for managing a patient's bill items.
859
860 Does NOT act on/listen to the current patient.
861 """
863
864 try:
865 self.__identity = kwargs['identity']
866 del kwargs['identity']
867 except KeyError:
868 self.__identity = None
869
870 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs)
871
872 self.new_callback = self._add_item
873 self.edit_callback = self._edit_item
874 self.delete_callback = self._del_item
875 self.refresh_callback = self.refresh
876
877 self.__show_non_invoiced_only = True
878
879 self.__init_ui()
880 self.refresh()
881 #--------------------------------------------------------
882 # external API
883 #--------------------------------------------------------
885 if self.__identity is None:
886 self._LCTRL_items.set_string_items()
887 return
888
889 b_items = gmBilling.get_bill_items(pk_patient = self.__identity.ID, non_invoiced_only = self.__show_non_invoiced_only)
890 items = [ [
891 gmDateTime.pydt_strftime(b['date_to_bill'], '%x', accuracy = gmDateTime.acc_days),
892 b['unit_count'],
893 u'%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], u'', u' - %s')),
894 u'%s %s' % (
895 b['total_amount'],
896 b['currency']
897 ),
898 u'%s %s (%s%%)' % (
899 b['vat'],
900 b['currency'],
901 b['vat_multiplier'] * 100
902 ),
903 u'%s (%s)' % (b['catalog_short'], b['catalog_version']),
904 u'%s %s %s %s %s' % (
905 b['unit_count'],
906 gmTools.u_multiply,
907 b['net_amount_per_unit'],
908 gmTools.u_multiply,
909 b['amount_multiplier']
910 ),
911 gmTools.coalesce(b['pk_bill'], gmTools.u_diameter),
912 b['pk_encounter_to_bill'],
913 b['pk_bill_item']
914 ] for b in b_items ]
915
916 self._LCTRL_items.set_string_items(items = items)
917 self._LCTRL_items.set_column_widths()
918 self._LCTRL_items.set_data(data = b_items)
919 #--------------------------------------------------------
920 # internal helpers
921 #--------------------------------------------------------
923 self._LCTRL_items.set_columns(columns = [
924 _('Charge date'),
925 _('Count'),
926 _('Description'),
927 _('Value'),
928 _('VAT'),
929 _('Catalog'),
930 _('Count %s Value %s Factor') % (gmTools.u_multiply, gmTools.u_multiply),
931 _('Invoice'),
932 _('Encounter'),
933 u'#'
934 ])
935 self._LCTRL_items.item_tooltip_callback = self._get_item_tooltip
936 # self.left_extra_button = (
937 # _('Select pending'),
938 # _('Select non-invoiced (pending) items.'),
939 # self._select_pending_items
940 # )
941 self.left_extra_button = (
942 _('Invoice selected items'),
943 _('Create invoice from selected items.'),
944 self._invoice_selected_items
945 )
946 self.middle_extra_button = (
947 _('Bills'),
948 _('Browse bills of this patient.'),
949 self._browse_bills
950 )
951 self.right_extra_button = (
952 _('Billables'),
953 _('Browse list of billables.'),
954 self._browse_billables
955 )
956 #--------------------------------------------------------
958 return edit_bill_item(parent = self, bill_item = None, single_entry = False)
959 #--------------------------------------------------------
961 return edit_bill_item(parent = self, bill_item = bill_item, single_entry = True)
962 #--------------------------------------------------------
964 if item['pk_bill'] is not None:
965 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete already invoiced bill items.'), beep = True)
966 return False
967 go_ahead = gmGuiHelpers.gm_show_question (
968 _( 'Do you really want to delete this\n'
969 'bill item from the patient ?'),
970 _('Deleting bill item')
971 )
972 if not go_ahead:
973 return False
974 gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item'])
975 return True
976 #--------------------------------------------------------
981 #--------------------------------------------------------
984 #--------------------------------------------------------
986 bill_items = self._LCTRL_items.get_selected_item_data()
987 bill = create_bill_from_items(bill_items)
988 if bill is None:
989 return
990 if bill['pk_receiver_address'] is None:
991 gmGuiHelpers.gm_show_error (
992 aMessage = _(
993 'Cannot create invoice.\n'
994 '\n'
995 'No receiver address selected.'
996 ),
997 aTitle = _('Creating invoice')
998 )
999 return
1000 if bill['close_date'] is None:
1001 bill['close_date'] = gmDateTime.pydt_now_here()
1002 bill.save()
1003 create_invoice_from_bill(parent = self, bill = bill, print_it = True, keep_a_copy = True)
1004 #--------------------------------------------------------
1008 #--------------------------------------------------------
1011 #--------------------------------------------------------
1012 # properties
1013 #--------------------------------------------------------
1016
1020
1021 identity = property(_get_identity, _set_identity)
1022 #--------------------------------------------------------
1025
1029
1030 show_non_invoiced_only = property(_get_show_non_invoiced_only, _set_show_non_invoiced_only)
1031
1032 #------------------------------------------------------------
1033 from Gnumed.wxGladeWidgets import wxgBillItemEAPnl
1034
1036
1038
1039 try:
1040 data = kwargs['bill_item']
1041 del kwargs['bill_item']
1042 except KeyError:
1043 data = None
1044
1045 wxgBillItemEAPnl.wxgBillItemEAPnl.__init__(self, *args, **kwargs)
1046 gmEditArea.cGenericEditAreaMixin.__init__(self)
1047
1048 self.mode = 'new'
1049 self.data = data
1050 if data is not None:
1051 self.mode = 'edit'
1052
1053 self.__init_ui()
1054 #----------------------------------------------------------------
1056 self._PRW_encounter.set_context(context = 'patient', val = gmPerson.gmCurrentPatient().ID)
1057 self._PRW_billable.add_callback_on_selection(self._on_billable_selected)
1058 #----------------------------------------------------------------
1059 # generic Edit Area mixin API
1060 #----------------------------------------------------------------
1062
1063 validity = True
1064
1065 if self._TCTRL_factor.GetValue().strip() == u'':
1066 validity = False
1067 self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = False)
1068 self._TCTRL_factor.SetFocus()
1069 else:
1070 converted, factor = gmTools.input2decimal(self._TCTRL_factor.GetValue())
1071 if not converted:
1072 validity = False
1073 self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = False)
1074 self._TCTRL_factor.SetFocus()
1075 else:
1076 self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = True)
1077
1078 if self._TCTRL_amount.GetValue().strip() == u'':
1079 validity = False
1080 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False)
1081 self._TCTRL_amount.SetFocus()
1082 else:
1083 converted, factor = gmTools.input2decimal(self._TCTRL_amount.GetValue())
1084 if not converted:
1085 validity = False
1086 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False)
1087 self._TCTRL_amount.SetFocus()
1088 else:
1089 self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = True)
1090
1091 if self._TCTRL_count.GetValue().strip() == u'':
1092 validity = False
1093 self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = False)
1094 self._TCTRL_count.SetFocus()
1095 else:
1096 converted, factor = gmTools.input2decimal(self._TCTRL_count.GetValue())
1097 if not converted:
1098 validity = False
1099 self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = False)
1100 self._TCTRL_count.SetFocus()
1101 else:
1102 self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = True)
1103
1104 if self._PRW_date.is_valid_timestamp(allow_empty = True):
1105 self._PRW_date.display_as_valid(True)
1106 else:
1107 validity = False
1108 self._PRW_date.display_as_valid(False)
1109 self._PRW_date.SetFocus()
1110
1111 if self._PRW_encounter.GetData() is None:
1112 validity = False
1113 self._PRW_encounter.display_as_valid(False)
1114 self._PRW_encounter.SetFocus()
1115 else:
1116 self._PRW_encounter.display_as_valid(True)
1117
1118 if self._PRW_billable.GetData() is None:
1119 validity = False
1120 self._PRW_billable.display_as_valid(False)
1121 self._PRW_billable.SetFocus()
1122 else:
1123 self._PRW_billable.display_as_valid(True)
1124
1125 return validity
1126 #----------------------------------------------------------------
1128 data = gmBilling.create_bill_item (
1129 pk_encounter = gmPerson.gmCurrentPatient().emr.active_encounter['pk_encounter'],
1130 pk_billable = self._PRW_billable.GetData(),
1131 pk_staff = gmStaff.gmCurrentProvider()['pk_staff'] # should be settable !
1132 )
1133 data['raw_date_to_bill'] = self._PRW_date.GetData()
1134 converted, data['unit_count'] = gmTools.input2decimal(self._TCTRL_count.GetValue())
1135 converted, data['net_amount_per_unit'] = gmTools.input2decimal(self._TCTRL_amount.GetValue())
1136 converted, data['amount_multiplier'] = gmTools.input2decimal(self._TCTRL_factor.GetValue())
1137 data['item_detail'] = self._TCTRL_comment.GetValue().strip()
1138 data.save()
1139
1140 self.data = data
1141 return True
1142 #----------------------------------------------------------------
1144 self.data['pk_encounter_to_bill'] = self._PRW_encounter.GetData()
1145 self.data['raw_date_to_bill'] = self._PRW_date.GetData()
1146 converted, self.data['unit_count'] = gmTools.input2decimal(self._TCTRL_count.GetValue())
1147 converted, self.data['net_amount_per_unit'] = gmTools.input2decimal(self._TCTRL_amount.GetValue())
1148 converted, self.data['amount_multiplier'] = gmTools.input2decimal(self._TCTRL_factor.GetValue())
1149 self.data['item_detail'] = self._TCTRL_comment.GetValue().strip()
1150 return self.data.save()
1151 #----------------------------------------------------------------
1153 self._PRW_billable.SetText()
1154 self._PRW_encounter.set_from_instance(gmPerson.gmCurrentPatient().emr.active_encounter)
1155 self._PRW_date.SetData()
1156 self._TCTRL_count.SetValue(u'1')
1157 self._TCTRL_amount.SetValue(u'')
1158 self._LBL_currency.SetLabel(gmTools.u_euro)
1159 self._TCTRL_factor.SetValue(u'1')
1160 self._TCTRL_comment.SetValue(u'')
1161
1162 self._PRW_billable.Enable()
1163 self._PRW_billable.SetFocus()
1164 #----------------------------------------------------------------
1166 self._PRW_billable.SetText()
1167 self._TCTRL_count.SetValue(u'1')
1168 self._TCTRL_amount.SetValue(u'')
1169 self._TCTRL_comment.SetValue(u'')
1170
1171 self._PRW_billable.Enable()
1172 self._PRW_billable.SetFocus()
1173 #----------------------------------------------------------------
1175 self._PRW_billable.set_from_pk(self.data['pk_billable'])
1176 self._PRW_encounter.set_from_instance(gmPerson.gmCurrentPatient().emr.active_encounter)
1177 self._PRW_date.SetData(data = self.data['raw_date_to_bill'])
1178 self._TCTRL_count.SetValue(u'%s' % self.data['unit_count'])
1179 self._TCTRL_amount.SetValue(u'%s' % self.data['net_amount_per_unit'])
1180 self._LBL_currency.SetLabel(self.data['currency'])
1181 self._TCTRL_factor.SetValue(u'%s' % self.data['amount_multiplier'])
1182 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['item_detail'], u''))
1183
1184 self._PRW_billable.Disable()
1185 self._PRW_date.SetFocus()
1186 #----------------------------------------------------------------
1194
1195 #============================================================
1196 # a plugin for billing
1197 #------------------------------------------------------------
1198 from Gnumed.wxGladeWidgets import wxgBillingPluginPnl
1199
1200 -class cBillingPluginPnl(wxgBillingPluginPnl.wxgBillingPluginPnl, gmRegetMixin.cRegetOnPaintMixin):
1202
1203 wxgBillingPluginPnl.wxgBillingPluginPnl.__init__(self, *args, **kwargs)
1204 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
1205 self.__register_interests()
1206 #-----------------------------------------------------
1208 self._PNL_bill_items.identity = None
1209 self._CHBOX_show_non_invoiced_only.SetValue(1)
1210 self._PRW_billable.SetText(u'', None)
1211 self._TCTRL_factor.SetValue(u'1.0')
1212 self._TCTRL_factor.Disable()
1213 self._TCTRL_details.SetValue(u'')
1214 self._TCTRL_details.Disable()
1215 #-----------------------------------------------------
1216 # event handling
1217 #-----------------------------------------------------
1219 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
1220 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
1221
1222 gmDispatcher.connect(signal = u'bill_item_mod_db', receiver = self._on_bill_item_modified)
1223
1224 self._PRW_billable.add_callback_on_selection(self._on_billable_selected_in_prw)
1225 #-----------------------------------------------------
1228 #-----------------------------------------------------
1231 #-----------------------------------------------------
1234 #-----------------------------------------------------
1237 #--------------------------------------------------------
1262 #--------------------------------------------------------
1264 if billable is None:
1265 self._TCTRL_factor.Disable()
1266 self._TCTRL_details.Disable()
1267 self._BTN_insert_item.Disable()
1268 else:
1269 self._TCTRL_factor.Enable()
1270 self._TCTRL_details.Enable()
1271 self._BTN_insert_item.Enable()
1272 #-----------------------------------------------------
1273 # reget-on-paint mixin API
1274 #-----------------------------------------------------
1278 #============================================================
1279 # main
1280 #------------------------------------------------------------
1281 if __name__ == '__main__':
1282
1283 if len(sys.argv) < 2:
1284 sys.exit()
1285
1286 if sys.argv[1] != 'test':
1287 sys.exit()
1288
1289 from Gnumed.pycommon import gmI18N
1290 gmI18N.activate_locale()
1291 gmI18N.install_domain(domain = 'gnumed')
1292
1293 #----------------------------------------
1294 app = wx.PyWidgetTester(size = (600, 600))
1295 #app.SetWidget(cATCPhraseWheel, -1)
1296 #app.SetWidget(cSubstancePhraseWheel, -1)
1297 app.MainLoop()
1298
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Wed Jun 13 03:59:10 2012 | http://epydoc.sourceforge.net |