1
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 = 'invoice'
26
27
28
29 _SQL_get_billable_fields = "SELECT * FROM ref.v_billables WHERE %s"
30
31 -class cBillable(gmBusinessDBObject.cBusinessDBObject):
32 """Items which can be billed to patients."""
33
34 _cmd_fetch_payload = _SQL_get_billable_fields % "pk_billable = %s"
35 _cmds_store_payload = [
36 """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
93
95 cmd = '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
102 -def get_billables(active_only=True, order_by=None, return_pks=False):
103
104 if order_by is None:
105 order_by = ' ORDER BY catalog_long, catalog_version, billable_code'
106 else:
107 order_by = ' ORDER BY %s' % order_by
108
109 if active_only:
110 where = 'active IS true'
111 else:
112 where = '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 if return_pks:
117 return [ r['pk_billable'] for r in rows ]
118 return [ cBillable(row = {'data': r, 'idx': idx, 'pk_field': 'pk_billable'}) for r in rows ]
119
120
121 -def create_billable(code=None, term=None, data_source=None, return_existing=False):
122 args = {
123 'code': code.strip(),
124 'term': term.strip(),
125 'data_src': data_source
126 }
127 cmd = """
128 INSERT INTO ref.billable (code, term, fk_data_source)
129 SELECT
130 %(code)s,
131 %(term)s,
132 %(data_src)s
133 WHERE NOT EXISTS (
134 SELECT 1 FROM ref.billable
135 WHERE
136 code = %(code)s
137 AND
138 term = %(term)s
139 AND
140 fk_data_source = %(data_src)s
141 )
142 RETURNING pk"""
143 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False, return_data = True)
144 if len(rows) > 0:
145 return cBillable(aPK_obj = rows[0]['pk'])
146
147 if not return_existing:
148 return None
149
150 cmd = """
151 SELECT * FROM ref.v_billables
152 WHERE
153 code = %(code)s
154 AND
155 term = %(term)s
156 AND
157 pk_data_source = %(data_src)s
158 """
159 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
160 return cBillable(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_billable'})
161
162
164 cmd = """
165 DELETE FROM ref.billable
166 WHERE
167 pk = %(pk)s
168 AND
169 NOT EXISTS (
170 SELECT 1 FROM bill.bill_item WHERE fk_billable = %(pk)s
171 )
172 """
173 args = {'pk': pk_billable}
174 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
175
176
177
178
179 _SQL_get_bill_item_fields = u"SELECT * FROM bill.v_bill_items WHERE %s"
180
181 -class cBillItem(gmBusinessDBObject.cBusinessDBObject):
182
183 _cmd_fetch_payload = _SQL_get_bill_item_fields % u"pk_bill_item = %s"
184 _cmds_store_payload = [
185 """UPDATE bill.bill_item SET
186 fk_provider = %(pk_provider)s,
187 fk_encounter = %(pk_encounter_to_bill)s,
188 date_to_bill = %(raw_date_to_bill)s,
189 description = gm.nullify_empty_string(%(item_detail)s),
190 net_amount_per_unit = %(net_amount_per_unit)s,
191 currency = gm.nullify_empty_string(%(currency)s),
192 fk_bill = %(pk_bill)s,
193 unit_count = %(unit_count)s,
194 amount_multiplier = %(amount_multiplier)s
195 WHERE
196 pk = %(pk_bill_item)s
197 AND
198 xmin = %(xmin_bill_item)s
199 RETURNING
200 xmin AS xmin_bill_item
201 """]
202
203 _updatable_fields = [
204 'pk_provider',
205 'pk_encounter_to_bill',
206 'raw_date_to_bill',
207 'item_detail',
208 'net_amount_per_unit',
209 'currency',
210 'pk_bill',
211 'unit_count',
212 'amount_multiplier'
213 ]
214
272
274 return cBillable(aPK_obj = self._payload[self._idx['pk_billable']])
275
276 billable = property(_get_billable, lambda x:x)
277
279 if self._payload[self._idx['pk_bill']] is None:
280 return None
281 return cBill(aPK_obj = self._payload[self._idx['pk_bill']])
282
283 bill = property(_get_bill, lambda x:x)
284
286 return self._payload[self._idx['pk_bill']] is not None
287
288 is_in_use = property(_get_is_in_use, lambda x:x)
289
290 -def get_bill_items(pk_patient=None, non_invoiced_only=False, return_pks=False):
291 if non_invoiced_only:
292 cmd = _SQL_get_bill_item_fields % u"pk_patient = %(pat)s AND pk_bill IS NULL"
293 else:
294 cmd = _SQL_get_bill_item_fields % u"pk_patient = %(pat)s"
295 args = {'pat': pk_patient}
296 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
297 if return_pks:
298 return [ r['pk_bill_item'] for r in rows ]
299 return [ cBillItem(row = {'data': r, 'idx': idx, 'pk_field': 'pk_bill_item'}) for r in rows ]
300
301
303
304 billable = cBillable(aPK_obj = pk_billable)
305 cmd = """
306 INSERT INTO bill.bill_item (
307 fk_provider,
308 fk_encounter,
309 net_amount_per_unit,
310 currency,
311 fk_billable
312 ) VALUES (
313 %(staff)s,
314 %(enc)s,
315 %(val)s,
316 %(curr)s,
317 %(billable)s
318 )
319 RETURNING pk"""
320 args = {
321 'staff': pk_staff,
322 'enc': pk_encounter,
323 'val': billable['raw_amount'],
324 'curr': billable['currency'],
325 'billable': pk_billable
326 }
327 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
328 return cBillItem(aPK_obj = rows[0][0])
329
330
332 cmd = 'DELETE FROM bill.bill_item WHERE pk = %(pk)s AND fk_bill IS NULL'
333 args = {'pk': pk_bill_item}
334 gmPG2.run_rw_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}])
335
336
337
338
339 _SQL_get_bill_fields = """SELECT * FROM bill.v_bills WHERE %s"""
340
341 -class cBill(gmBusinessDBObject.cBusinessDBObject):
342 """Represents a bill"""
343
344 _cmd_fetch_payload = _SQL_get_bill_fields % "pk_bill = %s"
345 _cmds_store_payload = [
346 """UPDATE bill.bill SET
347 invoice_id = gm.nullify_empty_string(%(invoice_id)s),
348 close_date = %(close_date)s,
349 apply_vat = %(apply_vat)s,
350 comment = gm.nullify_empty_string(%(comment)s),
351 fk_receiver_identity = %(pk_receiver_identity)s,
352 fk_receiver_address = %(pk_receiver_address)s,
353 fk_doc = %(pk_doc)s
354 WHERE
355 pk = %(pk_bill)s
356 AND
357 xmin = %(xmin_bill)s
358 RETURNING
359 pk as pk_bill,
360 xmin as xmin_bill
361 """
362 ]
363 _updatable_fields = [
364 'invoice_id',
365 'pk_receiver_identity',
366 'close_date',
367 'apply_vat',
368 'comment',
369 'pk_receiver_address',
370 'pk_doc'
371 ]
372
438
448
450 return [ cBillItem(aPK_obj = pk) for pk in self._payload[self._idx['pk_bill_items']] ]
451
452 bill_items = property(_get_bill_items, lambda x:x)
453
455 if self._payload[self._idx['pk_doc']] is None:
456 return None
457 return gmDocuments.cDocument(aPK_obj = self._payload[self._idx['pk_doc']])
458
459 invoice = property(_get_invoice, lambda x:x)
460
467
468 address = property(_get_address, lambda x:x)
469
475
476 default_address = property(_get_default_address, lambda x:x)
477
483
484 home_address = property(_get_home_address, lambda x:x)
485
487 if self._payload[self._idx['pk_receiver_address']] is not None:
488 return True
489 adr = self.default_address
490 if adr is None:
491 adr = self.home_address
492 if adr is None:
493 return False
494 self['pk_receiver_address'] = adr['pk_lnk_person_org_address']
495 return self.save_payload()
496
497
498 -def get_bills(order_by=None, pk_patient=None, return_pks=False):
499
500 args = {'pat': pk_patient}
501 where_parts = ['true']
502
503 if pk_patient is not None:
504 where_parts.append('pk_patient = %(pat)s')
505
506 if order_by is None:
507 order_by = ''
508 else:
509 order_by = ' ORDER BY %s' % order_by
510
511 cmd = (_SQL_get_bill_fields % ' AND '.join(where_parts)) + order_by
512 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
513 if return_pks:
514 return [ r['pk_bill'] for r in rows ]
515 return [ cBill(row = {'data': r, 'idx': idx, 'pk_field': 'pk_bill'}) for r in rows ]
516
517
519 args = {'pk_doc': pk_document}
520 cmd = _SQL_get_bill_fields % 'pk_doc = %(pk_doc)s'
521 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
522 return [ cBill(row = {'data': r, 'idx': idx, 'pk_field': 'pk_bill'}) for r in rows ]
523
524
526
527 args = {'inv_id': invoice_id}
528 cmd = """
529 INSERT INTO bill.bill (invoice_id)
530 VALUES (gm.nullify_empty_string(%(inv_id)s))
531 RETURNING pk
532 """
533 rows, idx = gmPG2.run_rw_queries(link_obj = conn, queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False)
534
535 return cBill(aPK_obj = rows[0]['pk'])
536
537
539 args = {'pk': pk_bill}
540 cmd = "DELETE FROM bill.bill WHERE pk = %(pk)s"
541 gmPG2.run_rw_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}])
542 return True
543
544
547
548
557
558
560 """Create scan2pay data for generating a QR code.
561
562 https://www.scan2pay.info
563 --------------------------------
564 BCD # (3) fixed, barcode tag
565 002 # (3) fixed, version
566 1 # (1) charset, 1 = utf8
567 SCT # (3) fixed
568 $<praxis_id::BIC//Bank//%(value)s::11>$ # (11) <BIC>
569 $2<range_of::$<current_provider_name::%(lastnames)s::>$,$<praxis::%(praxis)s::>$::70>2$ # (70) <Name of beneficiary> "Empfänger" - Praxis
570 $<praxis_id::IBAN//Bank//%(value)s::34>$ # (34) <IBAN>
571 EUR$<bill::%(total_amount_with_vat)s::12>$ # (12) <Amount in EURO> "EUR12.5"
572 # (4) <purpose of transfer> - leer
573 # (35) <remittance info - struct> - only this XOR the next field - GNUmed: leer
574 $2<range_of::InvID=$<bill::%(invoice_id)s::>$/Date=$<today::%d.%B %Y::>$::140$>2$ # (140) <remittance info - text> "Client:Marie Louise La Lune" - "Rg Nr, date"
575 <beneficiary-to-payor info> # (70) "pay soon :-)" - optional - GNUmed nur wenn bytes verfügbar
576 --------------------------------
577 total: 331 bytes (not chars ! - cave UTF8)
578 EOL: LF or CRLF
579 last *used* element not followed by anything, IOW can omit pending non-used elements
580 """
581 assert (branch is not None), '<branch> must not be <None>'
582 assert (bill is not None), '<bill> must not be <None>'
583
584 data = {}
585 IBANs = branch.get_external_ids(id_type = 'IBAN', issuer = 'Bank')
586 if len(IBANs) == 0:
587 _log.debug('no IBAN found, cannot create scan2pay data')
588 return None
589 data['IBAN'] = IBANs[0]['value'][:34]
590 data['beneficiary'] = gmTools.coalesce (
591 value2test = provider,
592 return_instead = branch['praxis'][:70],
593 template4value = '%%(lastnames)s, %s' % branch['praxis']
594 )[:70]
595 BICs = branch.get_external_ids(id_type = 'BIC', issuer = 'Bank')
596 if len(BICs) == 0:
597 data['BIC'] = ''
598 else:
599 data['BIC'] = BICs[0]['value'][:11]
600 data['amount'] = bill['total_amount_with_vat'][:9]
601 data['ref'] = (_('Inv: %s, %s') % (
602 bill['invoice_id'],
603 gmDateTime.pydt_strftime(gmDateTime.pydt_now_here(), '%d.%B %Y')
604 ))[:140]
605 data['cmt'] = gmTools.coalesce(comment, '', '\n%s')[:70]
606
607 data_str = 'BCD\n002\n1\nSCT\n%(BIC)s\n%(beneficiary)s\n%(IBAN)s\nEUR%(amount)s\n\n\n%(ref)s%(cmt)s' % data
608 data_str_bytes = bytes(data_str, 'utf8')[:331]
609 return str(data_str_bytes, 'utf8')
610
611
612
613
614 if __name__ == "__main__":
615
616 if len(sys.argv) < 2:
617 sys.exit()
618
619 if sys.argv[1] != 'test':
620 sys.exit()
621
622
623
624
625 from Gnumed.business import gmPraxis
626
627
628
629
634
636 print("--------------")
637 me = cBillable(aPK_obj=1)
638 fields = me.get_fields()
639 for field in fields:
640 print(field, ':', me[field])
641 print("updatable:", me.get_updatable_fields())
642
643
644
654
655
656
657
658 test_get_scan2pay_data()
659