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