Gnumed.wxpython.gmBillingWidgets

Package Gnumed :: Package wxpython :: Module gmBillingWidgets |
[frames] | no frames]

# Source Code for Module Gnumed.wxpython.gmBillingWidgets

       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  #================================================================
      50 -def edit_billable(parent=None, billable=None):
      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  #----------------------------------------------------------------
      68 -def manage_billables(parent=None):
      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  #----------------------------------------------------------------
     142 -class cBillablePhraseWheel(gmPhraseWheel.cPhraseWheel):
     143
     144 -        def __init__(self, *args, **kwargs):
     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          #------------------------------------------------------------
     170 -        def _data2instance(self):
     171                  return gmBilling.cBillable(aPK_obj = self._data.values()[0]['data'])
     172          #------------------------------------------------------------
     173 -        def _get_data_tooltip(self):
     174                  if self.GetData() is None:
     175                          return None
     176                  billable = gmBilling.cBillable(aPK_obj = self._data.values()[0]['data'])
     177                  return billable.format()
     178          #------------------------------------------------------------
     179 -        def set_from_instance(self, instance):
     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          #------------------------------------------------------------
     187 -        def set_from_pk(self, pk):
     188                  self.set_from_instance(gmBilling.cBillable(aPK_obj = pk))
     189
     190  #----------------------------------------------------------------
     191  from Gnumed.wxGladeWidgets import wxgBillableEAPnl
     192
     193 -class cBillableEAPnl(wxgBillableEAPnl.wxgBillableEAPnl, gmEditArea.cGenericEditAreaMixin):
     194
     195 -        def __init__(self, *args, **kwargs):
     196
     197                  try:
     198                          data = kwargs['billable']
     199                          del kwargs['billable']
     200                  except KeyError:
     201                          data = None
     202
     203                  wxgBillableEAPnl.wxgBillableEAPnl.__init__(self, *args, **kwargs)
     204                  gmEditArea.cGenericEditAreaMixin.__init__(self)
     205
     206                  # Code using this mixin should set mode and data
     207                  # after instantiating the class:
     208                  self.mode = 'new'
     209                  self.data = data
     210                  if data is not None:
     211                          self.mode = 'edit'
     212
     213                  #self.__init_ui()
     214          #----------------------------------------------------------------
     215  #       def __init_ui(self):
     216  #               # adjust phrasewheels etc
     217          #----------------------------------------------------------------
     218          # generic Edit Area mixin API
     219          #----------------------------------------------------------------
     220 -        def _valid_for_save(self):
     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          #----------------------------------------------------------------
     281 -        def _save_as_new(self):
     282                  data = gmBilling.create_billable (
     283                          code = self._TCTRL_code.GetValue().strip(),
     284                          term = self._TCTRL_description.GetValue().strip(),
     285                          data_source = self._PRW_coding_system.GetData(),
     286                          return_existing = False
     287                  )
     288                  if data is None:
     289                          self.status_message = _('Billable already exists.')
     290                          return False
     291
     292                  val = self._TCTRL_amount.GetValue().strip()
     293                  if val != u'':
     294                          tmp, val = gmTools.input2decimal(val)
     295                          data['raw_amount'] = val
     296                  val = self._TCTRL_currency.GetValue().strip()
     297                  if val != u'':
     298                          data['currency'] = val
     299                  vat = self._TCTRL_vat.GetValue().strip()
     300                  if vat != u'':
     301                          tmp, vat = gmTools.input2decimal(vat)
     302                          data['vat_multiplier'] = vat / 100
     303                  data['comment'] = self._TCTRL_comment.GetValue().strip()
     304                  data['active'] = self._CHBOX_active.GetValue()
     305
     306                  data.save()
     307
     308                  self.data = data
     309
     310                  return True
     311          #----------------------------------------------------------------
     312 -        def _save_as_update(self):
     313                  self.data['billable_description'] = self._TCTRL_description.GetValue().strip()
     314                  tmp, self.data['raw_amount'] = gmTools.input2decimal(self._TCTRL_amount.GetValue())
     315                  self.data['currency'] = self._TCTRL_currency.GetValue().strip()
     316                  vat = self._TCTRL_vat.GetValue().strip()
     317                  if vat == u'':
     318                          vat = 0
     319                  else:
     320                          tmp, vat = gmTools.input2decimal(vat)
     321                  self.data['vat_multiplier'] = vat / 100
     322                  self.data['comment'] = self._TCTRL_comment.GetValue().strip()
     323                  self.data['active'] = self._CHBOX_active.GetValue()
     324                  self.data.save()
     325                  return True
     326          #----------------------------------------------------------------
     327 -        def _refresh_as_new(self):
     328                  self._TCTRL_code.SetValue(u'')
     329                  self._PRW_coding_system.SetText(u'', None)
     330                  self._TCTRL_description.SetValue(u'')
     331                  self._TCTRL_amount.SetValue(u'')
     332                  self._TCTRL_currency.SetValue(u'')
     333                  self._TCTRL_vat.SetValue(u'')
     334                  self._TCTRL_comment.SetValue(u'')
     335                  self._CHBOX_active.SetValue(True)
     336
     337                  self._TCTRL_code.SetFocus()
     338          #----------------------------------------------------------------
     339 -        def _refresh_as_new_from_existing(self):
     340                  self._refresh_as_new()
     341          #----------------------------------------------------------------
     342 -        def _refresh_from_existing(self):
     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  # invoice related widgets
     359  #----------------------------------------------------------------
     360 -def configure_invoice_template(parent=None, with_vat=True):
     361
     362          if parent is None:
     363                  parent = wx.GetApp().GetTopWindow()
     364
     365          template = gmFormWidgets.manage_form_templates (
     366                  parent = parent,
     367                  template_types = ['invoice']
     368          )
     369
     370          if template is None:
     371                  gmDispatcher.send(signal = 'statustext', msg = _('No invoice template configured.'), beep = True)
     372                  return None
     373
     374          if template['engine'] not in [u'L', u'X']:
     375                  gmDispatcher.send(signal = 'statustext', msg = _('No invoice template configured.'), beep = True)
     376                  return None
     377
     378          if with_vat:
     379                  option = u'form_templates.invoice_with_vat'
     380          else:
     381                  option = u'form_templates.invoice_no_vat'
     382
     383          dbcfg = gmCfg.cCfgSQL()
     384          dbcfg.set (
     385                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     386                  option = option,
     387                  value = u'%s - %s' % (template['name_long'], template['external_version'])
     388          )
     389
     390          return template
     391  #----------------------------------------------------------------
     392 -def get_invoice_template(parent=None, with_vat=True):
     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  # per-patient bill related widgets
     433  #----------------------------------------------------------------
     434 -def edit_bill(parent=None, bill=None, single_entry=False):
     435
     436          if bill is None:
     437                  # manually creating bills is not yet supported
     438                  return
     439
     440          ea = cBillEAPnl(parent = parent, id = -1)
     441          ea.data = bill
     442          ea.mode = gmTools.coalesce(bill, 'new', 'edit')
     443          dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = single_entry)
     444          dlg.SetTitle(gmTools.coalesce(bill, _('Adding new bill'), _('Editing bill')))
     445          if dlg.ShowModal() == wx.ID_OK:
     446                  dlg.Destroy()
     447                  return True
     448          dlg.Destroy()
     449          return False
     450  #----------------------------------------------------------------
     451 -def create_bill_from_items(bill_items=None):
     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          # check item consistency
     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          # create bill
     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  #----------------------------------------------------------------
     500 -def create_invoice_from_bill(parent = None, bill=None, print_it=False, keep_a_copy=True):
     501
     502          bill_patient_not_active = False
     503          # do we have a current patient ?
     504          curr_pat = gmPerson.gmCurrentPatient()
     505          if curr_pat.connected:
     506                  # is the bill about the current patient, too ?
     507                  # (because that's what the new invoice would get
     508                  #  created for and attached to)
     509                  if curr_pat.ID != bill['pk_patient']:
     510                          bill_patient_not_active = True
     511          else:
     512                  bill_patient_not_active = True
     513
     514          # FIXME: could ask whether to set fk_receiver_identity
     515          # FIXME: but this would need enabling the bill EA to edit same
     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                  # cannot invoice open bills
     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                  # cannot create invoice if no receiver address
     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          # find template
     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          # process template
     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          #ph.debug = True
     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          # keep a copy
     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          # print template
     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  #----------------------------------------------------------------
     633 -def delete_bill(parent=None, bill=None):
     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  #----------------------------------------------------------------
     682 -def remove_items_from_bill(parent=None, bill=None):
     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          # remember this because unlinking/deleting the items
     760          # will remove the patient PK from the bill
     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          # delete bill, too, if empty
     777          if len(bill.bill_items) == 0:
     778                  gmBilling.delete_bill(pk_bill = bill['pk_bill'])
     779
     780          return True
     781  #----------------------------------------------------------------
     782 -def manage_bills(parent=None, patient=None):
     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                  # find invoice
     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                  # create it ?
     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                  # prepare invoicing
     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
     900 -        def __init__(self, *args, **kwargs):
     901
     902                  try:
     903                          data = kwargs['bill']
     904                          del kwargs['bill']
     905                  except KeyError:
     906                          data = None
     907
     908                  wxgBillEAPnl.wxgBillEAPnl.__init__(self, *args, **kwargs)
     909                  gmEditArea.cGenericEditAreaMixin.__init__(self)
     910
     911                  self.mode = 'new'
     912                  self.data = data
     913                  if data is not None:
     914                          self.mode = 'edit'
     915
     916  #               self.__init_ui()
     917          #----------------------------------------------------------------
     918  #       def __init_ui(self):
     919          #----------------------------------------------------------------
     920          # generic Edit Area mixin API
     921          #----------------------------------------------------------------
     922 -        def _valid_for_save(self):
     923                  validity = True
     924
     925                  # flag but do not count as wrong
     926                  if not self._PRW_close_date.is_valid_timestamp(allow_empty = False):
     927                          self._PRW_close_date.SetFocus()
     928
     929                  return validity
     930          #----------------------------------------------------------------
     931 -        def _save_as_new(self):
     932                  # not intended to be used
     933                  return False
     934          #----------------------------------------------------------------
     935 -        def _save_as_update(self):
     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          #----------------------------------------------------------------
     942 -        def _refresh_as_new(self):
     943                  pass # not used
     944          #----------------------------------------------------------------
     945 -        def _refresh_as_new_from_existing(self):
     946                  self._refresh_as_new()
     947          #----------------------------------------------------------------
     948 -        def _refresh_from_existing(self):
     949                  self._TCTRL_invoice_id.SetValue(self.data['invoice_id'])
     950                  self._PRW_close_date.SetText(data = self.data['close_date'])
     951
     952                  self.data.set_missing_address_from_default()
     953                  if self.data['pk_receiver_address'] is None:
     954                          self._TCTRL_address.SetValue(u'')
     955                  else:
     956                          adr = self.data.address
     957                          self._TCTRL_address.SetValue(adr.format(single_line = True, show_type = False))
     958
     959                  self._TCTRL_value.SetValue(u'%(currency)s%(total_amount)s' % self.data)
     960                  self._CHBOX_vat_applies.SetValue(self.data['apply_vat'])
     961                  self._CHBOX_vat_applies.SetLabel(_('&VAT applies (%s%%)') % self.data['percent_vat'])
     962                  if self.data['apply_vat']:
     963                          tmp = u'%s %%(currency)s%%(total_vat)s %s %s %%(currency)s%%(total_amount_with_vat)s' % (
     964                                  gmTools.u_corresponds_to,
     965                                  gmTools.u_right_arrow,
     966                                  gmTools.u_sum,
     967                          )
     968                          self._TCTRL_value_with_vat.SetValue(tmp % self.data)
     969                  else:
     970                          self._TCTRL_value_with_vat.SetValue(u'')
     971
     972                  self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], u''))
     973
     974                  self._PRW_close_date.SetFocus()
     975          #----------------------------------------------------------------
     976          # event handling
     977          #----------------------------------------------------------------
     978 -        def _on_vat_applies_box_checked(self, event):
     979                  if self._CHBOX_vat_applies.GetValue():
     980                          tmp = u'%s %%(currency)s%%(total_vat)s %s %s %%(currency)s%%(total_amount_with_vat)s' % (
     981                                  gmTools.u_corresponds_to,
     982                                  gmTools.u_right_arrow,
     983                                  gmTools.u_sum,
     984                          )
     985                          self._TCTRL_value_with_vat.SetValue(tmp % self.data)
     986                          return
     987                  self._TCTRL_value_with_vat.SetValue(u'')
     988          #----------------------------------------------------------------
     989 -        def _on_select_address_button_pressed(self, event):
     990                  adr = gmPersonContactWidgets.select_address (
     991                          missing = _('billing'),
     992                          person = gmPerson.cIdentity(aPK_obj = self.data['pk_patient'])
     993                  )
     994                  if adr is None:
     995                          gmGuiHelpers.gm_show_info (
     996                                  aTitle = _('Selecting address'),
     997                                  aMessage = _('GNUmed does not know any addresses for this patient.')
     998                          )
     999                          return
    1000                  self.data['pk_receiver_address'] = adr['pk_lnk_person_org_address']
    1001                  self.data.save()
    1002                  self._TCTRL_address.SetValue(adr.format(single_line = True, show_type = False))
    1003
    1004  #================================================================
    1005  # per-patient bill items related widgets
    1006  #----------------------------------------------------------------
    1007 -def edit_bill_item(parent=None, bill_item=None, single_entry=False):
    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  #----------------------------------------------------------------
    1025 -def manage_bill_items(parent=None, pk_patient=None):
    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                  #msg = msg,
    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  #------------------------------------------------------------
    1084 -class cPersonBillItemsManagerPnl(gmListWidgets.cGenericListManagerPnl):
    1085          """A list for managing a patient's bill items.
    1086
    1087          Does NOT act on/listen to the current patient.
    1088          """
    1089 -        def __init__(self, *args, **kwargs):
    1090
    1091                  try:
    1092                          self.__identity = kwargs['identity']
    1093                          del kwargs['identity']
    1094                  except KeyError:
    1095                          self.__identity = None
    1096
    1097                  gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs)
    1098
    1099                  self.new_callback = self._add_item
    1100                  self.edit_callback = self._edit_item
    1101                  self.delete_callback = self._del_item
    1102                  self.refresh_callback = self.refresh
    1103
    1104                  self.__show_non_invoiced_only = True
    1105
    1106                  self.__init_ui()
    1107                  self.refresh()
    1108          #--------------------------------------------------------
    1109          # external API
    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          # internal helpers
    1145          #--------------------------------------------------------
    1146 -        def __init_ui(self):
    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  #               self.left_extra_button = (
    1162  #                       _('Select pending'),
    1163  #                       _('Select non-invoiced (pending) items.'),
    1164  #                       self._select_pending_items
    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          #--------------------------------------------------------
    1182 -        def _add_item(self):
    1183                  return edit_bill_item(parent = self, bill_item = None, single_entry = False)
    1184          #--------------------------------------------------------
    1185 -        def _edit_item(self, bill_item):
    1186                  return edit_bill_item(parent = self, bill_item = bill_item, single_entry = True)
    1187          #--------------------------------------------------------
    1188 -        def _del_item(self, item):
    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          #--------------------------------------------------------
    1202 -        def _get_item_tooltip(self, item):
    1203                  if item is None:
    1204                          return None
    1205                  return item.format()
    1206          #--------------------------------------------------------
    1207 -        def _select_pending_items(self, item):
    1208                  pass
    1209          #--------------------------------------------------------
    1210 -        def _invoice_selected_items(self, item):
    1211                  bill_items = self._LCTRL_items.get_selected_item_data()
    1212                  bill = create_bill_from_items(bill_items)
    1213                  if bill is None:
    1214                          return
    1215                  if bill['pk_receiver_address'] is None:
    1216                          gmGuiHelpers.gm_show_error (
    1217                                  aMessage = _(
    1218                                          'Cannot create invoice.\n'
    1219                                          '\n'
    1220                                          'No receiver address selected.'
    1221                                  ),
    1222                                  aTitle = _('Creating invoice')
    1223                          )
    1224                          return
    1225                  if bill['close_date'] is None:
    1226                          bill['close_date'] = gmDateTime.pydt_now_here()
    1227                          bill.save()
    1228                  create_invoice_from_bill(parent = self, bill = bill, print_it = True, keep_a_copy = True)
    1229          #--------------------------------------------------------
    1230 -        def _browse_billables(self, item):
    1231                  manage_billables(parent = self)
    1232                  return False
    1233          #--------------------------------------------------------
    1234 -        def _browse_bills(self, item):
    1235                  manage_bills(parent = self, patient = self.__identity)
    1236          #--------------------------------------------------------
    1237          # properties
    1238          #--------------------------------------------------------
    1239 -        def _get_identity(self):
    1240                  return self.__identity
    1241
    1242 -        def _set_identity(self, identity):
    1243                  self.__identity = identity
    1244                  self.refresh()
    1245
    1246          identity = property(_get_identity, _set_identity)
    1247          #--------------------------------------------------------
    1248 -        def _get_show_non_invoiced_only(self):
    1249                  return self.__show_non_invoiced_only
    1250
    1251 -        def _set_show_non_invoiced_only(self, value):
    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
    1262 -        def __init__(self, *args, **kwargs):
    1263
    1264                  try:
    1265                          data = kwargs['bill_item']
    1266                          del kwargs['bill_item']
    1267                  except KeyError:
    1268                          data = None
    1269
    1270                  wxgBillItemEAPnl.wxgBillItemEAPnl.__init__(self, *args, **kwargs)
    1271                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    1272
    1273                  self.mode = 'new'
    1274                  self.data = data
    1275                  if data is not None:
    1276                          self.mode = 'edit'
    1277
    1278                  self.__init_ui()
    1279          #----------------------------------------------------------------
    1280 -        def __init_ui(self):
    1281                  self._PRW_encounter.set_context(context = 'patient', val = gmPerson.gmCurrentPatient().ID)
    1282                  self._PRW_billable.add_callback_on_selection(self._on_billable_selected)
    1283          #----------------------------------------------------------------
    1284          # generic Edit Area mixin API
    1285          #----------------------------------------------------------------
    1286 -        def _valid_for_save(self):
    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          #----------------------------------------------------------------
    1352 -        def _save_as_new(self):
    1353                  data = gmBilling.create_bill_item (
    1354                          pk_encounter = self._PRW_encounter.GetData(),
    1355                          pk_billable = self._PRW_billable.GetData(),
    1356                          pk_staff = gmStaff.gmCurrentProvider()['pk_staff']              # should be settable !
    1357                  )
    1358                  data['raw_date_to_bill'] = self._PRW_date.GetData()
    1359                  converted, data['unit_count'] = gmTools.input2decimal(self._TCTRL_count.GetValue())
    1360                  converted, data['net_amount_per_unit'] = gmTools.input2decimal(self._TCTRL_amount.GetValue())
    1361                  converted, data['amount_multiplier'] = gmTools.input2decimal(self._TCTRL_factor.GetValue())
    1362                  data['item_detail'] = self._TCTRL_comment.GetValue().strip()
    1363                  data.save()
    1364
    1365                  self.data = data
    1366                  return True
    1367          #----------------------------------------------------------------
    1368 -        def _save_as_update(self):
    1369                  self.data['pk_encounter_to_bill'] = self._PRW_encounter.GetData()
    1370                  self.data['raw_date_to_bill'] = self._PRW_date.GetData()
    1371                  converted, self.data['unit_count'] = gmTools.input2decimal(self._TCTRL_count.GetValue())
    1372                  converted, self.data['net_amount_per_unit'] = gmTools.input2decimal(self._TCTRL_amount.GetValue())
    1373                  converted, self.data['amount_multiplier'] = gmTools.input2decimal(self._TCTRL_factor.GetValue())
    1374                  self.data['item_detail'] = self._TCTRL_comment.GetValue().strip()
    1375                  return self.data.save()
    1376          #----------------------------------------------------------------
    1377 -        def _refresh_as_new(self):
    1378                  self._PRW_billable.SetText()
    1379                  self._PRW_encounter.set_from_instance(gmPerson.gmCurrentPatient().emr.active_encounter)
    1380                  self._PRW_date.SetData()
    1381                  self._TCTRL_count.SetValue(u'1')
    1382                  self._TCTRL_amount.SetValue(u'')
    1383                  self._LBL_currency.SetLabel(gmTools.u_euro)
    1384                  self._TCTRL_factor.SetValue(u'1')
    1385                  self._TCTRL_comment.SetValue(u'')
    1386
    1387                  self._PRW_billable.Enable()
    1388                  self._PRW_billable.SetFocus()
    1389          #----------------------------------------------------------------
    1390 -        def _refresh_as_new_from_existing(self):
    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          #----------------------------------------------------------------
    1399 -        def _refresh_from_existing(self):
    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          #----------------------------------------------------------------
    1412 -        def _on_billable_selected(self, item):
    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  # a plugin for billing
    1422  #------------------------------------------------------------
    1423  from Gnumed.wxGladeWidgets import wxgBillingPluginPnl
    1424
    1425 -class cBillingPluginPnl(wxgBillingPluginPnl.wxgBillingPluginPnl, gmRegetMixin.cRegetOnPaintMixin):
    1426 -        def __init__(self, *args, **kwargs):
    1427
    1428                  wxgBillingPluginPnl.wxgBillingPluginPnl.__init__(self, *args, **kwargs)
    1429                  gmRegetMixin.cRegetOnPaintMixin.__init__(self)
    1430                  self.__register_interests()
    1431          #-----------------------------------------------------
    1432 -        def __reset_ui(self):
    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          # event handling
    1442          #-----------------------------------------------------
    1443 -        def __register_interests(self):
    1444                  gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
    1445                  gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
    1446
    1447                  gmDispatcher.connect(signal = u'bill.bill_item_mod_db', receiver = self._on_bill_item_modified)
    1448
    1449                  self._PRW_billable.add_callback_on_selection(self._on_billable_selected_in_prw)
    1450          #-----------------------------------------------------
    1451 -        def _on_pre_patient_selection(self):
    1452                  wx.CallAfter(self.__reset_ui)
    1453          #-----------------------------------------------------
    1454 -        def _on_post_patient_selection(self):
    1455                  wx.CallAfter(self._schedule_data_reget)
    1456          #-----------------------------------------------------
    1457 -        def _on_bill_item_modified(self):
    1458                  wx.CallAfter(self._schedule_data_reget)
    1459          #-----------------------------------------------------
    1460 -        def _on_non_invoiced_only_checkbox_toggled(self, event):
    1461                  self._PNL_bill_items.show_non_invoiced_only = self._CHBOX_show_non_invoiced_only.GetValue()
    1462          #--------------------------------------------------------
    1463 -        def _on_insert_bill_item_button_pressed(self, event):
    1464                  val = self._TCTRL_factor.GetValue().strip()
    1465                  if val == u'':
    1466                          factor = 1.0
    1467                  else:
    1468                          converted, factor = gmTools.input2decimal(val)
    1469                          if not converted:
    1470                                  gmGuiHelpers.gm_show_warning (
    1471                                          _('"Factor" must be a number\n\nCannot insert bill item.'),
    1472                                          _('Inserting bill item')
    1473                                  )
    1474                                  return False
    1475                  bill_item = gmBilling.create_bill_item (
    1476                          pk_encounter = gmPerson.gmCurrentPatient().emr.active_encounter['pk_encounter'],
    1477                          pk_billable = self._PRW_billable.GetData(),
    1478                          pk_staff = gmStaff.gmCurrentProvider()['pk_staff']
    1479                  )
    1480                  bill_item['amount_multiplier'] = factor
    1481                  bill_item['item_detail'] = self._TCTRL_details.GetValue()
    1482                  bill_item.save()
    1483
    1484                  self._TCTRL_details.SetValue(u'')
    1485
    1486                  return True
    1487          #--------------------------------------------------------
    1488 -        def _on_billable_selected_in_prw(self, billable):
    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          # reget-on-paint mixin API
    1499          #-----------------------------------------------------
    1500 -        def _populate_with_data(self):
    1501                  self._PNL_bill_items.identity = gmPerson.gmCurrentPatient()
    1502                  return True
    1503  #============================================================
    1504  # main
    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          #app.SetWidget(cATCPhraseWheel, -1)
    1521          #app.SetWidget(cSubstancePhraseWheel, -1)
    1522          app.MainLoop()
    1523