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