Gnumed.wxpython.gmBillingWidgets

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

# Source Code for Module Gnumed.wxpython.gmBillingWidgets

       1  # -*- coding: utf-8 -*-
       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 gmCfg2
      24  from Gnumed.pycommon import gmPrinting
      25  from Gnumed.pycommon import gmNetworkTools
      26
      27  from Gnumed.business import gmBilling
      28  from Gnumed.business import gmPerson
      29  from Gnumed.business import gmStaff
      30  from Gnumed.business import gmDocuments
      31  from Gnumed.business import gmPraxis
      32  from Gnumed.business import gmForms
      33  from Gnumed.business import gmDemographicRecord
      34
      35  from Gnumed.wxpython import gmListWidgets
      36  from Gnumed.wxpython import gmRegetMixin
      37  from Gnumed.wxpython import gmPhraseWheel
      38  from Gnumed.wxpython import gmGuiHelpers
      39  from Gnumed.wxpython import gmEditArea
      40  from Gnumed.wxpython import gmPersonContactWidgets
      41  from Gnumed.wxpython import gmPatSearchWidgets
      42  from Gnumed.wxpython import gmMacro
      43  from Gnumed.wxpython import gmFormWidgets
      44  from Gnumed.wxpython import gmDocumentWidgets
      45  from Gnumed.wxpython import gmDataPackWidgets
      46
      47
      48  _log = logging.getLogger('gm.ui')
      49
      50  #================================================================
      51 -def edit_billable(parent=None, billable=None):
      52          ea = cBillableEAPnl(parent, -1)
      53          ea.data = billable
      54          ea.mode = gmTools.coalesce(billable, 'new', 'edit')
      55          dlg = gmEditArea.cGenericEditAreaDlg2 (
      56                  parent = parent,
      57                  id = -1,
      58                  edit_area = ea,
      59                  single_entry = gmTools.bool2subst((billable is None), False, True)
      60          )
      61          dlg.SetTitle(gmTools.coalesce(billable, _('Adding new billable'), _('Editing billable')))
      62          if dlg.ShowModal() == wx.ID_OK:
      63                  dlg.DestroyLater()
      64                  return True
      65          dlg.DestroyLater()
      66          return False
      67
      68  #----------------------------------------------------------------
      69 -def manage_billables(parent=None):
      70
      71          if parent is None:
      72                  parent = wx.GetApp().GetTopWindow()
      73
      74          #------------------------------------------------------------
      75          def edit(billable=None):
      76                  return edit_billable(parent = parent, billable = billable)
      77          #------------------------------------------------------------
      78          def delete(billable):
      79                  if billable.is_in_use:
      80                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete this billable item. It is in use.'), beep = True)
      81                          return False
      82                  return gmBilling.delete_billable(pk_billable = billable['pk_billable'])
      83          #------------------------------------------------------------
      84          def get_tooltip(item):
      85                  if item is None:
      86                          return None
      87                  return item.format()
      88          #------------------------------------------------------------
      89          def refresh(lctrl):
      90                  billables = gmBilling.get_billables()
      91                  items = [ [
      92                          b['billable_code'],
      93                          b['billable_description'],
      94                          '%(currency)s%(raw_amount)s' % b,
      95                          '%s (%s)' % (b['catalog_short'], b['catalog_version']),
      96                          gmTools.coalesce(b['comment'], ''),
      97                          b['pk_billable']
      98                  ] for b in billables ]
      99                  lctrl.set_string_items(items)
     100                  lctrl.set_data(billables)
     101          #------------------------------------------------------------
     102          def manage_data_packs(billable):
     103                  gmDataPackWidgets.manage_data_packs(parent = parent)
     104                  return True
     105          #------------------------------------------------------------
     106          def browse_catalogs(billable):
     107                  dbcfg = gmCfg.cCfgSQL()
     108                  url = dbcfg.get2 (
     109                          option = 'external.urls.schedules_of_fees',
     110                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     111                          bias = 'user',
     112                          default = 'http://www.e-bis.de/goae/defaultFrame.htm'
     113                  )
     114                  gmNetworkTools.open_url_in_browser(url = url)
     115                  return False
     116          #------------------------------------------------------------
     117          msg = _('\nThese are the items for billing registered with GNUmed.\n')
     118
     119          gmListWidgets.get_choices_from_list (
     120                  parent = parent,
     121                  msg = msg,
     122                  caption = _('Showing billable items.'),
     123                  columns = [_('Code'), _('Description'), _('Value'), _('Catalog'), _('Comment'), '#'],
     124                  single_selection = True,
     125                  new_callback = edit,
     126                  edit_callback = edit,
     127                  delete_callback = delete,
     128                  refresh_callback = refresh,
     129                  middle_extra_button = (
     130                          _('Data packs'),
     131                          _('Browse and install billing catalog (schedule of fees) data packs'),
     132                          manage_data_packs
     133                  ),
     134                  right_extra_button = (
     135                          _('Catalogs (WWW)'),
     136                          _('Browse billing catalogs (schedules of fees) on the web'),
     137                          browse_catalogs
     138                  ),
     139                  list_tooltip_callback = get_tooltip
     140          )
     141
     142  #----------------------------------------------------------------
     143 -class cBillablePhraseWheel(gmPhraseWheel.cPhraseWheel):
     144
     145 -        def __init__(self, *args, **kwargs):
     146                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
     147                  query = """
     148                          SELECT -- DISTINCT ON (label)
     149                                  r_vb.pk_billable
     150                                          AS data,
     151                                  r_vb.billable_code || ': ' || r_vb.billable_description || ' (' || r_vb.catalog_short || ' - ' || r_vb.catalog_version || ')'
     152                                          AS list_label,
     153                                  r_vb.billable_code || ' (' || r_vb.catalog_short || ' - ' || r_vb.catalog_version || ')'
     154                                          AS field_label
     155                          FROM
     156                                  ref.v_billables r_vb
     157                          WHERE
     158                                  r_vb.active
     159                                          AND (
     160                                                  r_vb.billable_code %(fragment_condition)s
     161                                                          OR
     162                                                  r_vb.billable_description %(fragment_condition)s
     163                                          )
     164                          ORDER BY list_label
     165                          LIMIT 20
     166                  """
     167                  mp = gmMatchProvider.cMatchProvider_SQL2(queries = query)
     168                  mp.setThresholds(1, 2, 4)
     169                  self.matcher = mp
     170          #------------------------------------------------------------
     171 -        def _data2instance(self):
     172                  return gmBilling.cBillable(aPK_obj = list(self._data.values())[0]['data'])
     173          #------------------------------------------------------------
     174 -        def _get_data_tooltip(self):
     175                  if self.GetData() is None:
     176                          return None
     177                  billable = gmBilling.cBillable(aPK_obj = list(self._data.values())[0]['data'])
     178                  return billable.format()
     179          #------------------------------------------------------------
     180 -        def set_from_instance(self, instance):
     181                  val = '%s (%s - %s)' % (
     182                          instance['billable_code'],
     183                          instance['catalog_short'],
     184                          instance['catalog_version']
     185                  )
     186                  self.SetText(value = val, data = instance['pk_billable'])
     187          #------------------------------------------------------------
     188 -        def set_from_pk(self, pk):
     189                  self.set_from_instance(gmBilling.cBillable(aPK_obj = pk))
     190
     191  #----------------------------------------------------------------
     192  from Gnumed.wxGladeWidgets import wxgBillableEAPnl
     193
     194 -class cBillableEAPnl(wxgBillableEAPnl.wxgBillableEAPnl, gmEditArea.cGenericEditAreaMixin):
     195
     196 -        def __init__(self, *args, **kwargs):
     197
     198                  try:
     199                          data = kwargs['billable']
     200                          del kwargs['billable']
     201                  except KeyError:
     202                          data = None
     203
     204                  wxgBillableEAPnl.wxgBillableEAPnl.__init__(self, *args, **kwargs)
     205                  gmEditArea.cGenericEditAreaMixin.__init__(self)
     206
     207                  self.mode = 'new'
     208                  self.data = data
     209                  if data is not None:
     210                          self.mode = 'edit'
     211
     212                  #self.__init_ui()
     213          #----------------------------------------------------------------
     214  #       def __init_ui(self):
     215  #               # adjust phrasewheels etc
     216          #----------------------------------------------------------------
     217          # generic Edit Area mixin API
     218          #----------------------------------------------------------------
     219 -        def _valid_for_save(self):
     220
     221                  validity = True
     222
     223                  vat = self._TCTRL_vat.GetValue().strip()
     224                  if vat == '':
     225                          self.display_tctrl_as_valid(tctrl = self._TCTRL_vat, valid = True)
     226                  else:
     227                          success, vat = gmTools.input2decimal(initial = vat)
     228                          if success:
     229                                  self.display_tctrl_as_valid(tctrl = self._TCTRL_vat, valid = True)
     230                          else:
     231                                  validity = False
     232                                  self.display_tctrl_as_valid(tctrl = self._TCTRL_vat, valid = False)
     233                                  self.StatusText = _('VAT must be empty or a number.')
     234                                  self._TCTRL_vat.SetFocus()
     235
     236                  currency = self._TCTRL_currency.GetValue().strip()
     237                  if currency == '':
     238                          validity = False
     239                          self.display_tctrl_as_valid(tctrl = self._TCTRL_currency, valid = False)
     240                          self.StatusText = _('Currency is missing.')
     241                          self._TCTRL_currency.SetFocus()
     242                  else:
     243                          self.display_tctrl_as_valid(tctrl = self._TCTRL_currency, valid = True)
     244
     245                  success, val = gmTools.input2decimal(initial = self._TCTRL_amount.GetValue())
     246                  if success:
     247                          self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = True)
     248                  else:
     249                          validity = False
     250                          self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False)
     251                          self.StatusText = _('Value is missing.')
     252                          self._TCTRL_amount.SetFocus()
     253
     254                  if self._TCTRL_description.GetValue().strip() == '':
     255                          validity = False
     256                          self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = False)
     257                          self.StatusText = _('Description is missing.')
     258                          self._TCTRL_description.SetFocus()
     259                  else:
     260                          self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = True)
     261
     262                  if self._PRW_coding_system.GetData() is None:
     263                          validity = False
     264                          self._PRW_coding_system.display_as_valid(False)
     265                          self.StatusText = _('Coding system is missing.')
     266                          self._PRW_coding_system.SetFocus()
     267                  else:
     268                          self._PRW_coding_system.display_as_valid(True)
     269
     270                  if self._TCTRL_code.GetValue().strip() == '':
     271                          validity = False
     272                          self.display_tctrl_as_valid(tctrl = self._TCTRL_code, valid = False)
     273                          self.StatusText = _('Code is missing.')
     274                          self._TCTRL_code.SetFocus()
     275                  else:
     276                          self.display_tctrl_as_valid(tctrl = self._TCTRL_code, valid = True)
     277
     278                  return validity
     279          #----------------------------------------------------------------
     280 -        def _save_as_new(self):
     281                  data = gmBilling.create_billable (
     282                          code = self._TCTRL_code.GetValue().strip(),
     283                          term = self._TCTRL_description.GetValue().strip(),
     284                          data_source = self._PRW_coding_system.GetData(),
     285                          return_existing = False
     286                  )
     287                  if data is None:
     288                          self.StatusText = _('Billable already exists.')
     289                          return False
     290
     291                  val = self._TCTRL_amount.GetValue().strip()
     292                  if val != '':
     293                          tmp, val = gmTools.input2decimal(val)
     294                          data['raw_amount'] = val
     295                  val = self._TCTRL_currency.GetValue().strip()
     296                  if val != '':
     297                          data['currency'] = val
     298                  vat = self._TCTRL_vat.GetValue().strip()
     299                  if vat != '':
     300                          tmp, vat = gmTools.input2decimal(vat)
     301                          data['vat_multiplier'] = vat / 100
     302                  data['comment'] = self._TCTRL_comment.GetValue().strip()
     303                  data['active'] = self._CHBOX_active.GetValue()
     304
     305                  data.save()
     306
     307                  self.data = data
     308
     309                  return True
     310          #----------------------------------------------------------------
     311 -        def _save_as_update(self):
     312                  self.data['billable_description'] = self._TCTRL_description.GetValue().strip()
     313                  tmp, self.data['raw_amount'] = gmTools.input2decimal(self._TCTRL_amount.GetValue())
     314                  self.data['currency'] = self._TCTRL_currency.GetValue().strip()
     315                  vat = self._TCTRL_vat.GetValue().strip()
     316                  if vat == '':
     317                          vat = 0
     318                  else:
     319                          tmp, vat = gmTools.input2decimal(vat)
     320                  self.data['vat_multiplier'] = vat / 100
     321                  self.data['comment'] = self._TCTRL_comment.GetValue().strip()
     322                  self.data['active'] = self._CHBOX_active.GetValue()
     323                  self.data.save()
     324                  return True
     325          #----------------------------------------------------------------
     326 -        def _refresh_as_new(self):
     327                  self._TCTRL_code.SetValue('')
     328                  self._PRW_coding_system.SetText('', None)
     329                  self._TCTRL_description.SetValue('')
     330                  self._TCTRL_amount.SetValue('')
     331                  self._TCTRL_currency.SetValue('')
     332                  self._TCTRL_vat.SetValue('')
     333                  self._TCTRL_comment.SetValue('')
     334                  self._CHBOX_active.SetValue(True)
     335
     336                  self._TCTRL_code.SetFocus()
     337          #----------------------------------------------------------------
     338 -        def _refresh_as_new_from_existing(self):
     339                  self._refresh_as_new()
     340          #----------------------------------------------------------------
     341 -        def _refresh_from_existing(self):
     342                  self._TCTRL_code.SetValue(self.data['billable_code'])
     343                  self._TCTRL_code.Enable(False)
     344                  self._PRW_coding_system.SetText('%s (%s)' % (self.data['catalog_short'], self.data['catalog_version']), self.data['pk_data_source'])
     345                  self._PRW_coding_system.Enable(False)
     346                  self._TCTRL_description.SetValue(self.data['billable_description'])
     347                  self._TCTRL_amount.SetValue('%s' % self.data['raw_amount'])
     348                  self._TCTRL_currency.SetValue(self.data['currency'])
     349                  self._TCTRL_vat.SetValue('%s' % (self.data['vat_multiplier'] * 100))
     350                  self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
     351                  self._CHBOX_active.SetValue(self.data['active'])
     352
     353                  self._TCTRL_description.SetFocus()
     354          #----------------------------------------------------------------
     355
     356  #================================================================
     357  # invoice related widgets
     358  #----------------------------------------------------------------
     359 -def configure_invoice_template(parent=None, with_vat=True):
     360
     361          if parent is None:
     362                  parent = wx.GetApp().GetTopWindow()
     363
     364          template = gmFormWidgets.manage_form_templates (
     365                  parent = parent,
     366                  template_types = ['invoice']
     367          )
     368
     369          if template is None:
     370                  gmDispatcher.send(signal = 'statustext', msg = _('No invoice template configured.'), beep = True)
     371                  return None
     372
     373          if template['engine'] not in ['L', 'X']:
     374                  gmDispatcher.send(signal = 'statustext', msg = _('No invoice template configured.'), beep = True)
     375                  return None
     376
     377          if with_vat:
     378                  option = 'form_templates.invoice_with_vat'
     379          else:
     380                  option = 'form_templates.invoice_no_vat'
     381
     382          dbcfg = gmCfg.cCfgSQL()
     383          dbcfg.set (
     384                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     385                  option = option,
     386                  value = '%s - %s' % (template['name_long'], template['external_version'])
     387          )
     388
     389          return template
     390  #----------------------------------------------------------------
     391 -def get_invoice_template(parent=None, with_vat=True):
     392
     393          dbcfg = gmCfg.cCfgSQL()
     394          if with_vat:
     395                  option = 'form_templates.invoice_with_vat'
     396          else:
     397                  option = 'form_templates.invoice_no_vat'
     398
     399          template = dbcfg.get2 (
     400                  option = option,
     401                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     402                  bias = 'user'
     403          )
     404
     405          if template is None:
     406                  template = configure_invoice_template(parent = parent, with_vat = with_vat)
     407                  if template is None:
     408                          gmGuiHelpers.gm_show_error (
     409                                  aMessage = _('There is no invoice template configured.'),
     410                                  aTitle = _('Getting invoice template')
     411                          )
     412                          return None
     413          else:
     414                  try:
     415                          name, ver = template.split(' - ')
     416                  except Exception:
     417                          _log.exception('problem splitting invoice template name [%s]', template)
     418                          gmDispatcher.send(signal = 'statustext', msg = _('Problem loading invoice template.'), beep = True)
     419                          return None
     420                  template = gmForms.get_form_template(name_long = name, external_version = ver)
     421                  if template is None:
     422                          gmGuiHelpers.gm_show_error (
     423                                  aMessage = _('Cannot load invoice template [%s - %s]') % (name, ver),
     424                                  aTitle = _('Getting invoice template')
     425                          )
     426                          return None
     427
     428          return template
     429
     430  #================================================================
     431  # per-patient bill related widgets
     432  #----------------------------------------------------------------
     433 -def edit_bill(parent=None, bill=None, single_entry=False):
     434
     435          if bill is None:
     436                  # manually creating bills is not yet supported
     437                  return
     438
     439          ea = cBillEAPnl(parent, -1)
     440          ea.data = bill
     441          ea.mode = gmTools.coalesce(bill, 'new', 'edit')
     442          dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea, single_entry = single_entry)
     443          dlg.SetTitle(gmTools.coalesce(bill, _('Adding new bill'), _('Editing bill')))
     444          if dlg.ShowModal() == wx.ID_OK:
     445                  dlg.DestroyLater()
     446                  return True
     447          dlg.DestroyLater()
     448          return False
     449
     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          pk_pat = item['pk_patient']
     460
     461          # check item consistency
     462          has_errors = False
     463          for item in bill_items:
     464                  if item['pk_bill'] is not None:
     465                          msg = _(
     466                                  'This item is already invoiced:\n'
     467                                  '\n'
     468                                  '%s\n'
     469                                  '\n'
     470                                  'Cannot put it on a second bill.'
     471                          ) % item.format()
     472                          has_errors = True
     473                          break
     474                  if      (item['currency'] != currency) or (
     475                           item['vat_multiplier'] != vat) or (
     476                           item['pk_patient'] != pk_pat
     477                          ):
     478                          msg = _(
     479                                  'All items to be included with a bill must\n'
     480                                  'coincide on currency, VAT, and patient.\n'
     481                                  '\n'
     482                                  'This item does not:\n'
     483                                  '\n'
     484                                  '%s\n'
     485                          ) % item.format()
     486                          has_errors = True
     487                          break
     488          if has_errors:
     489                  gmGuiHelpers.gm_show_warning(aTitle = _('Checking invoice items'), aMessage = msg)
     490                  return None
     491
     492          # create bill
     493          person = gmPerson.cPerson(pk_pat)
     494          dbcfg = gmCfg.cCfgSQL()
     495          invoice_id_template = dbcfg.get2 (
     496                  option = u'billing.invoice_id_template',
     497                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     498                  bias = 'user'
     499          )
     500          invoice_id = None
     501          max_attempts = 3
     502          attempt = 0
     503          while (invoice_id is None) and (attempt < max_attempts+1):
     504                  attempt += 1
     505                  invoice_id = gmBilling.generate_invoice_id(template = invoice_id_template, person = person)
     506                  if invoice_id is None:
     507                          continue
     508                  if gmBilling.lock_invoice_id(invoice_id):
     509                          break
     510                  invoice_id = None
     511          if invoice_id is None:
     512                  gmGuiHelpers.gm_show_warning (
     513                          aTitle = _('Generating bill'),
     514                          aMessage = _('Could not generate invoice ID.\n\nTry again later.')
     515                  )
     516                  return None
     517
     518          bill = gmBilling.create_bill(invoice_id = invoice_id)
     519          gmBilling.unlock_invoice_id(invoice_id)
     520          _log.info('created bill [%s]', bill['invoice_id'])
     521          bill.add_items(items = bill_items)
     522          bill.set_missing_address_from_default()
     523
     524          return bill
     525
     526  #----------------------------------------------------------------
     527 -def create_invoice_from_bill(parent = None, bill=None, print_it=False, keep_a_copy=True):
     528
     529          bill_patient_not_active = False
     530          # do we have a current patient ?
     531          curr_pat = gmPerson.gmCurrentPatient()
     532          if curr_pat.connected:
     533                  # is the bill about the current patient, too ?
     534                  # (because that's what the new invoice would get
     535                  #  created for and attached to)
     536                  if curr_pat.ID != bill['pk_patient']:
     537                          bill_patient_not_active = True
     538          else:
     539                  bill_patient_not_active = True
     540
     541          # FIXME: could ask whether to set fk_receiver_identity
     542          # FIXME: but this would need enabling the bill EA to edit same
     543          if bill_patient_not_active:
     544                  activate_patient = gmGuiHelpers.gm_show_question (
     545                          title = _('Creating invoice'),
     546                          question = _(
     547                                  'Cannot find an existing invoice PDF for this bill.\n'
     548                                  '\n'
     549                                  'Active patient: %s\n'
     550                                  'Patient on bill: #%s\n'
     551                                  '\n'
     552                                  'Activate patient on bill so invoice PDF can be created ?'
     553                          ) % (
     554                                  gmTools.coalesce(curr_pat.ID, '', '#%s'),
     555                                  bill['pk_patient']
     556                          )
     557                  )
     558                  if not activate_patient:
     559                          return False
     560                  if not gmPatSearchWidgets.set_active_patient(patient = bill['pk_patient']):
     561                          gmGuiHelpers.gm_show_error (
     562                                  aTitle = _('Creating invoice'),
     563                                  aMessage = _('Cannot activate patient #%s.') % bill['pk_patient']
     564                          )
     565                          return False
     566
     567          if None in [ bill['close_date'], bill['pk_receiver_address'], bill['apply_vat'] ]:
     568                  edit_bill(parent = parent, bill = bill, single_entry = True)
     569                  # cannot invoice open bills
     570                  if bill['close_date'] is None:
     571                          _log.error('cannot create invoice from bill, bill not closed')
     572                          gmGuiHelpers.gm_show_warning (
     573                                  aTitle = _('Creating invoice'),
     574                                  aMessage = _(
     575                                          'Cannot create invoice from bill.\n'
     576                                          '\n'
     577                                          'The bill is not closed.'
     578                                  )
     579                          )
     580                          return False
     581                  # cannot create invoice if no receiver address
     582                  if bill['pk_receiver_address'] is None:
     583                          _log.error('cannot create invoice from bill, lacking receiver address')
     584                          gmGuiHelpers.gm_show_warning (
     585                                  aTitle = _('Creating invoice'),
     586                                  aMessage = _(
     587                                          'Cannot create invoice from bill.\n'
     588                                          '\n'
     589                                          'There is no receiver address.'
     590                                  )
     591                          )
     592                          return False
     593                  # cannot create invoice if applying VAT is undecided
     594                  if bill['apply_vat'] is None:
     595                          _log.error('cannot create invoice from bill, apply_vat undecided')
     596                          gmGuiHelpers.gm_show_warning (
     597                                  aTitle = _('Creating invoice'),
     598                                  aMessage = _(
     599                                          'Cannot create invoice from bill.\n'
     600                                          '\n'
     601                                          'You must decide on whether to apply VAT.'
     602                                  )
     603                          )
     604                          return False
     605
     606          # find template
     607          template = get_invoice_template(parent = parent, with_vat = bill['apply_vat'])
     608          if template is None:
     609                  gmGuiHelpers.gm_show_warning (
     610                          aTitle = _('Creating invoice'),
     611                          aMessage = _(
     612                                  'Cannot create invoice from bill\n'
     613                                  'without an invoice template.'
     614                          )
     615                  )
     616                  return False
     617
     618          # process template
     619          try:
     620                  invoice = template.instantiate()
     621          except KeyError:
     622                  _log.exception('cannot instantiate invoice template [%s]', template)
     623                  gmGuiHelpers.gm_show_error (
     624                          aMessage = _('Invalid invoice template [%s - %s (%s)]') % (name, ver, template['engine']),
     625                          aTitle = _('Printing medication list')
     626                  )
     627                  return False
     628
     629          ph = gmMacro.gmPlaceholderHandler()
     630          #ph.debug = True
     631          ph.set_cache_value('bill', bill)
     632          invoice.substitute_placeholders(data_source = ph)
     633          ph.unset_cache_value('bill')
     634          pdf_name = invoice.generate_output()
     635          if pdf_name is None:
     636                  gmGuiHelpers.gm_show_error (
     637                          aMessage = _('Error generating invoice PDF.'),
     638                          aTitle = _('Creating invoice')
     639                  )
     640                  return False
     641
     642          # keep a copy
     643          if keep_a_copy:
     644                  files2import = []
     645                  files2import.extend(invoice.final_output_filenames)
     646                  files2import.extend(invoice.re_editable_filenames)
     647                  doc = gmDocumentWidgets.save_files_as_new_document (
     648                          parent = parent,
     649                          filenames = files2import,
     650                          document_type = template['instance_type'],
     651                          review_as_normal = True,
     652                          reference = bill['invoice_id'],
     653                          pk_org_unit = gmPraxis.gmCurrentPraxisBranch()['pk_org_unit']
     654                  )
     655                  bill['pk_doc'] = doc['pk_doc']
     656                  bill.save()
     657
     658          if not print_it:
     659                  return True
     660
     661          # print template
     662          _cfg = gmCfg2.gmCfgData()
     663          printed = gmPrinting.print_files(filenames = [pdf_name], jobtype = 'invoice', verbose = _cfg.get(option = 'debug'))
     664          if not printed:
     665                  gmGuiHelpers.gm_show_error (
     666                          aMessage = _('Error printing the invoice.'),
     667                          aTitle = _('Printing invoice')
     668                  )
     669                  return True
     670
     671          return True
     672
     673  #----------------------------------------------------------------
     674 -def delete_bill(parent=None, bill=None):
     675
     676          if parent is None:
     677                  parent = wx.GetApp().GetTopWindow()
     678
     679          dlg = gmGuiHelpers.c3ButtonQuestionDlg (
     680                  parent, -1,
     681                  caption = _('Deleting bill'),
     682                  question = _(
     683                          'When deleting the bill [%s]\n'
     684                          'do you want to keep its items (effectively \"unbilling\" them)\n'
     685                          'or do you want to also delete the bill items from the patient ?\n'
     686                  ) % bill['invoice_id'],
     687                  button_defs = [
     688                          {'label': _('Delete + keep'), 'tooltip': _('Delete the bill but keep ("unbill") its items.'), 'default': True},
     689                          {'label': _('Delete all'), 'tooltip': _('Delete both the bill and its items from the patient.')}
     690                  ],
     691                  show_checkbox = True,
     692                  checkbox_msg = _('Also remove invoice PDF'),
     693                  checkbox_tooltip = _('Also remove the invoice PDF from the document archive (because it will not correspond to the bill anymore).')
     694          )
     695          button_pressed = dlg.ShowModal()
     696          delete_invoice = dlg.checkbox_is_checked()
     697          dlg.DestroyLater()
     698
     699          if button_pressed == wx.ID_CANCEL:
     700                  return False
     701
     702          delete_items = (button_pressed == wx.ID_NO)
     703
     704          if delete_invoice:
     705                  if bill['pk_doc'] is not None:
     706                          gmDocuments.delete_document (
     707                                  document_id = bill['pk_doc'],
     708                                  encounter_id = gmPerson.cPatient(aPK_obj = bill['pk_patient']).emr.active_encounter['pk_encounter']
     709                          )
     710
     711          items = bill['pk_bill_items']
     712          success = gmBilling.delete_bill(pk_bill = bill['pk_bill'])
     713          if delete_items:
     714                  for item in items:
     715                          gmBilling.delete_bill_item(pk_bill_item = item)
     716
     717          return success
     718
     719  #----------------------------------------------------------------
     720 -def remove_items_from_bill(parent=None, bill=None):
     721
     722          if bill is None:
     723                  return False
     724
     725          list_data = bill.bill_items
     726          if len(list_data) == 0:
     727                  return False
     728
     729          if parent is None:
     730                  parent = wx.GetApp().GetTopWindow()
     731
     732          list_items = [ [
     733                  gmDateTime.pydt_strftime(b['date_to_bill'], '%Y %b %d', accuracy = gmDateTime.acc_days),
     734                  b['unit_count'],
     735                  '%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], '', ' - %s')),
     736                  '%(curr)s %(total_val)s (%(count)s %(x)s %(unit_val)s%(x)s%(val_multiplier)s)' % {
     737                          'curr': b['currency'],
     738                          'total_val': b['total_amount'],
     739                          'count': b['unit_count'],
     740                          'x': gmTools.u_multiply,
     741                          'unit_val': b['net_amount_per_unit'],
     742                          'val_multiplier': b['amount_multiplier']
     743                  },
     744                  '%(curr)s%(vat)s (%(perc_vat)s%%)' % {
     745                          'vat': b['vat'],
     746                          'curr': b['currency'],
     747                          'perc_vat': b['vat_multiplier'] * 100
     748                  },
     749                  '%s (%s)' % (b['catalog_short'], b['catalog_version']),
     750                  b['pk_bill_item']
     751          ] for b in list_data ]
     752
     753          msg = _('Select the items you want to remove from bill [%s]:\n') % bill['invoice_id']
     754          items2remove = gmListWidgets.get_choices_from_list (
     755                  parent = parent,
     756                  msg = msg,
     757                  caption = _('Removing items from bill'),
     758                  columns = [_('Date'), _('Count'), _('Description'), _('Value'), _('VAT'), _('Catalog'), '#'],
     759                  single_selection = False,
     760                  choices = list_items,
     761                  data = list_data
     762          )
     763
     764          if items2remove is None:
     765                  return False
     766
     767          if len(items2remove) == len(list_items):
     768                  gmGuiHelpers.gm_show_info (
     769                          title = _('Removing items from bill'),
     770                          info = _(
     771                                  'Cannot remove all items from a bill because\n'
     772                                  'GNUmed does not support empty bills.\n'
     773                                  '\n'
     774                                  'You must delete the bill itself if you want to\n'
     775                                  'remove all items (at which point you can opt to\n'
     776                                  'keep the items and only delete the bill).'
     777                          )
     778                  )
     779                  return False
     780
     781          dlg = gmGuiHelpers.c3ButtonQuestionDlg (
     782                  parent, -1,
     783                  caption = _('Removing items from bill'),
     784                  question = _(
     785                          '%s items selected from bill [%s]\n'
     786                          '\n'
     787                          'Do you want to only remove the selected items\n'
     788                          'from the bill ("unbill" them) or do you want\n'
     789                          'to delete them entirely from the patient ?\n'
     790                          '\n'
     791                          'Note that neither action is reversible.'
     792                  ) % (
     793                          len(items2remove),
     794                          bill['invoice_id']
     795                  ),
     796                  button_defs = [
     797                          {'label': _('"Unbill"'), 'tooltip': _('Only "unbill" items (remove from bill but do not delete from patient).'), 'default': True},
     798                          {'label': _('Delete'), 'tooltip': _('Completely delete items from the patient.')}
     799                  ],
     800                  show_checkbox = True,
     801                  checkbox_msg = _('Also remove invoice PDF'),
     802                  checkbox_tooltip = _('Also remove the invoice PDF from the document archive (because it will not correspond to the bill anymore).')
     803          )
     804          button_pressed = dlg.ShowModal()
     805          delete_invoice = dlg.checkbox_is_checked()
     806          dlg.DestroyLater()
     807
     808          if button_pressed == wx.ID_CANCEL:
     809                  return False
     810
     811          # remember this because unlinking/deleting the items
     812          # will remove the patient PK from the bill
     813          pk_patient = bill['pk_patient']
     814
     815          for item in items2remove:
     816                  item['pk_bill'] = None
     817                  item.save()
     818                  if button_pressed == wx.ID_NO:
     819                          gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item'])
     820
     821          if delete_invoice:
     822                  if bill['pk_doc'] is not None:
     823                          gmDocuments.delete_document (
     824                                  document_id = bill['pk_doc'],
     825                                  encounter_id = gmPerson.cPatient(aPK_obj = pk_patient).emr.active_encounter['pk_encounter']
     826                          )
     827
     828          # delete bill, too, if empty
     829          if len(bill.bill_items) == 0:
     830                  gmBilling.delete_bill(pk_bill = bill['pk_bill'])
     831
     832          return True
     833
     834  #----------------------------------------------------------------
     835 -def manage_bills(parent=None, patient=None):
     836
     837          if parent is None:
     838                  parent = wx.GetApp().GetTopWindow()
     839
     840          #------------------------------------------------------------
     841          def show_pdf(bill):
     842                  if bill is None:
     843                          return False
     844
     845                  # find invoice
     846                  invoice = bill.invoice
     847                  if invoice is not None:
     848                          success, msg = invoice.parts[-1].display_via_mime()
     849                          if not success:
     850                                  gmGuiHelpers.gm_show_error(aMessage = msg, aTitle = _('Displaying invoice'))
     851                          return False
     852
     853                  # create it ?
     854                  create_it = gmGuiHelpers.gm_show_question (
     855                          title = _('Displaying invoice'),
     856                          question = _(
     857                                  'Cannot find an existing\n'
     858                                  'invoice PDF for this bill.\n'
     859                                  '\n'
     860                                  'Do you want to create one ?'
     861                          ),
     862                  )
     863                  if not create_it:
     864                          return False
     865
     866                  # prepare invoicing
     867                  if not bill.set_missing_address_from_default():
     868                          gmGuiHelpers.gm_show_warning (
     869                                  aTitle = _('Creating invoice'),
     870                                  aMessage = _(
     871                                          'There is no pre-configured billing address.\n'
     872                                          '\n'
     873                                          'Select the address you want to send the bill to.'
     874                                  )
     875                          )
     876                          edit_bill(parent = parent, bill = bill, single_entry = True)
     877                          if bill['pk_receiver_address'] is None:
     878                                  return False
     879                  if bill['close_date'] is None:
     880                          bill['close_date'] = gmDateTime.pydt_now_here()
     881                          bill.save()
     882
     883                  return create_invoice_from_bill(parent = parent, bill = bill, print_it = True, keep_a_copy = True)
     884          #------------------------------------------------------------
     885          def edit(bill):
     886                  return edit_bill(parent = parent, bill = bill, single_entry = True)
     887          #------------------------------------------------------------
     888          def delete(bill):
     889                  return delete_bill(parent = parent, bill = bill)
     890          #------------------------------------------------------------
     891          def remove_items(bill):
     892                  return remove_items_from_bill(parent = parent, bill = bill)
     893          #------------------------------------------------------------
     894          def get_tooltip(item):
     895                  if item is None:
     896                          return None
     897                  return item.format()
     898          #------------------------------------------------------------
     899          def refresh(lctrl):
     900                  if patient is None:
     901                          bills = gmBilling.get_bills()
     902                  else:
     903                          bills = gmBilling.get_bills(pk_patient = patient.ID)
     904                  items = []
     905                  for b in bills:
     906                          if b['close_date'] is None:
     907                                  close_date = _('<open>')
     908                          else:
     909                                  close_date = gmDateTime.pydt_strftime(b['close_date'], '%Y %b %d')
     910                          if b['total_amount'] is None:
     911                                  amount = _('no items on bill')
     912                          else:
     913                                  amount = gmTools.bool2subst (
     914                                          b['apply_vat'],
     915                                          _('%(currency)s%(total_amount_with_vat)s (with %(percent_vat)s%% VAT)') % b,
     916                                          '%(currency)s%(total_amount)s' % b,
     917                                          _('without VAT: %(currency)s%(total_amount)s / with %(percent_vat)s%% VAT: %(currency)s%(total_amount_with_vat)s') % b
     918                                  )
     919                          items.append ([
     920                                  close_date,
     921                                  b['invoice_id'],
     922                                  amount,
     923                                  gmTools.coalesce(b['comment'], '')
     924                          ])
     925                  lctrl.set_string_items(items)
     926                  lctrl.set_data(bills)
     927          #------------------------------------------------------------
     928          return gmListWidgets.get_choices_from_list (
     929                  parent = parent,
     930                  caption = _('Showing bills.'),
     931                  columns = [_('Close date'), _('Invoice ID'), _('Value'), _('Comment')],
     932                  single_selection = True,
     933                  edit_callback = edit,
     934                  delete_callback = delete,
     935                  refresh_callback = refresh,
     936                  middle_extra_button = (
     937                          'PDF',
     938                          _('Create if necessary, and show the corresponding invoice PDF'),
     939                          show_pdf
     940                  ),
     941                  right_extra_button = (
     942                          _('Unbill'),
     943                          _('Select and remove items from a bill.'),
     944                          remove_items
     945                  ),
     946                  list_tooltip_callback = get_tooltip
     947          )
     948
     949  #----------------------------------------------------------------
     950  from Gnumed.wxGladeWidgets import wxgBillEAPnl
     951
     952 -class cBillEAPnl(wxgBillEAPnl.wxgBillEAPnl, gmEditArea.cGenericEditAreaMixin):
     953
     954 -        def __init__(self, *args, **kwargs):
     955
     956                  try:
     957                          data = kwargs['bill']
     958                          del kwargs['bill']
     959                  except KeyError:
     960                          data = None
     961
     962                  wxgBillEAPnl.wxgBillEAPnl.__init__(self, *args, **kwargs)
     963                  gmEditArea.cGenericEditAreaMixin.__init__(self)
     964
     965                  self.mode = 'new'
     966                  self.data = data
     967                  if data is not None:
     968                          self.mode = 'edit'
     969
     970                  self._3state2bool = {
     971                          wx.CHK_UNCHECKED: False,
     972                          wx.CHK_CHECKED: True,
     973                          wx.CHK_UNDETERMINED: None
     974                  }
     975                  self.bool_to_3state = {
     976                          False: wx.CHK_UNCHECKED,
     977                          True: wx.CHK_CHECKED,
     978                          None: wx.CHK_UNDETERMINED
     979                  }
     980
     981  #               self.__init_ui()
     982          #----------------------------------------------------------------
     983  #       def __init_ui(self):
     984          #----------------------------------------------------------------
     985          # generic Edit Area mixin API
     986          #----------------------------------------------------------------
     987 -        def _valid_for_save(self):
     988                  validity = True
     989
     990                  # flag but do not count as wrong
     991                  if not self._PRW_close_date.is_valid_timestamp(empty_is_valid = False):
     992                          self._PRW_close_date.SetFocus()
     993
     994                  # flag but do not count as wrong
     995                  if self._CHBOX_vat_applies.ThreeStateValue == wx.CHK_UNDETERMINED:
     996                          self._CHBOX_vat_applies.SetFocus()
     997                          self._CHBOX_vat_applies.SetBackgroundColour('yellow')
     998
     999                  return validity
    1000          #----------------------------------------------------------------
    1001 -        def _save_as_new(self):
    1002                  # not intended to be used
    1003                  return False
    1004          #----------------------------------------------------------------
    1005 -        def _save_as_update(self):
    1006                  self.data['close_date'] = self._PRW_close_date.GetData()
    1007                  self.data['apply_vat'] = self._3state2bool[self._CHBOX_vat_applies.ThreeStateValue]
    1008                  self.data['comment'] = self._TCTRL_comment.GetValue()
    1009                  self.data.save()
    1010                  return True
    1011          #----------------------------------------------------------------
    1012 -        def _refresh_as_new(self):
    1013                  pass # not used
    1014          #----------------------------------------------------------------
    1015 -        def _refresh_as_new_from_existing(self):
    1016                  self._refresh_as_new()
    1017          #----------------------------------------------------------------
    1018 -        def _refresh_from_existing(self):
    1019                  self._TCTRL_invoice_id.SetValue(self.data['invoice_id'])
    1020                  self._PRW_close_date.SetText(data = self.data['close_date'])
    1021
    1022                  self.data.set_missing_address_from_default()
    1023                  if self.data['pk_receiver_address'] is None:
    1024                          self._TCTRL_address.SetValue('')
    1025                  else:
    1026                          adr = self.data.address
    1027                          self._TCTRL_address.SetValue(adr.format(single_line = True, show_type = False))
    1028
    1029                  self._TCTRL_value.SetValue('%(currency)s%(total_amount)s' % self.data)
    1030                  self._CHBOX_vat_applies.ThreeStateValue = self.bool_to_3state[self.data['apply_vat']]
    1031                  self._CHBOX_vat_applies.SetLabel(_('&VAT applies (%s%%)') % self.data['percent_vat'])
    1032                  if self.data['apply_vat'] is True:
    1033                          tmp = '%s %%(currency)s%%(total_vat)s %s %s %%(currency)s%%(total_amount_with_vat)s' % (
    1034                                  gmTools.u_corresponds_to,
    1035                                  gmTools.u_arrow2right,
    1036                                  gmTools.u_sum,
    1037                          )
    1038                          self._TCTRL_value_with_vat.SetValue(tmp % self.data)
    1039                  elif self.data['apply_vat'] is None:
    1040                          self._TCTRL_value_with_vat.SetValue('?')
    1041                  else:
    1042                          self._TCTRL_value_with_vat.SetValue('')
    1043
    1044                  self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
    1045
    1046                  self._PRW_close_date.SetFocus()
    1047          #----------------------------------------------------------------
    1048          # event handling
    1049          #----------------------------------------------------------------
    1050 -        def _on_vat_applies_box_checked(self, event):
    1051                  if self._CHBOX_vat_applies.ThreeStateValue == wx.CHK_CHECKED:
    1052                          tmp = '%s %%(currency)s%%(total_vat)s %s %s %%(currency)s%%(total_amount_with_vat)s' % (
    1053                                  gmTools.u_corresponds_to,
    1054                                  gmTools.u_arrow2right,
    1055                                  gmTools.u_sum,
    1056                          )
    1057                          self._TCTRL_value_with_vat.SetValue(tmp % self.data)
    1058                          return
    1059                  if self._CHBOX_vat_applies.ThreeStateValue == wx.CHK_UNDETERMINED:
    1060                          self._TCTRL_value_with_vat.SetValue('?')
    1061                          return
    1062                  self._TCTRL_value_with_vat.SetValue('')
    1063          #----------------------------------------------------------------
    1064 -        def _on_select_address_button_pressed(self, event):
    1065                  adr = gmPersonContactWidgets.select_address (
    1066                          missing = _('billing'),
    1067                          person = gmPerson.cPerson(aPK_obj = self.data['pk_patient'])
    1068                  )
    1069                  if adr is None:
    1070                          gmGuiHelpers.gm_show_info (
    1071                                  aTitle = _('Selecting address'),
    1072                                  aMessage = _('GNUmed does not know any addresses for this patient.')
    1073                          )
    1074                          return
    1075                  self.data['pk_receiver_address'] = adr['pk_lnk_person_org_address']
    1076                  self.data.save()
    1077                  self._TCTRL_address.SetValue(adr.format(single_line = True, show_type = False))
    1078
    1079  #================================================================
    1080  # per-patient bill items related widgets
    1081  #----------------------------------------------------------------
    1082 -def edit_bill_item(parent=None, bill_item=None, single_entry=False):
    1083
    1084          if bill_item is not None:
    1085                  if bill_item.is_in_use:
    1086                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit already invoiced bill item.'), beep = True)
    1087                          return False
    1088
    1089          ea = cBillItemEAPnl(parent, -1)
    1090          ea.data = bill_item
    1091          ea.mode = gmTools.coalesce(bill_item, 'new', 'edit')
    1092          dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea, single_entry = single_entry)
    1093          dlg.SetTitle(gmTools.coalesce(bill_item, _('Adding new bill item'), _('Editing bill item')))
    1094          if dlg.ShowModal() == wx.ID_OK:
    1095                  dlg.DestroyLater()
    1096                  return True
    1097          dlg.DestroyLater()
    1098          return False
    1099  #----------------------------------------------------------------
    1100 -def manage_bill_items(parent=None, pk_patient=None):
    1101
    1102          if parent is None:
    1103                  parent = wx.GetApp().GetTopWindow()
    1104          #------------------------------------------------------------
    1105          def edit(item=None):
    1106                  return edit_bill_item(parent = parent, bill_item = item, single_entry = (item is not None))
    1107          #------------------------------------------------------------
    1108          def delete(item):
    1109                  if item.is_in_use is not None:
    1110                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete already invoiced bill items.'), beep = True)
    1111                          return False
    1112                  gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item'])
    1113                  return True
    1114          #------------------------------------------------------------
    1115          def get_tooltip(item):
    1116                  if item is None:
    1117                          return None
    1118                  return item.format()
    1119          #------------------------------------------------------------
    1120          def refresh(lctrl):
    1121                  b_items = gmBilling.get_bill_items(pk_patient = pk_patient)
    1122                  items = [ [
    1123                          gmDateTime.pydt_strftime(b['date_to_bill'], '%Y %b %d', accuracy = gmDateTime.acc_days),
    1124                          b['unit_count'],
    1125                          '%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], '', ' - %s')),
    1126                          b['currency'],
    1127                          '%s (%s %s %s%s%s)' % (
    1128                                  b['total_amount'],
    1129                                  b['unit_count'],
    1130                                  gmTools.u_multiply,
    1131                                  b['net_amount_per_unit'],
    1132                                  gmTools.u_multiply,
    1133                                  b['amount_multiplier']
    1134                          ),
    1135                          '%s (%s%%)' % (
    1136                                  b['vat'],
    1137                                  b['vat_multiplier'] * 100
    1138                          ),
    1139                          '%s (%s)' % (b['catalog_short'], b['catalog_version']),
    1140                          b['pk_bill_item']
    1141                  ] for b in b_items ]
    1142                  lctrl.set_string_items(items)
    1143                  lctrl.set_data(b_items)
    1144          #------------------------------------------------------------
    1145          gmListWidgets.get_choices_from_list (
    1146                  parent = parent,
    1147                  #msg = msg,
    1148                  caption = _('Showing bill items.'),
    1149                  columns = [_('Date'), _('Count'), _('Description'), _('$__replace_by_your_currency_symbol')[:-len('__replace_by_your_currency_symbol')], _('Value'), _('VAT'), _('Catalog'), '#'],
    1150                  single_selection = True,
    1151                  new_callback = edit,
    1152                  edit_callback = edit,
    1153                  delete_callback = delete,
    1154                  refresh_callback = refresh,
    1155                  list_tooltip_callback = get_tooltip
    1156          )
    1157
    1158  #------------------------------------------------------------
    1159 -class cPersonBillItemsManagerPnl(gmListWidgets.cGenericListManagerPnl):
    1160          """A list for managing a patient's bill items.
    1161
    1162          Does NOT act on/listen to the current patient.
    1163          """
    1164 -        def __init__(self, *args, **kwargs):
    1165
    1166                  try:
    1167                          self.__identity = kwargs['identity']
    1168                          del kwargs['identity']
    1169                  except KeyError:
    1170                          self.__identity = None
    1171
    1172                  gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs)
    1173
    1174                  self.refresh_callback = self.refresh
    1175                  self.new_callback = self._add_item
    1176                  self.edit_callback = self._edit_item
    1177                  self.delete_callback = self._del_item
    1178
    1179                  self.__show_non_invoiced_only = True
    1180
    1181                  self.__init_ui()
    1182                  self.refresh()
    1183          #--------------------------------------------------------
    1184          # external API
    1185          #--------------------------------------------------------
    1186 -        def refresh(self, *args, **kwargs):
    1187                  if self.__identity is None:
    1188                          self._LCTRL_items.set_string_items()
    1189                          return
    1190
    1191                  b_items = gmBilling.get_bill_items(pk_patient = self.__identity.ID, non_invoiced_only = self.__show_non_invoiced_only)
    1192                  items = [ [
    1193                          gmDateTime.pydt_strftime(b['date_to_bill'], '%Y %b %d', accuracy = gmDateTime.acc_days),
    1194                          b['unit_count'],
    1195                          '%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], '', ' - %s')),
    1196                          b['currency'],
    1197                          b['total_amount'],
    1198                          '%s (%s%%)' % (
    1199                                  b['vat'],
    1200                                  b['vat_multiplier'] * 100
    1201                          ),
    1202                          '%s (%s)' % (b['catalog_short'], b['catalog_version']),
    1203                          '%s %s %s %s %s' % (
    1204                                  b['unit_count'],
    1205                                  gmTools.u_multiply,
    1206                                  b['net_amount_per_unit'],
    1207                                  gmTools.u_multiply,
    1208                                  b['amount_multiplier']
    1209                          ),
    1210                          gmTools.coalesce(b['pk_bill'], gmTools.u_diameter),
    1211                          b['pk_encounter_to_bill'],
    1212                          b['pk_bill_item']
    1213                  ] for b in b_items ]
    1214
    1215                  self._LCTRL_items.set_string_items(items = items)
    1216                  self._LCTRL_items.set_column_widths()
    1217                  self._LCTRL_items.set_data(data = b_items)
    1218          #--------------------------------------------------------
    1219          # internal helpers
    1220          #--------------------------------------------------------
    1221 -        def __init_ui(self):
    1222                  self._LCTRL_items.set_columns(columns = [
    1223                          _('Charge date'),
    1224                          _('Count'),
    1225                          _('Description'),
    1226                          _('$__replace_by_your_currency_symbol')[:-len('__replace_by_your_currency_symbol')],
    1227                          _('Value'),
    1228                          _('VAT'),
    1229                          _('Catalog'),
    1230                          _('Count %s Value %s Factor') % (gmTools.u_multiply, gmTools.u_multiply),
    1231                          _('Invoice'),
    1232                          _('Encounter'),
    1233                          '#'
    1234                  ])
    1235                  self._LCTRL_items.item_tooltip_callback = self._get_item_tooltip
    1236  #               self.left_extra_button = (
    1237  #                       _('Select pending'),
    1238  #                       _('Select non-invoiced (pending) items.'),
    1239  #                       self._select_pending_items
    1240  #               )
    1241                  self.left_extra_button = (
    1242                          _('Invoice selected items'),
    1243                          _('Create invoice from selected items.'),
    1244                          self._invoice_selected_items
    1245                  )
    1246                  self.middle_extra_button = (
    1247                          _('Bills'),
    1248                          _('Browse bills of this patient.'),
    1249                          self._browse_bills
    1250                  )
    1251                  self.right_extra_button = (
    1252                          _('Billables'),
    1253                          _('Browse list of billables.'),
    1254                          self._browse_billables
    1255                  )
    1256          #--------------------------------------------------------
    1257 -        def _add_item(self):
    1258                  return edit_bill_item(parent = self, bill_item = None, single_entry = False)
    1259          #--------------------------------------------------------
    1260 -        def _edit_item(self, bill_item):
    1261                  return edit_bill_item(parent = self, bill_item = bill_item, single_entry = True)
    1262          #--------------------------------------------------------
    1263 -        def _del_item(self, item):
    1264                  if item['pk_bill'] is not None:
    1265                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete already invoiced bill items.'), beep = True)
    1266                          return False
    1267                  go_ahead = gmGuiHelpers.gm_show_question (
    1268                          _(      'Do you really want to delete this\n'
    1269                                  'bill item from the patient ?'),
    1270                          _('Deleting bill item')
    1271                  )
    1272                  if not go_ahead:
    1273                          return False
    1274                  gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item'])
    1275                  return True
    1276          #--------------------------------------------------------
    1277 -        def _get_item_tooltip(self, item):
    1278                  if item is None:
    1279                          return None
    1280                  return item.format()
    1281          #--------------------------------------------------------
    1282 -        def _select_pending_items(self, item):
    1283                  pass
    1284          #--------------------------------------------------------
    1285 -        def _invoice_selected_items(self, item):
    1286                  bill_items = self._LCTRL_items.get_selected_item_data()
    1287                  bill = create_bill_from_items(bill_items)
    1288                  if bill is None:
    1289                          return
    1290                  if bill['pk_receiver_address'] is None:
    1291                          gmGuiHelpers.gm_show_error (
    1292                                  aMessage = _(
    1293                                          'Cannot create invoice.\n'
    1294                                          '\n'
    1295                                          'No receiver address selected.'
    1296                                  ),
    1297                                  aTitle = _('Creating invoice')
    1298                          )
    1299                          return
    1300                  if bill['close_date'] is None:
    1301                          bill['close_date'] = gmDateTime.pydt_now_here()
    1302                          bill.save()
    1303                  create_invoice_from_bill(parent = self, bill = bill, print_it = True, keep_a_copy = True)
    1304          #--------------------------------------------------------
    1305 -        def _browse_billables(self, item):
    1306                  manage_billables(parent = self)
    1307                  return False
    1308          #--------------------------------------------------------
    1309 -        def _browse_bills(self, item):
    1310                  manage_bills(parent = self, patient = self.__identity)
    1311          #--------------------------------------------------------
    1312          # properties
    1313          #--------------------------------------------------------
    1314 -        def _get_identity(self):
    1315                  return self.__identity
    1316
    1317 -        def _set_identity(self, identity):
    1318                  self.__identity = identity
    1319                  self.refresh()
    1320
    1321          identity = property(_get_identity, _set_identity)
    1322          #--------------------------------------------------------
    1323 -        def _get_show_non_invoiced_only(self):
    1324                  return self.__show_non_invoiced_only
    1325
    1326 -        def _set_show_non_invoiced_only(self, value):
    1327                  self.__show_non_invoiced_only = value
    1328                  self.refresh()
    1329
    1330          show_non_invoiced_only = property(_get_show_non_invoiced_only, _set_show_non_invoiced_only)
    1331
    1332  #------------------------------------------------------------
    1333  from Gnumed.wxGladeWidgets import wxgBillItemEAPnl
    1334
    1335 -class cBillItemEAPnl(wxgBillItemEAPnl.wxgBillItemEAPnl, gmEditArea.cGenericEditAreaMixin):
    1336
    1337 -        def __init__(self, *args, **kwargs):
    1338
    1339                  try:
    1340                          data = kwargs['bill_item']
    1341                          del kwargs['bill_item']
    1342                  except KeyError:
    1343                          data = None
    1344
    1345                  wxgBillItemEAPnl.wxgBillItemEAPnl.__init__(self, *args, **kwargs)
    1346                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    1347
    1348                  self.mode = 'new'
    1349                  self.data = data
    1350                  if data is not None:
    1351                          self.mode = 'edit'
    1352
    1353                  self.__init_ui()
    1354          #----------------------------------------------------------------
    1355 -        def __init_ui(self):
    1356                  self._PRW_encounter.set_context(context = 'patient', val = gmPerson.gmCurrentPatient().ID)
    1357                  self._PRW_billable.add_callback_on_selection(self._on_billable_selected)
    1358                  self._PRW_billable.add_callback_on_modified(self._on_billable_modified)
    1359          #----------------------------------------------------------------
    1360          # generic Edit Area mixin API
    1361          #----------------------------------------------------------------
    1362 -        def _valid_for_save(self):
    1363
    1364                  validity = True
    1365
    1366                  if self._TCTRL_factor.GetValue().strip() == '':
    1367                          validity = False
    1368                          self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = False)
    1369                          self._TCTRL_factor.SetFocus()
    1370                  else:
    1371                          converted, factor = gmTools.input2decimal(self._TCTRL_factor.GetValue())
    1372                          if not converted:
    1373                                  validity = False
    1374                                  self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = False)
    1375                                  self._TCTRL_factor.SetFocus()
    1376                          else:
    1377                                  self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = True)
    1378
    1379                  if self._TCTRL_amount.GetValue().strip() == '':
    1380                          validity = False
    1381                          self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False)
    1382                          self._TCTRL_amount.SetFocus()
    1383                  else:
    1384                          converted, factor = gmTools.input2decimal(self._TCTRL_amount.GetValue())
    1385                          if not converted:
    1386                                  validity = False
    1387                                  self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False)
    1388                                  self._TCTRL_amount.SetFocus()
    1389                          else:
    1390                                  self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = True)
    1391
    1392                  if self._TCTRL_count.GetValue().strip() == '':
    1393                          validity = False
    1394                          self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = False)
    1395                          self._TCTRL_count.SetFocus()
    1396                  else:
    1397                          converted, factor = gmTools.input2decimal(self._TCTRL_count.GetValue())
    1398                          if not converted:
    1399                                  validity = False
    1400                                  self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = False)
    1401                                  self._TCTRL_count.SetFocus()
    1402                          else:
    1403                                  self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = True)
    1404
    1405                  if self._PRW_date.is_valid_timestamp(empty_is_valid = True):
    1406                          self._PRW_date.display_as_valid(True)
    1407                  else:
    1408                          validity = False
    1409                          self._PRW_date.display_as_valid(False)
    1410                          self._PRW_date.SetFocus()
    1411
    1412                  if self._PRW_encounter.GetData() is None:
    1413                          validity = False
    1414                          self._PRW_encounter.display_as_valid(False)
    1415                          self._PRW_encounter.SetFocus()
    1416                  else:
    1417                          self._PRW_encounter.display_as_valid(True)
    1418
    1419                  if self._PRW_billable.GetData() is None:
    1420                          validity = False
    1421                          self._PRW_billable.display_as_valid(False)
    1422                          self._PRW_billable.SetFocus()
    1423                  else:
    1424                          self._PRW_billable.display_as_valid(True)
    1425
    1426                  return validity
    1427          #----------------------------------------------------------------
    1428 -        def _save_as_new(self):
    1429                  data = gmBilling.create_bill_item (
    1430                          pk_encounter = self._PRW_encounter.GetData(),
    1431                          pk_billable = self._PRW_billable.GetData(),
    1432                          pk_staff = gmStaff.gmCurrentProvider()['pk_staff']              # should be settable !
    1433                  )
    1434                  data['raw_date_to_bill'] = self._PRW_date.GetData()
    1435                  converted, data['unit_count'] = gmTools.input2decimal(self._TCTRL_count.GetValue())
    1436                  converted, data['net_amount_per_unit'] = gmTools.input2decimal(self._TCTRL_amount.GetValue())
    1437                  converted, data['amount_multiplier'] = gmTools.input2decimal(self._TCTRL_factor.GetValue())
    1438                  data['item_detail'] = self._TCTRL_comment.GetValue().strip()
    1439                  data.save()
    1440
    1441                  self.data = data
    1442                  return True
    1443          #----------------------------------------------------------------
    1444 -        def _save_as_update(self):
    1445                  self.data['pk_encounter_to_bill'] = self._PRW_encounter.GetData()
    1446                  self.data['raw_date_to_bill'] = self._PRW_date.GetData()
    1447                  converted, self.data['unit_count'] = gmTools.input2decimal(self._TCTRL_count.GetValue())
    1448                  converted, self.data['net_amount_per_unit'] = gmTools.input2decimal(self._TCTRL_amount.GetValue())
    1449                  converted, self.data['amount_multiplier'] = gmTools.input2decimal(self._TCTRL_factor.GetValue())
    1450                  self.data['item_detail'] = self._TCTRL_comment.GetValue().strip()
    1451                  return self.data.save()
    1452          #----------------------------------------------------------------
    1453 -        def _refresh_as_new(self):
    1454                  self._PRW_billable.SetText()
    1455                  self._PRW_encounter.set_from_instance(gmPerson.gmCurrentPatient().emr.active_encounter)
    1456                  self._PRW_date.SetData()
    1457                  self._TCTRL_count.SetValue('1')
    1458                  self._TCTRL_amount.SetValue('')
    1459                  self._LBL_currency.SetLabel(gmTools.u_euro)
    1460                  self._TCTRL_factor.SetValue('1')
    1461                  self._TCTRL_comment.SetValue('')
    1462
    1463                  self._PRW_billable.Enable()
    1464                  self._PRW_billable.SetFocus()
    1465          #----------------------------------------------------------------
    1466 -        def _refresh_as_new_from_existing(self):
    1467                  self._PRW_billable.SetText()
    1468                  self._TCTRL_count.SetValue('1')
    1469                  self._TCTRL_amount.SetValue('')
    1470                  self._TCTRL_comment.SetValue('')
    1471
    1472                  self._PRW_billable.Enable()
    1473                  self._PRW_billable.SetFocus()
    1474          #----------------------------------------------------------------
    1475 -        def _refresh_from_existing(self):
    1476                  self._PRW_billable.set_from_pk(self.data['pk_billable'])
    1477                  self._PRW_encounter.SetData(self.data['pk_encounter_to_bill'])
    1478                  self._PRW_date.SetData(data = self.data['raw_date_to_bill'])
    1479                  self._TCTRL_count.SetValue('%s' % self.data['unit_count'])
    1480                  self._TCTRL_amount.SetValue('%s' % self.data['net_amount_per_unit'])
    1481                  self._LBL_currency.SetLabel(self.data['currency'])
    1482                  self._TCTRL_factor.SetValue('%s' % self.data['amount_multiplier'])
    1483                  self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['item_detail'], ''))
    1484
    1485                  self._PRW_billable.Disable()
    1486                  self._PRW_date.SetFocus()
    1487          #----------------------------------------------------------------
    1488 -        def _on_billable_selected(self, item):
    1489                  if item is None:
    1490                          return
    1491                  if self._TCTRL_amount.GetValue().strip() != '':
    1492                          return
    1493                  val = '%s' % self._PRW_billable.GetData(as_instance = True)['raw_amount']
    1494                  wx.CallAfter(self._TCTRL_amount.SetValue, val)
    1495          #----------------------------------------------------------------
    1496 -        def _on_billable_modified(self):
    1497                  if self._PRW_billable.GetData() is None:
    1498                          wx.CallAfter(self._TCTRL_amount.SetValue, '')
    1499
    1500  #============================================================
    1501  # a plugin for billing
    1502  #------------------------------------------------------------
    1503  from Gnumed.wxGladeWidgets import wxgBillingPluginPnl
    1504
    1505 -class cBillingPluginPnl(wxgBillingPluginPnl.wxgBillingPluginPnl, gmRegetMixin.cRegetOnPaintMixin):
    1506 -        def __init__(self, *args, **kwargs):
    1507
    1508                  wxgBillingPluginPnl.wxgBillingPluginPnl.__init__(self, *args, **kwargs)
    1509                  gmRegetMixin.cRegetOnPaintMixin.__init__(self)
    1510                  self.__register_interests()
    1511          #-----------------------------------------------------
    1512 -        def __reset_ui(self):
    1513                  self._PNL_bill_items.identity = None
    1514                  self._CHBOX_show_non_invoiced_only.SetValue(1)
    1515                  self._PRW_billable.SetText('', None)
    1516                  self._TCTRL_factor.SetValue('1.0')
    1517                  self._TCTRL_factor.Disable()
    1518                  self._TCTRL_details.SetValue('')
    1519                  self._TCTRL_details.Disable()
    1520          #-----------------------------------------------------
    1521          # event handling
    1522          #-----------------------------------------------------
    1523 -        def __register_interests(self):
    1524                  gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
    1525                  gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
    1526
    1527                  gmDispatcher.connect(signal = 'bill.bill_item_mod_db', receiver = self._on_bill_item_modified)
    1528
    1529                  self._PRW_billable.add_callback_on_selection(self._on_billable_selected_in_prw)
    1530          #-----------------------------------------------------
    1531 -        def _on_pre_patient_unselection(self):
    1532                  self.__reset_ui()
    1533          #-----------------------------------------------------
    1534 -        def _on_post_patient_selection(self):
    1535                  self._schedule_data_reget()
    1536          #-----------------------------------------------------
    1537 -        def _on_bill_item_modified(self):
    1538                  self._schedule_data_reget()
    1539          #-----------------------------------------------------
    1540 -        def _on_non_invoiced_only_checkbox_toggled(self, event):
    1541                  self._PNL_bill_items.show_non_invoiced_only = self._CHBOX_show_non_invoiced_only.GetValue()
    1542          #--------------------------------------------------------
    1543 -        def _on_insert_bill_item_button_pressed(self, event):
    1544                  if self._PRW_billable.GetData() is None:
    1545                          gmGuiHelpers.gm_show_warning (
    1546                                  _('No billable item selected.\n\nCannot insert bill item.'),
    1547                                  _('Inserting bill item')
    1548                          )
    1549                          return False
    1550                  val = self._TCTRL_factor.GetValue().strip()
    1551                  if val == '':
    1552                          factor = 1.0
    1553                  else:
    1554                          converted, factor = gmTools.input2decimal(val)
    1555                          if not converted:
    1556                                  gmGuiHelpers.gm_show_warning (
    1557                                          _('"Factor" must be a number\n\nCannot insert bill item.'),
    1558                                          _('Inserting bill item')
    1559                                  )
    1560                                  return False
    1561                  bill_item = gmBilling.create_bill_item (
    1562                          pk_encounter = gmPerson.gmCurrentPatient().emr.active_encounter['pk_encounter'],
    1563                          pk_billable = self._PRW_billable.GetData(),
    1564                          pk_staff = gmStaff.gmCurrentProvider()['pk_staff']
    1565                  )
    1566                  bill_item['amount_multiplier'] = factor
    1567                  bill_item['item_detail'] = self._TCTRL_details.GetValue()
    1568                  bill_item.save()
    1569
    1570                  self._TCTRL_details.SetValue('')
    1571
    1572                  return True
    1573          #--------------------------------------------------------
    1574 -        def _on_billable_selected_in_prw(self, billable):
    1575                  if billable is None:
    1576                          self._TCTRL_factor.Disable()
    1577                          self._TCTRL_details.Disable()
    1578                          self._BTN_insert_item.Disable()
    1579                  else:
    1580                          self._TCTRL_factor.Enable()
    1581                          self._TCTRL_details.Enable()
    1582                          self._BTN_insert_item.Enable()
    1583          #-----------------------------------------------------
    1584          # reget-on-paint mixin API
    1585          #-----------------------------------------------------
    1586 -        def _populate_with_data(self):
    1587                  self._PNL_bill_items.identity = gmPerson.gmCurrentPatient()
    1588                  return True
    1589
    1590  #============================================================
    1591  # main
    1592  #------------------------------------------------------------
    1593  if __name__ == '__main__':
    1594
    1595          if len(sys.argv) < 2:
    1596                  sys.exit()
    1597
    1598          if sys.argv[1] != 'test':
    1599                  sys.exit()
    1600
    1601          from Gnumed.pycommon import gmI18N
    1602          gmI18N.activate_locale()
    1603          gmI18N.install_domain(domain = 'gnumed')
    1604
    1605          #----------------------------------------
    1606          app = wx.PyWidgetTester(size = (600, 600))
    1607          #app.SetWidget(cXxxPhraseWheel, -1)
    1608          app.MainLoop()
    1609