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