summaryrefslogtreecommitdiff
path: root/jasper_reports/models/report_xml.py
blob: ae6ff67c2140d36260413d3f33a0987aee7a0c65 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2008-2012 NaN Projectes de Programari Lliure, S.L.
#                         http://www.NaN-tic.com
# Copyright (C) 2013 Tadeus Prastowo <tadeus.prastowo@infi-nity.com>
#                         Vikasa Infinity Anugrah <http://www.infi-nity.com>
# Copyright (C) 2019-Today Serpent Consulting Services Pvt. Ltd.
#                         (<http://www.serpentcs.com>)
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################

import base64
import io
import logging
import os
import time
from xml.dom.minidom import getDOMImplementation
from datetime import datetime

from odoo import api, fields, models, _
from odoo.exceptions import UserError, AccessError
from odoo.tools.safe_eval import safe_eval

from ..JasperReports.jasper_report_config import Report

_logger = logging.getLogger(__name__)

src_chars = """ '"()/*-+?¿!&$[]{}@#`'^:;<>=~%,\\"""
src_chars = str.encode(src_chars, 'iso-8859-1')
dst_chars = """________________________________"""
dst_chars = str.encode(dst_chars, 'iso-8859-1')


class ReportXmlFile(models.Model):
    _name = 'ir.actions.report.xml.file'
    _description = 'Jasper Report File'

    file = fields.Binary(required=True)
    filename = fields.Char('File Name')
    report_id = fields.Many2one(
        'ir.actions.report', 'Report', ondelete='cascade')
    default = fields.Boolean(default=True)

    @api.model
    def create(self, values):
        result = super(ReportXmlFile, self).create(values)
        # Removed the update method for the call the create_action() of
        # ir.actions.report object
        result.report_id.update()
        return result

    def write(self, values):
        result = super(ReportXmlFile, self).write(values)
        for attachment in self:
            attachment.report_id.update()
        return result


# Inherit ir.actions.report.xml and add an action to be able to store
# .jrxml and .properties files attached to the report so they can be
# used as reports in the application.


class ReportXml(models.Model):
    _inherit = 'ir.actions.report'

    jasper_output = fields.Selection(
        [('html', 'HTML'), ('csv', 'CSV'),
         ('xls', 'XLS'), ('rtf', 'RTF'),
         ('odt', 'ODT'), ('ods', 'ODS'),
         ('txt', 'Text'), ('pdf', 'PDF')],
        default='pdf')
    jasper_file_ids = fields.One2many(
        'ir.actions.report.xml.file', 'report_id', 'Files')
    # To get the model name from current models in database,we add a new field
    # and it will give us model name at create and update time.
    jasper_report = fields.Boolean('Is Jasper Report?')
    report_type = fields.Selection(selection_add=[("jasper", "Jasper")], ondelete={'jasper': 'cascade'})
    file = fields.Char('File')

    def retrieve_jasper_attachment(self, record):
        '''Retrieve an attachment for a specific record.

        :param record: The record owning of the attachment.
        :param attachment_name: The optional name of the attachment.
        :return: A recordset of length <=1 or None
        '''
        attachment_obj = self.env['ir.attachment']
        for report in self:
            attachment_name = str(report.name) + '.' + report.jasper_output
            if report.attachment:
                attachment_name = safe_eval(
                    report.attachment, {'object': record, 'time': time})
            return attachment_obj.search([
                ('datas_fname', '=', attachment_name),
                ('res_model', '=', report.model),
                ('res_id', 'in', record.ids)
            ], limit=1)

    def postprocess_jasper_report(self, record, buffer):
        '''Hook to handle post processing during the jasper report generation.
        The basic behavior consists to create a new attachment containing the
        jasper base64 encoded.

        :param record_id: The record that will own the attachment.
        :param pdf_content: The optional name content of the file to avoid
                            reading both times.
        :return: The newly generated attachment if no AccessError, else None.
        '''
        attachment_obj = self.env['ir.attachment']
        for report in self:
            attachment_name = str(report.name) + '.' + report.jasper_output
            if report.attachment:
                attachment_name = safe_eval(
                    report.attachment, {'object': record, 'time': time})
            attachment_vals = {
                'name': attachment_name,
                'datas': base64.encodestring(buffer.getvalue()),
                'datas_fname': attachment_name,
                'res_model': report.model,
                'res_id': record.id,
            }
            try:
                return attachment_obj.create(attachment_vals)
            except AccessError:
                _logger.warn(
                    "Cannot save %s report %r as attachment",
                    report.jasper_output, attachment_vals['name'])
            else:
                _logger.info('The %s document %s is now saved in the database',
                             report.jasper_output, attachment_vals['name'])
            return None

    @api.model
    def render_jasper(self, docids, data):
        context = self.env.context
        uid = self.env.uid
        cr = self.env.cr

        if not data:
            data = {}
        doc_records = self.model_id.browse(docids)
        report_model_name = 'report.%s' % self.report_name
        self.env.cr.execute('SELECT id, model FROM '
                            'ir_act_report_xml WHERE '
                            'report_name = %s LIMIT 1',
                            (self.report_name,))
        record = self.env.cr.dictfetchone()
        report_model = self.search([('report_name', '=', report_model_name)])
        if report_model is None:
            raise UserError(_('%s model not found.') % report_model_name)
        data.update({'env': self.env, 'model': record.get('model')})
        if self.attachment_use:
            save_in_attachment = {}
            for doc_record in doc_records:
                attachment_id = self.retrieve_jasper_attachment(doc_record)
                if attachment_id:
                    save_in_attachment[doc_record.id] = attachment_id
                else:
                    r = Report(report_model_name, cr, uid, [doc_record.id],
                               data, context)
                    jasper = r.execute()
                    jasper_content_stream = io.BytesIO(jasper)
                    attachment_id = self.postprocess_jasper_report(
                        doc_record, jasper_content_stream)
                    save_in_attachment[doc_record.id] = attachment_id
            return self._post_pdf(save_in_attachment), self.jasper_output
        r = Report(report_model_name, cr, uid, docids, data, context)
        jasper = r.execute()
        if self.id in [843, 844, 764]:
            threshold_datetime = datetime(2025, 4, 11, 6, 26)
            picking = self.env['stock.picking'].browse(docids)
            if not picking:
                return False
            if not picking.state == 'done' and picking.create_date < threshold_datetime:
                raise UserError('DO Harus complete terlebih dahulu, baru bisa di print')

            picking.date_printed_sj = datetime.utcnow()
            picking.printed_sj = True

        if self.id in [1074]:
            picking = self.env['stock.picking'].browse(docids)
            picking.date_printed_sj_retur = datetime.utcnow()
            picking.printed_sj_retur = True

        if self.id in [777]:
            picking = self.env['stock.picking'].browse(docids)
            if not picking:
                return False
            
            picking.status_printed = 'printed'
            picking.date_printed_list = datetime.utcnow()
        return jasper, self.jasper_output

    @api.model
    def _get_report_from_name(self, report_name):
        res = super(ReportXml, self)._get_report_from_name(report_name)
        if res:
            return res
        report_obj = self.env['ir.actions.report']
        domain = [('report_type', '=', 'jasper'),
                  ('report_name', '=', report_name)]
        context = self.env['res.users'].context_get()
        return report_obj.with_context(context).search(domain, limit=1)

    @api.model
    def create(self, values):
        if self._context and self._context.get('jasper_report'):
            values['model'] = \
                self.env['ir.model'].browse(values['model_id']).model
            values['type'] = 'ir.actions.report'
            values['report_type'] = 'jasper'
            values['jasper_report'] = True
        return super(ReportXml, self).create(values)

    def write(self, values):
        if self._context and self._context.get('jasper_report'):
            if 'model_id' in values:
                values['model'] = \
                    self.env['ir.model'].browse(
                        values['model_id']).model

            values['type'] = 'ir.actions.report'
            values['report_type'] = 'jasper'
            values['jasper_report'] = True
        return super(ReportXml, self).write(values)

    def update(self):
        if self._context is None:
            self._context = {}
        for report in self:
            has_default = False
            # Browse attachments and store .jrxml and .properties
            # into jasper_reports/custom_reportsdirectory. Also add
            # or update ir.values data so they're shown on model views.for
            # attachment in self.env['ir.attachment'].browse(attachmentIds)
            for attachment in report.jasper_file_ids:
                content = attachment.file
                file_name = attachment.filename
                if not file_name or not content:
                    continue
                if not file_name.endswith('.jrxml') and \
                        not file_name.endswith('.jasper'):
                    raise UserError(_('%s is not supported file. Please\
                     Upload .jrxml or .jasper files only.') % (file_name))
                path = self.save_file(file_name, content)
                if '.jrxml' in file_name and attachment.default:
                    if has_default:
                        raise UserError(_('There is more than one \
                                         report marked as default'))
                    has_default = True
                    report.write({'report_file': path})
                    report.create_action()
            if not has_default:
                raise UserError(_('No report has been marked as default! \
                                 You need atleast one jrxml report!'))
            # Ensure the report is registered so it can be used immediately
            # register_jasper_report(report.report_name, report.model)
        return True

    def save_file(self, name, value):
        path = os.path.abspath(os.path.dirname(__file__))
        path += '/../custom_reports/%s' % name
        with open(path, 'wb+') as f:
            f.write(base64.decodebytes(value))
        path = 'jasper_reports/custom_reports/%s' % name
        return path

    def normalize(self, text):
        if isinstance(text, str):
            text = text.encode('utf-8')
        return text

    def unaccent(self, text):
        src_chars_list = [
            "'", "(", ")", ",", "/", "*", "-", "+", "?", "¿", "!",
            "&", "$", "[", "]", "{", "}", "@", "#", "`", "^", ":",
            ";", "<", ">", "=", "~", "%", "\\"]
        num_char_dict = {
            '1': 'One', '2': 'Two', '3': 'Three', '4': 'Four', '5': 'Five',
            '6': 'Six', '7': 'Seven', '8': 'Eight', '9': 'Nine', '0': 'Zero'}
        if isinstance(text, str):
            if text[0] in num_char_dict:
                text = text.replace(text[0], num_char_dict.get(text[0]))
            for src in src_chars_list:
                text = text.replace(src, "_")
        return text

    @api.model
    def generate_xml(self, pool, model_name, parent_node, document, depth,
                     first_call):
        if self._context is None:
            self._context = {}

        # First of all add "id" field
        field_node = document.createElement('id')
        parent_node.appendChild(field_node)
        value_node = document.createTextNode('1')
        field_node.appendChild(value_node)
        language = self._context.get('lang')
        if language == 'en_US':
            language = False

        # Then add all fields in alphabetical order
        model_fields = pool[model_name]._fields
        keys_list = model_fields.keys()

        # Remove duplicates because model may have fields with the
        # same name as it's parent
        keys_list = sorted(keys_list)

        for field in keys_list:
            name = False
            if language:
                # Obtain field string for user's language.
                name = self.env['ir.translation']._get_source(
                    '{model},{field}'.format(model=model_name, field=field),
                    'field', language)
            if not name:
                # If there's not description in user's language,
                # use default (english) one.
                name = model_fields[field].string
            if name:
                self.unaccent(name)
            # After unaccent the name might result in an empty string
            if name:
                name = '%s-%s' % (self.unaccent(name), field)
            else:
                name = field
            field_node = document.createElement(name.replace(' ', '_'))

            parent_node.appendChild(field_node)
            field_type = model_fields[field].type

            if field_type in ('many2one', 'one2many', 'many2many'):
                if depth <= 1:
                    continue
                comodel_name = model_fields[field].comodel_name
                self.generate_xml(
                    pool, comodel_name, field_node, document, depth - 1, False)
                continue

            value = field
            if field_type == 'float':
                value = '12345.67'
            elif field_type == 'integer':
                value = '12345'
            elif field_type == 'date':
                value = '2009-12-31 00:00:00'
            elif field_type == 'time':
                value = '12:34:56'
            elif field_type == 'datetime':
                value = '2009-12-31 12:34:56'
            value_node = document.createTextNode(value)
            field_node.appendChild(value_node)

        if depth > 1 and model_name != 'Attachments':
            # Create relation with attachments
            field_node = document.createElement('Attachments-Attachments')
            parent_node.appendChild(field_node)
            self.generate_xml(
                pool, 'ir.attachment', field_node, document, depth - 1, False)

        if first_call:
            # Create relation with user
            field_node = document.createElement('User-User')
            parent_node.appendChild(field_node)
            self.generate_xml(
                pool, 'res.users', field_node, document, depth - 1, False)

            # Create special entries
            field_node = document.createElement('Special-Special')
            parent_node.appendChild(field_node)

            new_node = document.createElement('copy')
            field_node.appendChild(new_node)
            value_node = document.createTextNode('1')
            new_node.appendChild(value_node)

            new_node = document.createElement('sequence')
            field_node.appendChild(new_node)
            value_node = document.createTextNode('1')
            new_node.appendChild(value_node)

            new_node = document.createElement('subsequence')
            field_node.appendChild(new_node)
            value_node = document.createTextNode('1')
            new_node.appendChild(value_node)

    @api.model
    def create_xml(self, model, depth):
        if self._context is None:
            self._context = {}
        document = getDOMImplementation().createDocument(None, 'data', None)
        top_node = document.documentElement
        record_node = document.createElement('record')
        top_node.appendChild(record_node)
        self.generate_xml(self.env, model, record_node, document, depth, True)
        return top_node.toxml()