24
24
from invenio_mail .tasks import send_email as task_send_email
25
25
from num2words import num2words
26
26
27
+ from .api import Notification
27
28
from .models import NotificationChannel , NotificationType
28
29
from .utils import get_communication_channel_to_use , get_template_to_use
29
30
from ..libraries .api import email_notification_type
30
31
from ..locations .api import Location
32
+ from ..patrons .api import Patron
31
33
from ...filter import format_date_filter
32
34
from ...utils import language_iso639_2to1
33
35
@@ -45,10 +47,6 @@ def dispatch_notifications(cls, notification_pids=None, resend=False,
45
47
:param verbose: Verbose output.
46
48
:returns: dictionary with processed and send count
47
49
"""
48
- from .api import Notification
49
- from ..items .api import Item
50
- from ..loans .api import LoanState
51
- from ..patrons .api import Patron
52
50
53
51
def get_dispatcher_function (channel ):
54
52
try :
@@ -74,152 +72,14 @@ def get_dispatcher_function(channel):
74
72
# 3. library
75
73
# 4. patron
76
74
for notification in notifications :
77
- n_type = notification ['notification_type' ]
78
- process_date = notification .get ('process_date' )
79
-
80
- # 1. Check if notification has already been processed and if we
81
- # need to resend it. If not, skip this notification and continue
82
- if process_date :
83
- current_app .logger .warning (
84
- f'Notification: { notification .pid } already processed '
85
- f'on: { process_date } '
86
- )
87
- if not resend :
88
- continue
89
-
90
- data = notification .replace_pids_and_refs ()
91
- loan = data ['loan' ]
92
- patron = loan ['patron' ]
93
-
94
- # 2. Find the communication channel to use to dispatch this
95
- # notification. The channel depends on the loan, the
96
- # notification type and the related patron
97
- communication_channel = get_communication_channel_to_use (
98
- loan ,
99
- notification ,
100
- patron
101
- )
102
-
103
- # 3. Get the communication language to use. Except for internal
104
- # notification, the language to use is defined into the related
105
- # patron account. For Internal notifications, the language is
106
- # the library defined language.
107
- communication_lang = patron ['patron' ]['communication_language' ]
108
- if n_type in NotificationType .INTERNAL_NOTIFICATIONS :
109
- communication_lang = loan ['library' ]['communication_language' ]
110
- language = language_iso639_2to1 (communication_lang )
111
-
112
- # 4. Compute the reminder counter.
113
- # For some notification (overdue, ...) the reminder counter is
114
- # an information to use into the message to send. We need to
115
- # translate this counter into a localized string.
116
- reminder_counter = data .get ('reminder_counter' , 0 )
117
- reminder = reminder_counter + 1
118
- reminder = num2words (reminder , to = 'ordinal' , lang = language )
119
-
120
- # 5. Get the template to use for the notification.
121
- # Depending of the notification and the reminder counter, find
122
- # the correct template file to use.
123
- tpl_path = get_template_to_use (
124
- loan , n_type , reminder_counter ).rstrip ('/' )
125
- template = f'{ tpl_path } /{ communication_lang } .txt'
126
-
127
- # 6. Build the context to use to render the template.
128
- ctx_data = {
129
- 'notification_type' : n_type ,
130
- 'creation_date' : format_date_filter (
131
- notification .get ('creation_date' ),
132
- date_format = 'medium' ,
133
- locale = language
134
- ),
135
- 'in_transit' : loan ['state' ] in [
136
- LoanState .ITEM_IN_TRANSIT_TO_HOUSE ,
137
- LoanState .ITEM_IN_TRANSIT_FOR_PICKUP
138
- ],
139
- 'template' : template ,
140
- 'profile_url' : loan ['profile_url' ],
141
- 'patron' : patron ,
142
- 'library' : loan ['library' ],
143
- 'pickup_library' : loan ['pickup_library' ],
144
- 'transaction_library' : loan ['transaction_library' ],
145
- 'pickup_name' : loan ['pickup_name' ],
146
- 'documents' : [],
147
- 'notifications' : []
148
- }
149
- transaction_location = loan .get ('transaction_location' )
150
- if transaction_location :
151
- ctx_data ['transaction_location_name' ] = \
152
- transaction_location ['name' ]
153
-
154
- # aggregate notifications
155
- l_pid = loan ['library' ]['pid' ]
156
- p_pid = patron ['pid' ]
157
- c_channel = communication_channel
158
-
159
- aggregated .setdefault (n_type , {})
160
- aggregated [n_type ].setdefault (c_channel , {})
161
- aggregated [n_type ][c_channel ].setdefault (l_pid , {})
162
- aggregated [n_type ][c_channel ][l_pid ].setdefault (p_pid , ctx_data )
163
-
164
- documents_data = {
165
- 'title_text' : loan ['document' ]['title_text' ],
166
- 'responsibility_statement' :
167
- loan ['document' ]['responsibility_statement' ],
168
- 'reminder' : reminder ,
169
- 'end_date' : loan .get ('end_date' )
170
- }
171
- documents_data = {k : v for k , v in documents_data .items () if v }
172
-
173
- # Add item to document
174
- item_data = loan .get ('item' )
175
- if item_data :
176
- if n_type in [
177
- NotificationType .BOOKING ,
178
- NotificationType .AVAILABILITY
179
- ]:
180
- # get item from the checkin loan
181
- item = Item .get_record_by_pid (item_data .get ('pid' ))
182
- # get the requested loan it can be in several states
183
- # due to the automatic request validation
184
- request_loan = None
185
- for state in [
186
- LoanState .ITEM_AT_DESK ,
187
- LoanState .ITEM_IN_TRANSIT_FOR_PICKUP ,
188
- LoanState .PENDING
189
- ]:
190
- request_loan = item .get_first_loan_by_state (state )
191
- if request_loan :
192
- break
193
- request_patron = Patron .get_record_by_pid (
194
- request_loan ['patron_pid' ])
195
- ctx_data ['request_patron' ] = \
196
- request_patron .replace_refs ().dumps ()
197
- pickup_location = Location .get_record_by_pid (
198
- request_loan ['pickup_location_pid' ])
199
- ctx_data ['request_pickup_name' ] = \
200
- pickup_location ['pickup_name' ]
201
-
202
- documents_data ['item' ] = {
203
- 'barcode' : item_data ['barcode' ],
204
- 'call_number' : item_data .get ('call_number' ),
205
- 'second_call_number' : item_data .get ('second_call_number' )
206
- }
207
- documents_data ['item' ] = \
208
- {k : v for k , v in documents_data ['item' ].items () if v }
209
- location = item_data .get ('location' )
210
- if location :
211
- loc = Location .get_record_by_pid (location .get ('pid' ))
212
- lib = loc .get_library ()
213
- documents_data ['item' ]['location_name' ] = loc .get ('name' )
214
- documents_data ['item' ]['library_name' ] = lib .get ('name' )
215
- email = loc .get ('notification_email' )
216
- if email :
217
- ctx_data ['location_email' ] = email
218
-
219
- # Add information into correct aggregations
220
- aggregation = aggregated [n_type ][c_channel ][l_pid ][p_pid ]
221
- aggregation ['documents' ].append (documents_data )
222
- aggregation ['notifications' ].append (notification )
75
+ try :
76
+ cls ._process_notification (
77
+ notification , resend , aggregated )
78
+ except Exception as error :
79
+ current_app .logger .error (
80
+ f'Notification has not be sent (pid: { notification .pid } ,'
81
+ f' type: { notification ["notification_type" ]} ): '
82
+ f'{ error } ' )
223
83
224
84
# SEND AGGREGATED NOTIFICATIONS
225
85
for notification_type , notification_values in aggregated .items ():
@@ -248,6 +108,165 @@ def get_dispatcher_function(channel):
248
108
'not_sent' : not_sent
249
109
}
250
110
111
+ @classmethod
112
+ def _process_notification (cls , notification , resend , aggregated ):
113
+ """Process one notification.
114
+
115
+ :param notification: Notification to process.
116
+ :param resend: Resend notification if already send.
117
+ :param aggregated: Dict to store notification results.
118
+ :returns: A dispatcher function.
119
+ """
120
+ from ..items .api import Item
121
+ from ..loans .api import LoanState
122
+
123
+ n_type = notification ['notification_type' ]
124
+ process_date = notification .get ('process_date' )
125
+
126
+ # 1. Check if notification has already been processed and if we
127
+ # need to resend it. If not, skip this notification and continue
128
+ if process_date :
129
+ current_app .logger .warning (
130
+ f'Notification: { notification .pid } already processed '
131
+ f'on: { process_date } '
132
+ )
133
+ if not resend :
134
+ return
135
+
136
+ data = notification .replace_pids_and_refs ()
137
+ loan = data ['loan' ]
138
+ patron = loan ['patron' ]
139
+
140
+ # 2. Find the communication channel to use to dispatch this
141
+ # notification. The channel depends on the loan, the
142
+ # notification type and the related patron
143
+ communication_channel = get_communication_channel_to_use (
144
+ loan ,
145
+ notification ,
146
+ patron
147
+ )
148
+
149
+ # 3. Get the communication language to use. Except for internal
150
+ # notification, the language to use is defined into the related
151
+ # patron account. For Internal notifications, the language is
152
+ # the library defined language.
153
+ communication_lang = patron ['patron' ]['communication_language' ]
154
+ if n_type in NotificationType .INTERNAL_NOTIFICATIONS :
155
+ communication_lang = loan ['library' ]['communication_language' ]
156
+ language = language_iso639_2to1 (communication_lang )
157
+
158
+ # 4. Compute the reminder counter.
159
+ # For some notification (overdue, ...) the reminder counter is
160
+ # an information to use into the message to send. We need to
161
+ # translate this counter into a localized string.
162
+ reminder_counter = data .get ('reminder_counter' , 0 )
163
+ reminder = reminder_counter + 1
164
+ reminder = num2words (reminder , to = 'ordinal' , lang = language )
165
+
166
+ # 5. Get the template to use for the notification.
167
+ # Depending of the notification and the reminder counter, find
168
+ # the correct template file to use.
169
+ tpl_path = get_template_to_use (
170
+ loan , n_type , reminder_counter ).rstrip ('/' )
171
+ template = f'{ tpl_path } /{ communication_lang } .txt'
172
+
173
+ # 6. Build the context to use to render the template.
174
+ ctx_data = {
175
+ 'notification_type' : n_type ,
176
+ 'creation_date' : format_date_filter (
177
+ notification .get ('creation_date' ),
178
+ date_format = 'medium' ,
179
+ locale = language
180
+ ),
181
+ 'in_transit' : loan ['state' ] in [
182
+ LoanState .ITEM_IN_TRANSIT_TO_HOUSE ,
183
+ LoanState .ITEM_IN_TRANSIT_FOR_PICKUP
184
+ ],
185
+ 'template' : template ,
186
+ 'profile_url' : loan ['profile_url' ],
187
+ 'patron' : patron ,
188
+ 'library' : loan ['library' ],
189
+ 'pickup_library' : loan ['pickup_library' ],
190
+ 'transaction_library' : loan ['transaction_library' ],
191
+ 'pickup_name' : loan ['pickup_name' ],
192
+ 'documents' : [],
193
+ 'notifications' : []
194
+ }
195
+ transaction_location = loan .get ('transaction_location' )
196
+ if transaction_location :
197
+ ctx_data ['transaction_location_name' ] = \
198
+ transaction_location ['name' ]
199
+
200
+ # aggregate notifications
201
+ l_pid = loan ['library' ]['pid' ]
202
+ p_pid = patron ['pid' ]
203
+ c_channel = communication_channel
204
+
205
+ aggregated .setdefault (n_type , {})
206
+ aggregated [n_type ].setdefault (c_channel , {})
207
+ aggregated [n_type ][c_channel ].setdefault (l_pid , {})
208
+ aggregated [n_type ][c_channel ][l_pid ].setdefault (p_pid , ctx_data )
209
+
210
+ documents_data = {
211
+ 'title_text' : loan ['document' ]['title_text' ],
212
+ 'responsibility_statement' :
213
+ loan ['document' ]['responsibility_statement' ],
214
+ 'reminder' : reminder ,
215
+ 'end_date' : loan .get ('end_date' )
216
+ }
217
+ documents_data = {k : v for k , v in documents_data .items () if v }
218
+
219
+ # Add item to document
220
+ item_data = loan .get ('item' )
221
+ if item_data :
222
+ if n_type in [
223
+ NotificationType .BOOKING ,
224
+ NotificationType .AVAILABILITY
225
+ ]:
226
+ # get item from the checkin loan
227
+ item = Item .get_record_by_pid (item_data .get ('pid' ))
228
+ # get the requested loan it can be in several states
229
+ # due to the automatic request validation
230
+ request_loan = None
231
+ for state in [
232
+ LoanState .ITEM_AT_DESK ,
233
+ LoanState .ITEM_IN_TRANSIT_FOR_PICKUP ,
234
+ LoanState .PENDING
235
+ ]:
236
+ request_loan = item .get_first_loan_by_state (state )
237
+ if request_loan :
238
+ break
239
+ request_patron = Patron .get_record_by_pid (
240
+ request_loan ['patron_pid' ])
241
+ ctx_data ['request_patron' ] = \
242
+ request_patron .replace_refs ().dumps ()
243
+ pickup_location = Location .get_record_by_pid (
244
+ request_loan ['pickup_location_pid' ])
245
+ ctx_data ['request_pickup_name' ] = \
246
+ pickup_location ['pickup_name' ]
247
+
248
+ documents_data ['item' ] = {
249
+ 'barcode' : item_data ['barcode' ],
250
+ 'call_number' : item_data .get ('call_number' ),
251
+ 'second_call_number' : item_data .get ('second_call_number' )
252
+ }
253
+ documents_data ['item' ] = \
254
+ {k : v for k , v in documents_data ['item' ].items () if v }
255
+ location = item_data .get ('location' )
256
+ if location :
257
+ loc = Location .get_record_by_pid (location .get ('pid' ))
258
+ lib = loc .get_library ()
259
+ documents_data ['item' ]['location_name' ] = loc .get ('name' )
260
+ documents_data ['item' ]['library_name' ] = lib .get ('name' )
261
+ email = loc .get ('notification_email' )
262
+ if email :
263
+ ctx_data ['location_email' ] = email
264
+
265
+ # Add information into correct aggregations
266
+ aggregation = aggregated [n_type ][c_channel ][l_pid ][p_pid ]
267
+ aggregation ['documents' ].append (documents_data )
268
+ aggregation ['notifications' ].append (notification )
269
+
251
270
@staticmethod
252
271
def _create_email (recipients , reply_to , ctx_data , template ):
253
272
"""Create email message from template.
0 commit comments