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.Destroy()
      64                  return True
      65          dlg.Destroy()
      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.status_message = _('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.status_message = _('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.status_message = _('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.status_message = _('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.status_message = _('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.status_message = _('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.status_message = _('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:
     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.Destroy()
     446                  return True
     447          dlg.Destroy()
     448          return False
     449  #----------------------------------------------------------------
     450 -def create_bill_from_items(bill_items=None):
     451
     452          if len(bill_items) == 0:
     453                  return None
     454
     455          item = bill_items[0]
     456          currency = item['currency']
     457          vat = item['vat_multiplier']
     458          pat = item['pk_patient']
     459
     460          # check item consistency
     461          has_errors = False
     462          for item in bill_items:
     463                  if      (item['currency'] != currency) or (
     464                           item['vat_multiplier'] != vat) or (
     465                           item['pk_patient'] != pat
     466                          ):
     467                          msg = _(
     468                                  'All items to be included with a bill must\n'
     469                                  'coincide on currency, VAT, and patient.\n'
     470                                  '\n'
     471                                  'This item does not:\n'
     472                                  '\n'
     473                                  '%s\n'
     474                          ) % item.format()
     475                          has_errors = True
     476
     477                  if item['pk_bill'] is not None:
     478                          msg = _(
     479                                  'This item is already invoiced:\n'
     480                                  '\n'
     481                                  '%s\n'
     482                                  '\n'
     483                                  'Cannot put it on a second bill.'
     484                          ) % item.format()
     485                          has_errors = True
     486
     487                  if has_errors:
     488                          gmGuiHelpers.gm_show_warning(aTitle = _('Checking invoice items'), aMessage = msg)
     489                          return None
     490
     491          # create bill
     492          bill = gmBilling.create_bill(invoice_id = gmBilling.get_invoice_id(pk_patient = pat))
     493          _log.info('created bill [%s]', bill['invoice_id'])
     494          bill.add_items(items = bill_items)
     495          bill.set_missing_address_from_default()
     496
     497          return bill
     498
     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, '', '#%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'], bill['apply_vat'] ]:
     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                  # cannot create invoice if applying VAT is undecided
     567                  if bill['apply_vat'] is None:
     568                          _log.error('cannot create invoice from bill, apply_vat undecided')
     569                          gmGuiHelpers.gm_show_warning (
     570                                  aTitle = _('Creating invoice'),
     571                                  aMessage = _(
     572                                          'Cannot create invoice from bill.\n'
     573                                          '\n'
     574                                          'You must decide on whether to apply VAT.'
     575                                  )
     576                          )
     577                          return False
     578
     579          # find template
     580          template = get_invoice_template(parent = parent, with_vat = bill['apply_vat'])
     581          if template is None:
     582                  gmGuiHelpers.gm_show_warning (
     583                          aTitle = _('Creating invoice'),
     584                          aMessage = _(
     585                                  'Cannot create invoice from bill\n'
     586                                  'without an invoice template.'
     587                          )
     588                  )
     589                  return False
     590
     591          # process template
     592          try:
     593                  invoice = template.instantiate()
     594          except KeyError:
     595                  _log.exception('cannot instantiate invoice template [%s]', template)
     596                  gmGuiHelpers.gm_show_error (
     597                          aMessage = _('Invalid invoice template [%s - %s (%s)]') % (name, ver, template['engine']),
     598                          aTitle = _('Printing medication list')
     599                  )
     600                  return False
     601
     602          ph = gmMacro.gmPlaceholderHandler()
     603          #ph.debug = True
     604          ph.set_cache_value('bill', bill)
     605          invoice.substitute_placeholders(data_source = ph)
     606          ph.unset_cache_value('bill')
     607          pdf_name = invoice.generate_output()
     608          if pdf_name is None:
     609                  gmGuiHelpers.gm_show_error (
     610                          aMessage = _('Error generating invoice PDF.'),
     611                          aTitle = _('Creating invoice')
     612                  )
     613                  return False
     614
     615          # keep a copy
     616          if keep_a_copy:
     617                  files2import = []
     618                  files2import.extend(invoice.final_output_filenames)
     619                  files2import.extend(invoice.re_editable_filenames)
     620                  doc = gmDocumentWidgets.save_files_as_new_document (
     621                          parent = parent,
     622                          filenames = files2import,
     623                          document_type = template['instance_type'],
     624                          review_as_normal = True,
     625                          reference = bill['invoice_id'],
     626                          pk_org_unit = gmPraxis.gmCurrentPraxisBranch()['pk_org_unit']
     627                  )
     628                  bill['pk_doc'] = doc['pk_doc']
     629                  bill.save()
     630
     631          if not print_it:
     632                  return True
     633
     634          # print template
     635          _cfg = gmCfg2.gmCfgData()
     636          printed = gmPrinting.print_files(filenames = [pdf_name], jobtype = 'invoice', verbose = _cfg.get(option = 'debug'))
     637          if not printed:
     638                  gmGuiHelpers.gm_show_error (
     639                          aMessage = _('Error printing the invoice.'),
     640                          aTitle = _('Printing invoice')
     641                  )
     642                  return True
     643
     644          return True
     645
     646  #----------------------------------------------------------------
     647 -def delete_bill(parent=None, bill=None):
     648
     649          if parent is None:
     650                  parent = wx.GetApp().GetTopWindow()
     651
     652          dlg = gmGuiHelpers.c3ButtonQuestionDlg (
     653                  parent, -1,
     654                  caption = _('Deleting bill'),
     655                  question = _(
     656                          'When deleting the bill [%s]\n'
     657                          'do you want to keep its items (effectively \"unbilling\" them)\n'
     658                          'or do you want to also delete the bill items from the patient ?\n'
     659                  ) % bill['invoice_id'],
     660                  button_defs = [
     661                          {'label': _('Delete + keep'), 'tooltip': _('Delete the bill but keep ("unbill") its items.'), 'default': True},
     662                          {'label': _('Delete all'), 'tooltip': _('Delete both the bill and its items from the patient.')}
     663                  ],
     664                  show_checkbox = True,
     665                  checkbox_msg = _('Also remove invoice PDF'),
     666                  checkbox_tooltip = _('Also remove the invoice PDF from the document archive (because it will not correspond to the bill anymore).')
     667          )
     668          button_pressed = dlg.ShowModal()
     669          delete_invoice = dlg.checkbox_is_checked()
     670          dlg.Destroy()
     671
     672          if button_pressed == wx.ID_CANCEL:
     673                  return False
     674
     675          delete_items = (button_pressed == wx.ID_NO)
     676
     677          if delete_invoice:
     678                  if bill['pk_doc'] is not None:
     679                          gmDocuments.delete_document (
     680                                  document_id = bill['pk_doc'],
     681                                  encounter_id = gmPerson.cPatient(aPK_obj = bill['pk_patient']).emr.active_encounter['pk_encounter']
     682                          )
     683
     684          items = bill['pk_bill_items']
     685          success = gmBilling.delete_bill(pk_bill = bill['pk_bill'])
     686          if delete_items:
     687                  for item in items:
     688                          gmBilling.delete_bill_item(pk_bill_item = item)
     689
     690          return success
     691
     692  #----------------------------------------------------------------
     693 -def remove_items_from_bill(parent=None, bill=None):
     694
     695          if bill is None:
     696                  return False
     697
     698          list_data = bill.bill_items
     699          if len(list_data) == 0:
     700                  return False
     701
     702          if parent is None:
     703                  parent = wx.GetApp().GetTopWindow()
     704
     705          list_items = [ [
     706                  gmDateTime.pydt_strftime(b['date_to_bill'], '%Y %b %d', accuracy = gmDateTime.acc_days),
     707                  b['unit_count'],
     708                  '%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], '', ' - %s')),
     709                  '%(curr)s %(total_val)s (%(count)s %(x)s %(unit_val)s%(x)s%(val_multiplier)s)' % {
     710                          'curr': b['currency'],
     711                          'total_val': b['total_amount'],
     712                          'count': b['unit_count'],
     713                          'x': gmTools.u_multiply,
     714                          'unit_val': b['net_amount_per_unit'],
     715                          'val_multiplier': b['amount_multiplier']
     716                  },
     717                  '%(curr)s%(vat)s (%(perc_vat)s%%)' % {
     718                          'vat': b['vat'],
     719                          'curr': b['currency'],
     720                          'perc_vat': b['vat_multiplier'] * 100
     721                  },
     722                  '%s (%s)' % (b['catalog_short'], b['catalog_version']),
     723                  b['pk_bill_item']
     724          ] for b in list_data ]
     725
     726          msg = _('Select the items you want to remove from bill [%s]:\n') % bill['invoice_id']
     727          items2remove = gmListWidgets.get_choices_from_list (
     728                  parent = parent,
     729                  msg = msg,
     730                  caption = _('Removing items from bill'),
     731                  columns = [_('Date'), _('Count'), _('Description'), _('Value'), _('VAT'), _('Catalog'), '#'],
     732                  single_selection = False,
     733                  choices = list_items,
     734                  data = list_data
     735          )
     736
     737          if items2remove is None:
     738                  return False
     739
     740          if len(items2remove) == len(list_items):
     741                  gmGuiHelpers.gm_show_info (
     742                          title = _('Removing items from bill'),
     743                          info = _(
     744                                  'Cannot remove all items from a bill because\n'
     745                                  'GNUmed does not support empty bills.\n'
     746                                  '\n'
     747                                  'You must delete the bill itself if you want to\n'
     748                                  'remove all items (at which point you can opt to\n'
     749                                  'keep the items and only delete the bill).'
     750                          )
     751                  )
     752                  return False
     753
     754          dlg = gmGuiHelpers.c3ButtonQuestionDlg (
     755                  parent, -1,
     756                  caption = _('Removing items from bill'),
     757                  question = _(
     758                          '%s items selected from bill [%s]\n'
     759                          '\n'
     760                          'Do you want to only remove the selected items\n'
     761                          'from the bill ("unbill" them) or do you want\n'
     762                          'to delete them entirely from the patient ?\n'
     763                          '\n'
     764                          'Note that neither action is reversible.'
     765                  ) % (
     766                          len(items2remove),
     767                          bill['invoice_id']
     768                  ),
     769                  button_defs = [
     770                          {'label': _('"Unbill"'), 'tooltip': _('Only "unbill" items (remove from bill but do not delete from patient).'), 'default': True},
     771                          {'label': _('Delete'), 'tooltip': _('Completely delete items from the patient.')}
     772                  ],
     773                  show_checkbox = True,
     774                  checkbox_msg = _('Also remove invoice PDF'),
     775                  checkbox_tooltip = _('Also remove the invoice PDF from the document archive (because it will not correspond to the bill anymore).')
     776          )
     777          button_pressed = dlg.ShowModal()
     778          delete_invoice = dlg.checkbox_is_checked()
     779          dlg.Destroy()
     780
     781          if button_pressed == wx.ID_CANCEL:
     782                  return False
     783
     784          # remember this because unlinking/deleting the items
     785          # will remove the patient PK from the bill
     786          pk_patient = bill['pk_patient']
     787
     788          for item in items2remove:
     789                  item['pk_bill'] = None
     790                  item.save()
     791                  if button_pressed == wx.ID_NO:
     792                          gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item'])
     793
     794          if delete_invoice:
     795                  if bill['pk_doc'] is not None:
     796                          gmDocuments.delete_document (
     797                                  document_id = bill['pk_doc'],
     798                                  encounter_id = gmPerson.cPatient(aPK_obj = pk_patient).emr.active_encounter['pk_encounter']
     799                          )
     800
     801          # delete bill, too, if empty
     802          if len(bill.bill_items) == 0:
     803                  gmBilling.delete_bill(pk_bill = bill['pk_bill'])
     804
     805          return True
     806
     807  #----------------------------------------------------------------
     808 -def manage_bills(parent=None, patient=None):
     809
     810          if parent is None:
     811                  parent = wx.GetApp().GetTopWindow()
     812
     813          #------------------------------------------------------------
     814          def show_pdf(bill):
     815                  if bill is None:
     816                          return False
     817
     818                  # find invoice
     819                  invoice = bill.invoice
     820                  if invoice is not None:
     821                          success, msg = invoice.parts[-1].display_via_mime()
     822                          if not success:
     823                                  gmGuiHelpers.gm_show_error(aMessage = msg, aTitle = _('Displaying invoice'))
     824                          return False
     825
     826                  # create it ?
     827                  create_it = gmGuiHelpers.gm_show_question (
     828                          title = _('Displaying invoice'),
     829                          question = _(
     830                                  'Cannot find an existing\n'
     831                                  'invoice PDF for this bill.\n'
     832                                  '\n'
     833                                  'Do you want to create one ?'
     834                          ),
     835                  )
     836                  if not create_it:
     837                          return False
     838
     839                  # prepare invoicing
     840                  if not bill.set_missing_address_from_default():
     841                          gmGuiHelpers.gm_show_warning (
     842                                  aTitle = _('Creating invoice'),
     843                                  aMessage = _(
     844                                          'There is no pre-configured billing address.\n'
     845                                          '\n'
     846                                          'Select the address you want to send the bill to.'
     847                                  )
     848                          )
     849                          edit_bill(parent = parent, bill = bill, single_entry = True)
     850                          if bill['pk_receiver_address'] is None:
     851                                  return False
     852                  if bill['close_date'] is None:
     853                          bill['close_date'] = gmDateTime.pydt_now_here()
     854                          bill.save()
     855
     856                  return create_invoice_from_bill(parent = parent, bill = bill, print_it = True, keep_a_copy = True)
     857          #------------------------------------------------------------
     858          def edit(bill):
     859                  return edit_bill(parent = parent, bill = bill, single_entry = True)
     860          #------------------------------------------------------------
     861          def delete(bill):
     862                  return delete_bill(parent = parent, bill = bill)
     863          #------------------------------------------------------------
     864          def remove_items(bill):
     865                  return remove_items_from_bill(parent = parent, bill = bill)
     866          #------------------------------------------------------------
     867          def get_tooltip(item):
     868                  if item is None:
     869                          return None
     870                  return item.format()
     871          #------------------------------------------------------------
     872          def refresh(lctrl):
     873                  if patient is None:
     874                          bills = gmBilling.get_bills()
     875                  else:
     876                          bills = gmBilling.get_bills(pk_patient = patient.ID)
     877                  items = []
     878                  for b in bills:
     879                          if b['close_date'] is None:
     880                                  close_date = _('<open>')
     881                          else:
     882                                  close_date = gmDateTime.pydt_strftime(b['close_date'], '%Y %b %d')
     883                          if b['total_amount'] is None:
     884                                  amount = _('no items on bill')
     885                          else:
     886                                  amount = gmTools.bool2subst (
     887                                          b['apply_vat'],
     888                                          _('%(currency)s%(total_amount_with_vat)s (with %(percent_vat)s%% VAT)') % b,
     889                                          '%(currency)s%(total_amount)s' % b,
     890                                          _('without VAT: %(currency)s%(total_amount)s / with %(percent_vat)s%% VAT: %(currency)s%(total_amount_with_vat)s') % b
     891                                  )
     892                          items.append ([
     893                                  close_date,
     894                                  b['invoice_id'],
     895                                  amount,
     896                                  gmTools.coalesce(b['comment'], '')
     897                          ])
     898                  lctrl.set_string_items(items)
     899                  lctrl.set_data(bills)
     900          #------------------------------------------------------------
     901          return gmListWidgets.get_choices_from_list (
     902                  parent = parent,
     903                  caption = _('Showing bills.'),
     904                  columns = [_('Close date'), _('Invoice ID'), _('Value'), _('Comment')],
     905                  single_selection = True,
     906                  edit_callback = edit,
     907                  delete_callback = delete,
     908                  refresh_callback = refresh,
     909                  middle_extra_button = (
     910                          'PDF',
     911                          _('Create if necessary, and show the corresponding invoice PDF'),
     912                          show_pdf
     913                  ),
     914                  right_extra_button = (
     915                          _('Unbill'),
     916                          _('Select and remove items from a bill.'),
     917                          remove_items
     918                  ),
     919                  list_tooltip_callback = get_tooltip
     920          )
     921
     922  #----------------------------------------------------------------
     923  from Gnumed.wxGladeWidgets import wxgBillEAPnl
     924
     925 -class cBillEAPnl(wxgBillEAPnl.wxgBillEAPnl, gmEditArea.cGenericEditAreaMixin):
     926
     927 -        def __init__(self, *args, **kwargs):
     928
     929                  try:
     930                          data = kwargs['bill']
     931                          del kwargs['bill']
     932                  except KeyError:
     933                          data = None
     934
     935                  wxgBillEAPnl.wxgBillEAPnl.__init__(self, *args, **kwargs)
     936                  gmEditArea.cGenericEditAreaMixin.__init__(self)
     937
     938                  self.mode = 'new'
     939                  self.data = data
     940                  if data is not None:
     941                          self.mode = 'edit'
     942
     943                  self._3state2bool = {
     944                          wx.CHK_UNCHECKED: False,
     945                          wx.CHK_CHECKED: True,
     946                          wx.CHK_UNDETERMINED: None
     947                  }
     948                  self.bool_to_3state = {
     949                          False: wx.CHK_UNCHECKED,
     950                          True: wx.CHK_CHECKED,
     951                          None: wx.CHK_UNDETERMINED
     952                  }
     953
     954  #               self.__init_ui()
     955          #----------------------------------------------------------------
     956  #       def __init_ui(self):
     957          #----------------------------------------------------------------
     958          # generic Edit Area mixin API
     959          #----------------------------------------------------------------
     960 -        def _valid_for_save(self):
     961                  validity = True
     962
     963                  # flag but do not count as wrong
     964                  if not self._PRW_close_date.is_valid_timestamp(empty_is_valid = False):
     965                          self._PRW_close_date.SetFocus()
     966
     967                  # flag but do not count as wrong
     968                  if self._CHBOX_vat_applies.ThreeStateValue == wx.CHK_UNDETERMINED:
     969                          self._CHBOX_vat_applies.SetFocus()
     970                          self._CHBOX_vat_applies.SetBackgroundColour('yellow')
     971
     972                  return validity
     973          #----------------------------------------------------------------
     974 -        def _save_as_new(self):
     975                  # not intended to be used
     976                  return False
     977          #----------------------------------------------------------------
     978 -        def _save_as_update(self):
     979                  self.data['close_date'] = self._PRW_close_date.GetData()
     980                  self.data['apply_vat'] = self._3state2bool[self._CHBOX_vat_applies.ThreeStateValue]
     981                  self.data['comment'] = self._TCTRL_comment.GetValue()
     982                  self.data.save()
     983                  return True
     984          #----------------------------------------------------------------
     985 -        def _refresh_as_new(self):
     986                  pass # not used
     987          #----------------------------------------------------------------
     988 -        def _refresh_as_new_from_existing(self):
     989                  self._refresh_as_new()
     990          #----------------------------------------------------------------
     991 -        def _refresh_from_existing(self):
     992                  self._TCTRL_invoice_id.SetValue(self.data['invoice_id'])
     993                  self._PRW_close_date.SetText(data = self.data['close_date'])
     994
     995                  self.data.set_missing_address_from_default()
     996                  if self.data['pk_receiver_address'] is None:
     997                          self._TCTRL_address.SetValue('')
     998                  else:
     999                          adr = self.data.address
    1000                          self._TCTRL_address.SetValue(adr.format(single_line = True, show_type = False))
    1001
    1002                  self._TCTRL_value.SetValue('%(currency)s%(total_amount)s' % self.data)
    1003                  self._CHBOX_vat_applies.ThreeStateValue = self.bool_to_3state[self.data['apply_vat']]
    1004                  self._CHBOX_vat_applies.SetLabel(_('&VAT applies (%s%%)') % self.data['percent_vat'])
    1005                  if self.data['apply_vat'] is True:
    1006                          tmp = '%s %%(currency)s%%(total_vat)s %s %s %%(currency)s%%(total_amount_with_vat)s' % (
    1007                                  gmTools.u_corresponds_to,
    1008                                  gmTools.u_arrow2right,
    1009                                  gmTools.u_sum,
    1010                          )
    1011                          self._TCTRL_value_with_vat.SetValue(tmp % self.data)
    1012                  elif self.data['apply_vat'] is None:
    1013                          self._TCTRL_value_with_vat.SetValue('?')
    1014                  else:
    1015                          self._TCTRL_value_with_vat.SetValue('')
    1016
    1017                  self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
    1018
    1019                  self._PRW_close_date.SetFocus()
    1020          #----------------------------------------------------------------
    1021          # event handling
    1022          #----------------------------------------------------------------
    1023 -        def _on_vat_applies_box_checked(self, event):
    1024                  if self._CHBOX_vat_applies.ThreeStateValue == wx.CHK_CHECKED:
    1025                          tmp = '%s %%(currency)s%%(total_vat)s %s %s %%(currency)s%%(total_amount_with_vat)s' % (
    1026                                  gmTools.u_corresponds_to,
    1027                                  gmTools.u_arrow2right,
    1028                                  gmTools.u_sum,
    1029                          )
    1030                          self._TCTRL_value_with_vat.SetValue(tmp % self.data)
    1031                          return
    1032                  if self._CHBOX_vat_applies.ThreeStateValue == wx.CHK_UNDETERMINED:
    1033                          self._TCTRL_value_with_vat.SetValue('?')
    1034                          return
    1035                  self._TCTRL_value_with_vat.SetValue('')
    1036          #----------------------------------------------------------------
    1037 -        def _on_select_address_button_pressed(self, event):
    1038                  adr = gmPersonContactWidgets.select_address (
    1039                          missing = _('billing'),
    1040                          person = gmPerson.cPerson(aPK_obj = self.data['pk_patient'])
    1041                  )
    1042                  if adr is None:
    1043                          gmGuiHelpers.gm_show_info (
    1044                                  aTitle = _('Selecting address'),
    1045                                  aMessage = _('GNUmed does not know any addresses for this patient.')
    1046                          )
    1047                          return
    1048                  self.data['pk_receiver_address'] = adr['pk_lnk_person_org_address']
    1049                  self.data.save()
    1050                  self._TCTRL_address.SetValue(adr.format(single_line = True, show_type = False))
    1051
    1052  #================================================================
    1053  # per-patient bill items related widgets
    1054  #----------------------------------------------------------------
    1055 -def edit_bill_item(parent=None, bill_item=None, single_entry=False):
    1056
    1057          if bill_item is not None:
    1058                  if bill_item.is_in_use:
    1059                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit already invoiced bill item.'), beep = True)
    1060                          return False
    1061
    1062          ea = cBillItemEAPnl(parent, -1)
    1063          ea.data = bill_item
    1064          ea.mode = gmTools.coalesce(bill_item, 'new', 'edit')
    1065          dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea, single_entry = single_entry)
    1066          dlg.SetTitle(gmTools.coalesce(bill_item, _('Adding new bill item'), _('Editing bill item')))
    1067          if dlg.ShowModal() == wx.ID_OK:
    1068                  dlg.Destroy()
    1069                  return True
    1070          dlg.Destroy()
    1071          return False
    1072  #----------------------------------------------------------------
    1073 -def manage_bill_items(parent=None, pk_patient=None):
    1074
    1075          if parent is None:
    1076                  parent = wx.GetApp().GetTopWindow()
    1077          #------------------------------------------------------------
    1078          def edit(item=None):
    1079                  return edit_bill_item(parent = parent, bill_item = item, single_entry = (item is not None))
    1080          #------------------------------------------------------------
    1081          def delete(item):
    1082                  if item.is_in_use is not None:
    1083                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete already invoiced bill items.'), beep = True)
    1084                          return False
    1085                  gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item'])
    1086                  return True
    1087          #------------------------------------------------------------
    1088          def get_tooltip(item):
    1089                  if item is None:
    1090                          return None
    1091                  return item.format()
    1092          #------------------------------------------------------------
    1093          def refresh(lctrl):
    1094                  b_items = gmBilling.get_bill_items(pk_patient = pk_patient)
    1095                  items = [ [
    1096                          gmDateTime.pydt_strftime(b['date_to_bill'], '%Y %b %d', accuracy = gmDateTime.acc_days),
    1097                          b['unit_count'],
    1098                          '%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], '', ' - %s')),
    1099                          b['currency'],
    1100                          '%s (%s %s %s%s%s)' % (
    1101                                  b['total_amount'],
    1102                                  b['unit_count'],
    1103                                  gmTools.u_multiply,
    1104                                  b['net_amount_per_unit'],
    1105                                  gmTools.u_multiply,
    1106                                  b['amount_multiplier']
    1107                          ),
    1108                          '%s (%s%%)' % (
    1109                                  b['vat'],
    1110                                  b['vat_multiplier'] * 100
    1111                          ),
    1112                          '%s (%s)' % (b['catalog_short'], b['catalog_version']),
    1113                          b['pk_bill_item']
    1114                  ] for b in b_items ]
    1115                  lctrl.set_string_items(items)
    1116                  lctrl.set_data(b_items)
    1117          #------------------------------------------------------------
    1118          gmListWidgets.get_choices_from_list (
    1119                  parent = parent,
    1120                  #msg = msg,
    1121                  caption = _('Showing bill items.'),
    1122                  columns = [_('Date'), _('Count'), _('Description'), _('$__replace_by_your_currency_symbol')[:-len('__replace_by_your_currency_symbol')], _('Value'), _('VAT'), _('Catalog'), '#'],
    1123                  single_selection = True,
    1124                  new_callback = edit,
    1125                  edit_callback = edit,
    1126                  delete_callback = delete,
    1127                  refresh_callback = refresh,
    1128                  list_tooltip_callback = get_tooltip
    1129          )
    1130
    1131  #------------------------------------------------------------
    1132 -class cPersonBillItemsManagerPnl(gmListWidgets.cGenericListManagerPnl):
    1133          """A list for managing a patient's bill items.
    1134
    1135          Does NOT act on/listen to the current patient.
    1136          """
    1137 -        def __init__(self, *args, **kwargs):
    1138
    1139                  try:
    1140                          self.__identity = kwargs['identity']
    1141                          del kwargs['identity']
    1142                  except KeyError:
    1143                          self.__identity = None
    1144
    1145                  gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs)
    1146
    1147                  self.refresh_callback = self.refresh
    1148                  self.new_callback = self._add_item
    1149                  self.edit_callback = self._edit_item
    1150                  self.delete_callback = self._del_item
    1151
    1152                  self.__show_non_invoiced_only = True
    1153
    1154                  self.__init_ui()
    1155                  self.refresh()
    1156          #--------------------------------------------------------
    1157          # external API
    1158          #--------------------------------------------------------
    1159 -        def refresh(self, *args, **kwargs):
    1160                  if self.__identity is None:
    1161                          self._LCTRL_items.set_string_items()
    1162                          return
    1163
    1164                  b_items = gmBilling.get_bill_items(pk_patient = self.__identity.ID, non_invoiced_only = self.__show_non_invoiced_only)
    1165                  items = [ [
    1166                          gmDateTime.pydt_strftime(b['date_to_bill'], '%Y %b %d', accuracy = gmDateTime.acc_days),
    1167                          b['unit_count'],
    1168                          '%s: %s%s' % (b['billable_code'], b['billable_description'], gmTools.coalesce(b['item_detail'], '', ' - %s')),
    1169                          b['currency'],
    1170                          b['total_amount'],
    1171                          '%s (%s%%)' % (
    1172                                  b['vat'],
    1173                                  b['vat_multiplier'] * 100
    1174                          ),
    1175                          '%s (%s)' % (b['catalog_short'], b['catalog_version']),
    1176                          '%s %s %s %s %s' % (
    1177                                  b['unit_count'],
    1178                                  gmTools.u_multiply,
    1179                                  b['net_amount_per_unit'],
    1180                                  gmTools.u_multiply,
    1181                                  b['amount_multiplier']
    1182                          ),
    1183                          gmTools.coalesce(b['pk_bill'], gmTools.u_diameter),
    1184                          b['pk_encounter_to_bill'],
    1185                          b['pk_bill_item']
    1186                  ] for b in b_items ]
    1187
    1188                  self._LCTRL_items.set_string_items(items = items)
    1189                  self._LCTRL_items.set_column_widths()
    1190                  self._LCTRL_items.set_data(data = b_items)
    1191          #--------------------------------------------------------
    1192          # internal helpers
    1193          #--------------------------------------------------------
    1194 -        def __init_ui(self):
    1195                  self._LCTRL_items.set_columns(columns = [
    1196                          _('Charge date'),
    1197                          _('Count'),
    1198                          _('Description'),
    1199                          _('$__replace_by_your_currency_symbol')[:-len('__replace_by_your_currency_symbol')],
    1200                          _('Value'),
    1201                          _('VAT'),
    1202                          _('Catalog'),
    1203                          _('Count %s Value %s Factor') % (gmTools.u_multiply, gmTools.u_multiply),
    1204                          _('Invoice'),
    1205                          _('Encounter'),
    1206                          '#'
    1207                  ])
    1208                  self._LCTRL_items.item_tooltip_callback = self._get_item_tooltip
    1209  #               self.left_extra_button = (
    1210  #                       _('Select pending'),
    1211  #                       _('Select non-invoiced (pending) items.'),
    1212  #                       self._select_pending_items
    1213  #               )
    1214                  self.left_extra_button = (
    1215                          _('Invoice selected items'),
    1216                          _('Create invoice from selected items.'),
    1217                          self._invoice_selected_items
    1218                  )
    1219                  self.middle_extra_button = (
    1220                          _('Bills'),
    1221                          _('Browse bills of this patient.'),
    1222                          self._browse_bills
    1223                  )
    1224                  self.right_extra_button = (
    1225                          _('Billables'),
    1226                          _('Browse list of billables.'),
    1227                          self._browse_billables
    1228                  )
    1229          #--------------------------------------------------------
    1230 -        def _add_item(self):
    1231                  return edit_bill_item(parent = self, bill_item = None, single_entry = False)
    1232          #--------------------------------------------------------
    1233 -        def _edit_item(self, bill_item):
    1234                  return edit_bill_item(parent = self, bill_item = bill_item, single_entry = True)
    1235          #--------------------------------------------------------
    1236 -        def _del_item(self, item):
    1237                  if item['pk_bill'] is not None:
    1238                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete already invoiced bill items.'), beep = True)
    1239                          return False
    1240                  go_ahead = gmGuiHelpers.gm_show_question (
    1241                          _(      'Do you really want to delete this\n'
    1242                                  'bill item from the patient ?'),
    1243                          _('Deleting bill item')
    1244                  )
    1245                  if not go_ahead:
    1246                          return False
    1247                  gmBilling.delete_bill_item(pk_bill_item = item['pk_bill_item'])
    1248                  return True
    1249          #--------------------------------------------------------
    1250 -        def _get_item_tooltip(self, item):
    1251                  if item is None:
    1252                          return None
    1253                  return item.format()
    1254          #--------------------------------------------------------
    1255 -        def _select_pending_items(self, item):
    1256                  pass
    1257          #--------------------------------------------------------
    1258 -        def _invoice_selected_items(self, item):
    1259                  bill_items = self._LCTRL_items.get_selected_item_data()
    1260                  bill = create_bill_from_items(bill_items)
    1261                  if bill is None:
    1262                          return
    1263                  if bill['pk_receiver_address'] is None:
    1264                          gmGuiHelpers.gm_show_error (
    1265                                  aMessage = _(
    1266                                          'Cannot create invoice.\n'
    1267                                          '\n'
    1268                                          'No receiver address selected.'
    1269                                  ),
    1270                                  aTitle = _('Creating invoice')
    1271                          )
    1272                          return
    1273                  if bill['close_date'] is None:
    1274                          bill['close_date'] = gmDateTime.pydt_now_here()
    1275                          bill.save()
    1276                  create_invoice_from_bill(parent = self, bill = bill, print_it = True, keep_a_copy = True)
    1277          #--------------------------------------------------------
    1278 -        def _browse_billables(self, item):
    1279                  manage_billables(parent = self)
    1280                  return False
    1281          #--------------------------------------------------------
    1282 -        def _browse_bills(self, item):
    1283                  manage_bills(parent = self, patient = self.__identity)
    1284          #--------------------------------------------------------
    1285          # properties
    1286          #--------------------------------------------------------
    1287 -        def _get_identity(self):
    1288                  return self.__identity
    1289
    1290 -        def _set_identity(self, identity):
    1291                  self.__identity = identity
    1292                  self.refresh()
    1293
    1294          identity = property(_get_identity, _set_identity)
    1295          #--------------------------------------------------------
    1296 -        def _get_show_non_invoiced_only(self):
    1297                  return self.__show_non_invoiced_only
    1298
    1299 -        def _set_show_non_invoiced_only(self, value):
    1300                  self.__show_non_invoiced_only = value
    1301                  self.refresh()
    1302
    1303          show_non_invoiced_only = property(_get_show_non_invoiced_only, _set_show_non_invoiced_only)
    1304
    1305  #------------------------------------------------------------
    1306  from Gnumed.wxGladeWidgets import wxgBillItemEAPnl
    1307
    1308 -class cBillItemEAPnl(wxgBillItemEAPnl.wxgBillItemEAPnl, gmEditArea.cGenericEditAreaMixin):
    1309
    1310 -        def __init__(self, *args, **kwargs):
    1311
    1312                  try:
    1313                          data = kwargs['bill_item']
    1314                          del kwargs['bill_item']
    1315                  except KeyError:
    1316                          data = None
    1317
    1318                  wxgBillItemEAPnl.wxgBillItemEAPnl.__init__(self, *args, **kwargs)
    1319                  gmEditArea.cGenericEditAreaMixin.__init__(self)
    1320
    1321                  self.mode = 'new'
    1322                  self.data = data
    1323                  if data is not None:
    1324                          self.mode = 'edit'
    1325
    1326                  self.__init_ui()
    1327          #----------------------------------------------------------------
    1328 -        def __init_ui(self):
    1329                  self._PRW_encounter.set_context(context = 'patient', val = gmPerson.gmCurrentPatient().ID)
    1330                  self._PRW_billable.add_callback_on_selection(self._on_billable_selected)
    1331                  self._PRW_billable.add_callback_on_modified(self._on_billable_modified)
    1332          #----------------------------------------------------------------
    1333          # generic Edit Area mixin API
    1334          #----------------------------------------------------------------
    1335 -        def _valid_for_save(self):
    1336
    1337                  validity = True
    1338
    1339                  if self._TCTRL_factor.GetValue().strip() == '':
    1340                          validity = False
    1341                          self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = False)
    1342                          self._TCTRL_factor.SetFocus()
    1343                  else:
    1344                          converted, factor = gmTools.input2decimal(self._TCTRL_factor.GetValue())
    1345                          if not converted:
    1346                                  validity = False
    1347                                  self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = False)
    1348                                  self._TCTRL_factor.SetFocus()
    1349                          else:
    1350                                  self.display_tctrl_as_valid(tctrl = self._TCTRL_factor, valid = True)
    1351
    1352                  if self._TCTRL_amount.GetValue().strip() == '':
    1353                          validity = False
    1354                          self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False)
    1355                          self._TCTRL_amount.SetFocus()
    1356                  else:
    1357                          converted, factor = gmTools.input2decimal(self._TCTRL_amount.GetValue())
    1358                          if not converted:
    1359                                  validity = False
    1360                                  self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = False)
    1361                                  self._TCTRL_amount.SetFocus()
    1362                          else:
    1363                                  self.display_tctrl_as_valid(tctrl = self._TCTRL_amount, valid = True)
    1364
    1365                  if self._TCTRL_count.GetValue().strip() == '':
    1366                          validity = False
    1367                          self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = False)
    1368                          self._TCTRL_count.SetFocus()
    1369                  else:
    1370                          converted, factor = gmTools.input2decimal(self._TCTRL_count.GetValue())
    1371                          if not converted:
    1372                                  validity = False
    1373                                  self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = False)
    1374                                  self._TCTRL_count.SetFocus()
    1375                          else:
    1376                                  self.display_tctrl_as_valid(tctrl = self._TCTRL_count, valid = True)
    1377
    1378                  if self._PRW_date.is_valid_timestamp(empty_is_valid = True):
    1379                          self._PRW_date.display_as_valid(True)
    1380                  else:
    1381                          validity = False
    1382                          self._PRW_date.display_as_valid(False)
    1383                          self._PRW_date.SetFocus()
    1384
    1385                  if self._PRW_encounter.GetData() is None:
    1386                          validity = False
    1387                          self._PRW_encounter.display_as_valid(False)
    1388                          self._PRW_encounter.SetFocus()
    1389                  else:
    1390                          self._PRW_encounter.display_as_valid(True)
    1391
    1392                  if self._PRW_billable.GetData() is None:
    1393                          validity = False
    1394                          self._PRW_billable.display_as_valid(False)
    1395                          self._PRW_billable.SetFocus()
    1396                  else:
    1397                          self._PRW_billable.display_as_valid(True)
    1398
    1399                  return validity
    1400          #----------------------------------------------------------------
    1401 -        def _save_as_new(self):
    1402                  data = gmBilling.create_bill_item (
    1403                          pk_encounter = self._PRW_encounter.GetData(),
    1404                          pk_billable = self._PRW_billable.GetData(),
    1405                          pk_staff = gmStaff.gmCurrentProvider()['pk_staff']              # should be settable !
    1406                  )
    1407                  data['raw_date_to_bill'] = self._PRW_date.GetData()
    1408                  converted, data['unit_count'] = gmTools.input2decimal(self._TCTRL_count.GetValue())
    1409                  converted, data['net_amount_per_unit'] = gmTools.input2decimal(self._TCTRL_amount.GetValue())
    1410                  converted, data['amount_multiplier'] = gmTools.input2decimal(self._TCTRL_factor.GetValue())
    1411                  data['item_detail'] = self._TCTRL_comment.GetValue().strip()
    1412                  data.save()
    1413
    1414                  self.data = data
    1415                  return True
    1416          #----------------------------------------------------------------
    1417 -        def _save_as_update(self):
    1418                  self.data['pk_encounter_to_bill'] = self._PRW_encounter.GetData()
    1419                  self.data['raw_date_to_bill'] = self._PRW_date.GetData()
    1420                  converted, self.data['unit_count'] = gmTools.input2decimal(self._TCTRL_count.GetValue())
    1421                  converted, self.data['net_amount_per_unit'] = gmTools.input2decimal(self._TCTRL_amount.GetValue())
    1422                  converted, self.data['amount_multiplier'] = gmTools.input2decimal(self._TCTRL_factor.GetValue())
    1423                  self.data['item_detail'] = self._TCTRL_comment.GetValue().strip()
    1424                  return self.data.save()
    1425          #----------------------------------------------------------------
    1426 -        def _refresh_as_new(self):
    1427                  self._PRW_billable.SetText()
    1428                  self._PRW_encounter.set_from_instance(gmPerson.gmCurrentPatient().emr.active_encounter)
    1429                  self._PRW_date.SetData()
    1430                  self._TCTRL_count.SetValue('1')
    1431                  self._TCTRL_amount.SetValue('')
    1432                  self._LBL_currency.SetLabel(gmTools.u_euro)
    1433                  self._TCTRL_factor.SetValue('1')
    1434                  self._TCTRL_comment.SetValue('')
    1435
    1436                  self._PRW_billable.Enable()
    1437                  self._PRW_billable.SetFocus()
    1438          #----------------------------------------------------------------
    1439 -        def _refresh_as_new_from_existing(self):
    1440                  self._PRW_billable.SetText()
    1441                  self._TCTRL_count.SetValue('1')
    1442                  self._TCTRL_amount.SetValue('')
    1443                  self._TCTRL_comment.SetValue('')
    1444
    1445                  self._PRW_billable.Enable()
    1446                  self._PRW_billable.SetFocus()
    1447          #----------------------------------------------------------------
    1448 -        def _refresh_from_existing(self):
    1449                  self._PRW_billable.set_from_pk(self.data['pk_billable'])
    1450                  self._PRW_encounter.SetData(self.data['pk_encounter_to_bill'])
    1451                  self._PRW_date.SetData(data = self.data['raw_date_to_bill'])
    1452                  self._TCTRL_count.SetValue('%s' % self.data['unit_count'])
    1453                  self._TCTRL_amount.SetValue('%s' % self.data['net_amount_per_unit'])
    1454                  self._LBL_currency.SetLabel(self.data['currency'])
    1455                  self._TCTRL_factor.SetValue('%s' % self.data['amount_multiplier'])
    1456                  self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['item_detail'], ''))
    1457
    1458                  self._PRW_billable.Disable()
    1459                  self._PRW_date.SetFocus()
    1460          #----------------------------------------------------------------
    1461 -        def _on_billable_selected(self, item):
    1462                  if item is None:
    1463                          return
    1464                  if self._TCTRL_amount.GetValue().strip() != '':
    1465                          return
    1466                  val = '%s' % self._PRW_billable.GetData(as_instance = True)['raw_amount']
    1467                  wx.CallAfter(self._TCTRL_amount.SetValue, val)
    1468          #----------------------------------------------------------------
    1469 -        def _on_billable_modified(self):
    1470                  if self._PRW_billable.GetData() is None:
    1471                          wx.CallAfter(self._TCTRL_amount.SetValue, '')
    1472
    1473  #============================================================
    1474  # a plugin for billing
    1475  #------------------------------------------------------------
    1476  from Gnumed.wxGladeWidgets import wxgBillingPluginPnl
    1477
    1478 -class cBillingPluginPnl(wxgBillingPluginPnl.wxgBillingPluginPnl, gmRegetMixin.cRegetOnPaintMixin):
    1479 -        def __init__(self, *args, **kwargs):
    1480
    1481                  wxgBillingPluginPnl.wxgBillingPluginPnl.__init__(self, *args, **kwargs)
    1482                  gmRegetMixin.cRegetOnPaintMixin.__init__(self)
    1483                  self.__register_interests()
    1484          #-----------------------------------------------------
    1485 -        def __reset_ui(self):
    1486                  self._PNL_bill_items.identity = None
    1487                  self._CHBOX_show_non_invoiced_only.SetValue(1)
    1488                  self._PRW_billable.SetText('', None)
    1489                  self._TCTRL_factor.SetValue('1.0')
    1490                  self._TCTRL_factor.Disable()
    1491                  self._TCTRL_details.SetValue('')
    1492                  self._TCTRL_details.Disable()
    1493          #-----------------------------------------------------
    1494          # event handling
    1495          #-----------------------------------------------------
    1496 -        def __register_interests(self):
    1497                  gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
    1498                  gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
    1499
    1500                  gmDispatcher.connect(signal = 'bill.bill_item_mod_db', receiver = self._on_bill_item_modified)
    1501
    1502                  self._PRW_billable.add_callback_on_selection(self._on_billable_selected_in_prw)
    1503          #-----------------------------------------------------
    1504 -        def _on_pre_patient_unselection(self):
    1505                  self.__reset_ui()
    1506          #-----------------------------------------------------
    1507 -        def _on_post_patient_selection(self):
    1508                  self._schedule_data_reget()
    1509          #-----------------------------------------------------
    1510 -        def _on_bill_item_modified(self):
    1511                  self._schedule_data_reget()
    1512          #-----------------------------------------------------
    1513 -        def _on_non_invoiced_only_checkbox_toggled(self, event):
    1514                  self._PNL_bill_items.show_non_invoiced_only = self._CHBOX_show_non_invoiced_only.GetValue()
    1515          #--------------------------------------------------------
    1516 -        def _on_insert_bill_item_button_pressed(self, event):
    1517                  if self._PRW_billable.GetData() is None:
    1518                          gmGuiHelpers.gm_show_warning (
    1519                                  _('No billable item selected.\n\nCannot insert bill item.'),
    1520                                  _('Inserting bill item')
    1521                          )
    1522                          return False
    1523                  val = self._TCTRL_factor.GetValue().strip()
    1524                  if val == '':
    1525                          factor = 1.0
    1526                  else:
    1527                          converted, factor = gmTools.input2decimal(val)
    1528                          if not converted:
    1529                                  gmGuiHelpers.gm_show_warning (
    1530                                          _('"Factor" must be a number\n\nCannot insert bill item.'),
    1531                                          _('Inserting bill item')
    1532                                  )
    1533                                  return False
    1534                  bill_item = gmBilling.create_bill_item (
    1535                          pk_encounter = gmPerson.gmCurrentPatient().emr.active_encounter['pk_encounter'],
    1536                          pk_billable = self._PRW_billable.GetData(),
    1537                          pk_staff = gmStaff.gmCurrentProvider()['pk_staff']
    1538                  )
    1539                  bill_item['amount_multiplier'] = factor
    1540                  bill_item['item_detail'] = self._TCTRL_details.GetValue()
    1541                  bill_item.save()
    1542
    1543                  self._TCTRL_details.SetValue('')
    1544
    1545                  return True
    1546          #--------------------------------------------------------
    1547 -        def _on_billable_selected_in_prw(self, billable):
    1548                  if billable is None:
    1549                          self._TCTRL_factor.Disable()
    1550                          self._TCTRL_details.Disable()
    1551                          self._BTN_insert_item.Disable()
    1552                  else:
    1553                          self._TCTRL_factor.Enable()
    1554                          self._TCTRL_details.Enable()
    1555                          self._BTN_insert_item.Enable()
    1556          #-----------------------------------------------------
    1557          # reget-on-paint mixin API
    1558          #-----------------------------------------------------
    1559 -        def _populate_with_data(self):
    1560                  self._PNL_bill_items.identity = gmPerson.gmCurrentPatient()
    1561                  return True
    1562  #============================================================
    1563  # main
    1564  #------------------------------------------------------------
    1565  if __name__ == '__main__':
    1566
    1567          if len(sys.argv) < 2:
    1568                  sys.exit()
    1569
    1570          if sys.argv[1] != 'test':
    1571                  sys.exit()
    1572
    1573          from Gnumed.pycommon import gmI18N
    1574          gmI18N.activate_locale()
    1575          gmI18N.install_domain(domain = 'gnumed')
    1576
    1577          #----------------------------------------
    1578          app = wx.PyWidgetTester(size = (600, 600))
    1579          #app.SetWidget(cXxxPhraseWheel, -1)
    1580          app.MainLoop()
    1581