| Home | Trees | Indices | Help |
|
|---|
|
|
1 # -*- coding: utf8 -*-
2 """Billing code.
3
4 Copyright: authors
5 """
6 #============================================================
7 __author__ = "Nico Latzer <nl@mnet-online.de>, Karsten Hilbert <Karsten.Hilbert@gmx.net>"
8 __license__ = 'GPL v2 or later (details at http://www.gnu.org)'
9
10 import sys
11 import logging
12
13
14 if __name__ == '__main__':
15 sys.path.insert(0, '../../')
16 from Gnumed.pycommon import gmPG2
17 from Gnumed.pycommon import gmBusinessDBObject
18 from Gnumed.pycommon import gmTools
19 from Gnumed.pycommon import gmDateTime
20 from Gnumed.business import gmDemographicRecord
21 from Gnumed.business import gmDocuments
22
23 _log = logging.getLogger('gm.bill')
24
25 INVOICE_DOCUMENT_TYPE = u'invoice'
26 #============================================================
27 # billables
28 #------------------------------------------------------------
29 _SQL_get_billable_fields = u"SELECT * FROM ref.v_billables WHERE %s"
30
32 """Items which can be billed to patients."""
33
34 _cmd_fetch_payload = _SQL_get_billable_fields % u"""pk_billable = %s"""
35 _cmds_store_payload = [
36 u"""UPDATE ref.billable SET
37 code = %(billable_code)s,
38 term = %(billable_description)s,
39 amount = %(raw_amount)s,
40 currency = %(currency)s,
41 vat_multiplier = %(vat_multiplier)s
42 WHERE
43 pk = %(pk_billabs)s
44 AND
45 xmin = %(xmin_billable)s
46 RETURNING
47 xmin AS xmin_billable
48 """]
49
50 _updatable_fields = [
51 'billable_description',
52 'raw_amount',
53 'vat_multiplier',
54 ]
55 #--------------------------------------------------------
57 txt = u'%s [#%s]\n\n' % (
58 gmTools.bool2subst (
59 self._payload[self._idx['active']],
60 _('Active billable item'),
61 _('Inactive billable item')
62 ),
63 self._payload[self._idx['pk_billable']]
64 )
65 txt += u' %s: %s\n' % (
66 self._payload[self._idx['billable_code']],
67 self._payload[self._idx['billable_description']]
68 )
69 txt += _(' %(curr)s%(raw_val)s + %(perc_vat)s%% VAT = %(curr)s%(val_w_vat)s\n') % {
70 'curr': self._payload[self._idx['currency']],
71 'raw_val': self._payload[self._idx['raw_amount']],
72 'perc_vat': self._payload[self._idx['vat_multiplier']] * 100,
73 'val_w_vat': self._payload[self._idx['amount_with_vat']]
74 }
75 txt += u' %s %s%s (%s)' % (
76 self._payload[self._idx['catalog_short']],
77 self._payload[self._idx['catalog_version']],
78 gmTools.coalesce(self._payload[self._idx['catalog_language']], u'', ' - %s'),
79 self._payload[self._idx['catalog_long']]
80 )
81 txt += gmTools.coalesce(self._payload[self._idx['comment']], u'', u'\n %s')
82
83 return txt
84 #--------------------------------------------------------
86 cmd = u'SELECT EXISTS(SELECT 1 FROM bill.bill_item WHERE fk_billable = %(pk)s LIMIT 1)'
87 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self._payload[self._idx['pk_billable']]}}])
88 return rows[0][0]
89
90 is_in_use = property(_get_is_in_use, lambda x:x)
91 #------------------------------------------------------------
93
94 if order_by is None:
95 order_by = u' ORDER BY catalog_long, catalog_version, billable_code'
96 else:
97 order_by = u' ORDER BY %s' % order_by
98
99 if active_only:
100 where = u'active IS true'
101 else:
102 where = u'true'
103
104 cmd = (_SQL_get_billable_fields % where) + order_by
105 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
106 return [ cBillable(row = {'data': r, 'idx': idx, 'pk_field': 'pk_billable'}) for r in rows ]
107 #------------------------------------------------------------
109 cmd = u"""
110 DELETE FROM ref.billable
111 WHERE
112 pk = %(pk)s
113 AND
114 NOT EXISTS (
115 SELECT 1 FROM bill.bill_item WHERE fk_billable = %(pk)s
116 )
117 """
118 args = {'pk': pk_billable}
119 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
120 #============================================================
121 # bill items
122 #------------------------------------------------------------
123 _SQL_fetch_bill_item_fields = u"SELECT * FROM bill.v_bill_items WHERE %s"
124
126
127 _cmd_fetch_payload = _SQL_fetch_bill_item_fields % u"pk_bill_item = %s"
128 _cmds_store_payload = [
129 u"""UPDATE bill.bill_item SET
130 fk_provider = %(pk_provider)s,
131 fk_encounter = %(pk_encounter_to_bill)s,
132 date_to_bill = %(raw_date_to_bill)s,
133 description = gm.nullify_empty_string(%(item_detail)s),
134 net_amount_per_unit = %(net_amount_per_unit)s,
135 currency = gm.nullify_empty_string(%(currency)s),
136 fk_bill = %(pk_bill)s,
137 unit_count = %(unit_count)s,
138 amount_multiplier = %(amount_multiplier)s
139 WHERE
140 pk = %(pk_bill_item)s
141 AND
142 xmin = %(xmin_bill_item)s
143 RETURNING
144 xmin AS xmin_bill_item
145 """]
146
147 _updatable_fields = [
148 'pk_provider',
149 'pk_encounter_to_bill',
150 'raw_date_to_bill',
151 'item_detail',
152 'net_amount_per_unit',
153 'currency',
154 'pk_bill',
155 'unit_count',
156 'amount_multiplier'
157 ]
158 #--------------------------------------------------------
160 txt = u'%s (%s %s%s) [#%s]\n' % (
161 gmTools.bool2subst(
162 self._payload[self._idx['pk_bill']] is None,
163 _('Open item'),
164 _('Billed item'),
165 ),
166 self._payload[self._idx['catalog_short']],
167 self._payload[self._idx['catalog_version']],
168 gmTools.coalesce(self._payload[self._idx['catalog_language']], u'', ' - %s'),
169 self._payload[self._idx['pk_bill_item']]
170 )
171 txt += u' %s: %s\n' % (
172 self._payload[self._idx['billable_code']],
173 self._payload[self._idx['billable_description']]
174 )
175 txt += gmTools.coalesce (
176 self._payload[self._idx['billable_comment']],
177 u'',
178 u' (%s)\n',
179 )
180 txt += gmTools.coalesce (
181 self._payload[self._idx['item_detail']],
182 u'',
183 _(' Details: %s\n'),
184 )
185
186 txt += u'\n'
187 txt += _(' %s of units: %s\n') % (
188 gmTools.u_numero,
189 self._payload[self._idx['unit_count']]
190 )
191 txt += _(' Amount per unit: %(curr)s%(val_p_unit)s (%(cat_curr)s%(cat_val)s per catalog)\n') % {
192 'curr': self._payload[self._idx['currency']],
193 'val_p_unit': self._payload[self._idx['net_amount_per_unit']],
194 'cat_curr': self._payload[self._idx['billable_currency']],
195 'cat_val': self._payload[self._idx['billable_amount']]
196 }
197 txt += _(' Amount multiplier: %s\n') % self._payload[self._idx['amount_multiplier']]
198 txt += _(' VAT would be: %(perc_vat)s%% %(equals)s %(curr)s%(vat)s\n') % {
199 'perc_vat': self._payload[self._idx['vat_multiplier']] * 100,
200 'equals': gmTools.u_corresponds_to,
201 'curr': self._payload[self._idx['currency']],
202 'vat': self._payload[self._idx['vat']]
203 }
204
205 txt += u'\n'
206 txt += _(' Charge date: %s') % gmDateTime.pydt_strftime (
207 self._payload[self._idx['date_to_bill']],
208 '%Y %b %d',
209 accuracy = gmDateTime.acc_days
210 )
211 bill = self.bill
212 if bill is not None:
213 txt += _('\n On bill: %s') % bill['invoice_id']
214
215 return txt
216 #--------------------------------------------------------
218 return cBillable(aPK_obj = self._payload[self._idx['pk_billable']])
219
220 billable = property(_get_billable, lambda x:x)
221 #--------------------------------------------------------
223 if self._payload[self._idx['pk_bill']] is None:
224 return None
225 return cBill(aPK_obj = self._payload[self._idx['pk_bill']])
226
227 bill = property(_get_bill, lambda x:x)
228 #--------------------------------------------------------
231
232 is_in_use = property(_get_is_in_use, lambda x:x)
233 #------------------------------------------------------------
235 if non_invoiced_only:
236 cmd = _SQL_fetch_bill_item_fields % u"pk_patient = %(pat)s AND pk_bill IS NULL"
237 else:
238 cmd = _SQL_fetch_bill_item_fields % u"pk_patient = %(pat)s"
239 args = {'pat': pk_patient}
240 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
241 return [ cBillItem(row = {'data': r, 'idx': idx, 'pk_field': 'pk_bill_item'}) for r in rows ]
242 #------------------------------------------------------------
244
245 billable = cBillable(aPK_obj = pk_billable)
246 cmd = u"""
247 INSERT INTO bill.bill_item (
248 fk_provider,
249 fk_encounter,
250 net_amount_per_unit,
251 currency,
252 fk_billable
253 ) VALUES (
254 %(staff)s,
255 %(enc)s,
256 %(val)s,
257 %(curr)s,
258 %(billable)s
259 )
260 RETURNING pk"""
261 args = {
262 'staff': pk_staff,
263 'enc': pk_encounter,
264 'val': billable['raw_amount'],
265 'curr': billable['currency'],
266 'billable': pk_billable
267 }
268 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
269 return cBillItem(aPK_obj = rows[0][0])
270 #------------------------------------------------------------
272 cmd = u'DELETE FROM bill.bill_item WHERE pk = %(pk)s AND fk_bill IS NULL'
273 args = {'pk': pk_bill_item}
274 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
275
276 #============================================================
277 # bills
278 #------------------------------------------------------------
279 _SQL_get_bill_fields = u"""SELECT * FROM bill.v_bills WHERE %s"""
280
282 """Represents a bill"""
283
284 _cmd_fetch_payload = _SQL_get_bill_fields % u"pk_bill = %s"
285 _cmds_store_payload = [
286 u"""UPDATE bill.bill SET
287 invoice_id = gm.nullify_empty_string(%(invoice_id)s),
288 close_date = %(close_date)s,
289 apply_vat = %(apply_vat)s,
290 comment = gm.nullify_empty_string(%(comment)s),
291 fk_receiver_identity = %(pk_receiver_identity)s,
292 fk_receiver_address = %(pk_receiver_address)s,
293 fk_doc = %(pk_doc)s
294 WHERE
295 pk = %(pk_bill)s
296 AND
297 xmin = %(xmin_bill)s
298 RETURNING
299 pk as pk_bill,
300 xmin as xmin_bill
301 """
302 ]
303 _updatable_fields = [
304 u'invoice_id',
305 u'pk_receiver_identity',
306 u'close_date',
307 u'apply_vat',
308 u'comment',
309 u'pk_receiver_address',
310 u'pk_doc'
311 ]
312 #--------------------------------------------------------
314 txt = u'%s [#%s]\n' % (
315 gmTools.bool2subst (
316 (self._payload[self._idx['close_date']] is None),
317 _('Open bill'),
318 _('Closed bill')
319 ),
320 self._payload[self._idx['pk_bill']]
321 )
322 txt += _(' Invoice ID: %s\n') % self._payload[self._idx['invoice_id']]
323
324 if self._payload[self._idx['close_date']] is not None:
325 txt += _(' Closed: %s\n') % gmDateTime.pydt_strftime (
326 self._payload[self._idx['close_date']],
327 '%Y %b %d',
328 accuracy = gmDateTime.acc_days
329 )
330
331 if self._payload[self._idx['comment']] is not None:
332 txt += _(' Comment: %s\n') % self._payload[self._idx['comment']]
333
334 txt += _(' Bill value: %(curr)s%(val)s\n') % {
335 'curr': self._payload[self._idx['currency']],
336 'val': self._payload[self._idx['total_amount']]
337 }
338
339 if self._payload[self._idx['apply_vat']]:
340 txt += _(' VAT: %(perc_vat)s%% %(equals)s %(curr)s%(vat)s\n') % {
341 'perc_vat': self._payload[self._idx['percent_vat']],
342 'equals': gmTools.u_corresponds_to,
343 'curr': self._payload[self._idx['currency']],
344 'vat': self._payload[self._idx['total_vat']]
345 }
346 txt += _(' Value + VAT: %(curr)s%(val)s\n') % {
347 'curr': self._payload[self._idx['currency']],
348 'val': self._payload[self._idx['total_amount_with_vat']]
349 }
350 else:
351 txt += _(' VAT: does not apply\n')
352
353 if self._payload[self._idx['pk_bill_items']] is None:
354 txt += _(' Items billed: 0\n')
355 else:
356 txt += _(' Items billed: %s\n') % len(self._payload[self._idx['pk_bill_items']])
357 txt += _(' Invoice: %s\n') % (
358 gmTools.bool2subst (
359 self._payload[self._idx['pk_doc']] is None,
360 _('not available'),
361 u'#%s' % self._payload[self._idx['pk_doc']]
362 )
363 )
364 txt += _(' Patient: #%s\n') % self._payload[self._idx['pk_patient']]
365 txt += gmTools.coalesce (
366 self._payload[self._idx['pk_receiver_identity']],
367 u'',
368 _(' Receiver: #%s\n')
369 )
370 if self._payload[self._idx['pk_receiver_address']] is not None:
371 txt += u'\n '.join(gmDemographicRecord.get_patient_address(pk_patient_address = self._payload[self._idx['pk_receiver_address']]).format())
372
373 return txt
374 #--------------------------------------------------------
376 """Requires no pending changes within the bill itself."""
377 # should check for item consistency first
378 conn = gmPG2.get_connection(readonly = False)
379 for item in items:
380 item['pk_bill'] = self._payload[self._idx['pk_bill']]
381 item.save(conn = conn)
382 conn.commit()
383 self.refetch_payload() # make sure aggregates are re-filled from view
384 #--------------------------------------------------------
386 return [ cBillItem(aPK_obj = pk) for pk in self._payload[self._idx['pk_bill_items']] ]
387
388 bill_items = property(_get_bill_items, lambda x:x)
389 #--------------------------------------------------------
391 if self._payload[self._idx['pk_doc']] is None:
392 return None
393 return gmDocuments.cDocument(aPK_obj = self._payload[self._idx['pk_doc']])
394
395 invoice = property(_get_invoice, lambda x:x)
396 #--------------------------------------------------------
398 if self._payload[self._idx['pk_receiver_address']] is None:
399 return None
400 return gmDemographicRecord.get_address_from_patient_address_pk (
401 pk_patient_address = self._payload[self._idx['pk_receiver_address']]
402 )
403
404 address = property(_get_address, lambda x:x)
405 #--------------------------------------------------------
407 return gmDemographicRecord.get_patient_address_by_type (
408 pk_patient = self._payload[self._idx['pk_patient']],
409 adr_type = u'billing'
410 )
411
412 default_address = property(_get_default_address, lambda x:x)
413 #--------------------------------------------------------
415 if self._payload[self._idx['pk_receiver_address']] is not None:
416 return True
417 adr = self.default_address
418 if adr is None:
419 return False
420 self['pk_receiver_address'] = adr['pk_lnk_person_org_address']
421 return self.save_payload()
422 #------------------------------------------------------------
424
425 args = {'pat': pk_patient}
426 where_parts = [u'true']
427
428 if pk_patient is not None:
429 where_parts.append(u'pk_patient = %(pat)s')
430
431 if order_by is None:
432 order_by = u''
433 else:
434 order_by = u' ORDER BY %s' % order_by
435
436 cmd = (_SQL_get_bill_fields % u' AND '.join(where_parts)) + order_by
437 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
438 return [ cBill(row = {'data': r, 'idx': idx, 'pk_field': 'pk_bill'}) for r in rows ]
439 #------------------------------------------------------------
441
442 args = {u'inv_id': invoice_id}
443 cmd = u"""
444 INSERT INTO bill.bill (invoice_id)
445 VALUES (gm.nullify_empty_string(%(inv_id)s))
446 RETURNING pk
447 """
448 rows, idx = gmPG2.run_rw_queries(link_obj = conn, queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False)
449
450 return cBill(aPK_obj = rows[0]['pk'])
451 #------------------------------------------------------------
453 args = {'pk': pk_bill}
454 cmd = u"DELETE FROM bill.bill WHERE pk = %(pk)s"
455 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
456 return True
457 #------------------------------------------------------------
460 #------------------------------------------------------------
462 return u'GM%s / %s' % (
463 pk_patient,
464 gmDateTime.pydt_strftime (
465 gmDateTime.pydt_now_here(),
466 '%Y-%m-%d / %H%M%S'
467 )
468 )
469 #============================================================
470 # main
471 #------------------------------------------------------------
472 if __name__ == "__main__":
473
474 if len(sys.argv) < 2:
475 sys.exit()
476
477 if sys.argv[1] != 'test':
478 sys.exit()
479
480 # from Gnumed.pycommon import gmLog2
481 # from Gnumed.pycommon import gmI18N
482 # from Gnumed.business import gmPerson
483
484 # gmI18N.activate_locale()
485 ## gmDateTime.init()
486
488 bills = get_bills(pk_patient = 12)
489 first_bill = bills[0]
490 print first_bill.default_address
491
493 print "--------------"
494 me = cBillable(aPK_obj=1)
495 fields = me.get_fields()
496 for field in fields:
497 print field, ':', me[field]
498 print "updatable:", me.get_updatable_fields()
499 #me['vat']=4; me.store_payload()
500 #--------------------------------------------------
501 #test_me()
502 test_default_address()
503
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Mon Jun 10 03:56:27 2013 | http://epydoc.sourceforge.net |