diff --git a/reportbro/.idea/misc.xml b/reportbro/.idea/misc.xml new file mode 100644 index 000000000..865611434 --- /dev/null +++ b/reportbro/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/reportbro/.idea/modules.xml b/reportbro/.idea/modules.xml new file mode 100644 index 000000000..61207c1b6 --- /dev/null +++ b/reportbro/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/reportbro/.idea/reportbro.iml b/reportbro/.idea/reportbro.iml new file mode 100644 index 000000000..dbd23979a --- /dev/null +++ b/reportbro/.idea/reportbro.iml @@ -0,0 +1,16 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/reportbro/.idea/workspace.xml b/reportbro/.idea/workspace.xml new file mode 100644 index 000000000..58b26f6a9 --- /dev/null +++ b/reportbro/.idea/workspace.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1701846826371 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/reportbro/__init__.py b/reportbro/__init__.py new file mode 100644 index 000000000..511a0ca3a --- /dev/null +++ b/reportbro/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from . import controllers +from . import models \ No newline at end of file diff --git a/reportbro/__manifest__.py b/reportbro/__manifest__.py new file mode 100644 index 000000000..3e1cbf424 --- /dev/null +++ b/reportbro/__manifest__.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +{ + 'name': "fast_reportbro", + 'version':'1.7', + 'summary': """ + reportbro Frontend and backend report service storage, output module, support for custom data sources, custom template PDF printing + """, + 'description': """ + reportbro Frontend and backend report service storage, output module, support for custom data sources, custom template PDF printing + """, + 'currency':'USD', + 'support':'email:444713077@qq.com', + 'author': 'zxltwins', + 'website': "www.odoo.com", + 'category': 'tools/print', + 'depends': ['base'], + "external_dependencies": {"python": ["requests", "reportbro-lib"]}, + # always loaded + 'data': [ + 'security/ir.model.access.csv', + 'views/templates.xml' + ], + + 'application': True, + 'installable': True, + 'images':[ + 'static/description/main_screenshot.png', + 'static/description/icon.png', + ], + 'assets': { + 'web._assets_primary_variables': [ + + ], + 'web.assets_backend': [ + 'reportbro/static/src/js/report_designer.js' + ], + 'web.assets_frontend': [ + + + ], + 'web.assets_tests': [ + + ], + + 'web.report_assets_common': [ + + ], + 'web.report_assets_pdf': [ + + ], + }, + 'license': 'LGPL-3', +} diff --git a/reportbro/controllers/__init__.py b/reportbro/controllers/__init__.py new file mode 100644 index 000000000..cd1cf7e32 --- /dev/null +++ b/reportbro/controllers/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import report_server \ No newline at end of file diff --git a/reportbro/controllers/report_server.py b/reportbro/controllers/report_server.py new file mode 100644 index 000000000..443980d22 --- /dev/null +++ b/reportbro/controllers/report_server.py @@ -0,0 +1,182 @@ +import uuid, json, werkzeug, werkzeug.utils +from reportbro import Report, ReportBroError +from odoo import http +from odoo.tools import date_utils +from odoo.http import request, Response,JsonRPCDispatcher +from jinja2 import Environment, PackageLoader +from urllib.parse import quote +from werkzeug.exceptions import NotFound + +# def _json_response(self, result=None, error=None): +# ir_http = self.request.registry['ir.http'] +# try: +# rule, args = ir_http._match(self.request.httprequest.path) +# except NotFound: +# self.params = self.request.get_http_params() +# response = ir_http._serve_fallback() +# if response: +# self.dispatcher.post_dispatch(response) +# return response +# raise +# +# +# if rule.endpoint and rule.endpoint.routing.get('my_json'): +# response = {} +# if error is not None: +# response['error'] = error +# if result is not None: +# response = result +# else: +# # odoo返回的默认数据格式 +# response = { +# 'jsonrpc': '2.0', +# 'id': self.jsonrequest.get('id') +# } +# if error is not None: +# response['error'] = error +# if result is not None: +# response['result'] = result +# +# mime = 'application/json' +# body = json.dumps(response, default=date_utils.json_default) +# +# return Response( +# body, status=error and error.pop('http_status', 200) or 200, +# headers=[('Content-Type', mime), ('Content-Length', len(body))] +# ) +# +# # 重写JsonRequest中的_json_response方法 +# +# setattr(JsonRPCDispatcher, '_response', _json_response) + + +class WmsReportServer(http.Controller): + + @http.route('/report_designer', type='http', auth='public', csrf=False, methods=['Get'], website=True, cors="*") + def report_designer(self, **kw): + env = Environment(loader=PackageLoader('odoo.addons.reportbro', 'templates')) + template = env.get_template('report_designer.html') + return template.render() + + + + @http.route('/api/report/get_report_id', type='json', auth='public', csrf=False, methods=['Post'], website=True, + cors="*", my_json=True) + def get_report_id(self, *args, **kwargs): + + res=request.env['report.designer'].sudo().search([],limit=1,order='create_date desc') + id=res.name + #res.sudo().unlink() + return {'id':id,'code':0} + + + + @http.route('/api/report/report_save', type='json', auth='public', csrf=False, methods=['Post'], website=True, + cors="*", my_json=True) + def report_save(self, *args, **kwargs): + data = dict(request.dispatcher.jsonrequest or {}) + report_id = data.get('report_id', None) + report_data = data.get('report_data', None) + print(json.dumps(report_data)) + if report_data is None or report_id is None: + return {'code': 1, 'msg': '保存失败'} + res = request.env['report.report'].sudo().search([('id', '=', int(report_id))], limit=1) + if res: + res.sudo().write({'report_data': json.dumps(report_data)}) + return {'code': 0, 'msg': '保存成功'} + else: + return {'code': 1, 'msg': '保存失败'} + + @http.route('/api/report/report_read', type='json', auth='public', csrf=False, methods=['Post'], website=True,cors="*", my_json=True) + def report_read(self, *args, **kwargs): + + data = dict(request.dispatcher.jsonrequest or {}) + report_id = data.get('report_id', None) + if report_id is None: + return {'code': 1, 'msg': '读取失败'} + res = request.env['report.report'].sudo().search([('id', '=', int(report_id))], limit=1) + if res: + report_data=res.report_data + if res.report_data is False: + report_data =str(json.dumps({'docElements': [], 'parameters': [ + { + 'id': 1, 'name': 'page_count', 'type': 'number', 'arrayItemType': 'string', 'eval': False, + 'nullable': False, 'pattern': '', 'expression': '', 'showOnlyNameType': True, + 'testData': '111' + }, + { + 'id': 2, 'name': 'page_number', 'type': 'number', 'arrayItemType': 'string', 'eval': False, + 'nullable': False, 'pattern': '', 'expression': '', 'showOnlyNameType': True, + 'testData': '222' + }], + 'styles': [], 'version': 3, + 'documentProperties': { + 'pageFormat': 'A4', 'pageWidth': '', 'pageHeight': '', + 'unit': 'mm', 'orientation': 'portrait', 'contentHeight': '', + 'marginLeft': '', 'marginTop': '', 'marginRight': '', + 'marginBottom': '', 'header': True, 'headerSize': '80', + 'headerDisplay': 'always', 'footer': True, 'footerSize': '80', + 'footerDisplay': 'always', 'patternLocale': 'en', + 'patternCurrencySymbol': '$' }})) + + return {'code': 0, 'msg': json.loads(report_data)} + else: + return {'code': 1, 'msg': '模板不存在'} + + @http.route('/api/report/report_server/run', type='http', auth='public', methods=['Get', 'Post'], csrf=False, + website=True, cors="*", my_json=True) + def report_run(self, **kw): + + add_fonts = [dict(value='msyh', filename='msyh.ttf',bold_filename='msyhbd.ttf',italic_filename='msyh.ttf',bold_italic_filename='msyh.ttf')] + method = str(request.httprequest.method) + + if method == 'POST': + json_data = json.loads(kw['data']) + report_definition = json_data.get('report') + output_format = json_data.get('outputFormat') + if output_format not in ('pdf', 'xlsx'): + return {'HttpCode': 400, 'Msg': 'outputFormat parameter missing or invalid'} + data = json_data.get('data') + data=eval(str(json.dumps(data)).replace('False','')) + is_test_data = bool(json_data.get('isTestData')) + try: + report = Report(report_definition, data, is_test_data, additional_fonts=add_fonts) + except Exception as e: + return {'HttpCode': 400, 'Msg': 'failed to initialize report: ' + str(e)} + if report.errors: + return {'HttpCode': 400, 'Msg': 'ReportError: ' + str(report.errors)} + try: + key = str(uuid.uuid4()) + + report_file_pdf = bytes(report.generate_pdf()) + + report_file_xlsx = bytes(report.generate_xlsx()) + + if request.env['wms.print.list'].search_count([('p_key', '=', key)]) == 0: + request.env['wms.print.list'].create({'p_key': key, 'report_file_pdf': report_file_pdf, 'report_file_xlsx': report_file_xlsx}) + return request.make_response('key:' + key, headers=[('Content-Type', 'text/html')]) + except ReportBroError as err: + return {'HttpCode': 400, 'Msg': 'ReportBroError: ' + str(err.error)} + + if method == 'GET': + key = kw['key'] + output_format = kw['outputFormat'] + report = request.env['wms.print.list'].search([('p_key', '=', key)], limit=1) + fname = 'report' + if 'report_id' in kw: + fname = kw['report_id'] + if report: + report_file = report.report_file_pdf + if output_format == 'pdf': + report_headers = [('Content-Type', 'application/pdf'), ('Content-Length', len(report_file)), ( + 'Content-Disposition', + 'inline; filename="%s.pdf"'%(quote(fname)))] + else: + report_file = report.report_file_xlsx + report_headers = [ + ('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'), + ('Content-Length', len(report_file)), ('Content-Disposition', + 'inline; filename="%s.xlsx"'%(quote(fname)))] + return werkzeug.wrappers.Response(report_file, status=200, headers=report_headers) + else: + return {'HttpCode': 400, 'Msg': 'ReportBroError: '} diff --git a/reportbro/models/1.jpg b/reportbro/models/1.jpg new file mode 100644 index 000000000..dbe669013 Binary files /dev/null and b/reportbro/models/1.jpg differ diff --git a/reportbro/models/2.jpg b/reportbro/models/2.jpg new file mode 100644 index 000000000..772208de6 Binary files /dev/null and b/reportbro/models/2.jpg differ diff --git a/reportbro/models/2.png b/reportbro/models/2.png new file mode 100644 index 000000000..b5afd3991 Binary files /dev/null and b/reportbro/models/2.png differ diff --git a/reportbro/models/__init__.py b/reportbro/models/__init__.py new file mode 100644 index 000000000..329f79fbf --- /dev/null +++ b/reportbro/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import datasource,report diff --git a/reportbro/models/datasource.py b/reportbro/models/datasource.py new file mode 100644 index 000000000..85daf4b7f --- /dev/null +++ b/reportbro/models/datasource.py @@ -0,0 +1,61 @@ +import decimal, json, requests, os, uuid +from odoo import models,fields +from odoo.exceptions import UserError +from PyPDF2 import PdfFileWriter, PdfFileReader + + +class report_designer(models.Model): + _name = 'report.designer' + _description = 'report id' + + name=fields.Char('name') + +class wms_datasource(models.TransientModel): + _name = 'wms.datasource' + _description = '报表数据源' + + ''' + 各个报表的数据源,集中写在这里 + ''' + def get_data(self,report_id,data_id,d_main,d_details,m_key): + ''' + 自动获取数据源 + :param report_id: + :param data_id: + :param d_main: + :param d_details: + :param m_key: + :return: + ''' + report_report=self.env['report.report'].sudo().browse(report_id) + report_report_main=self.env['report.report.main'].sudo().search([('report_main_id','=',report_report.id),('is_report','=',True)]) + + #order_id = 0 + res_detail = [] + # 行序号 可以单独控制生成样式 + index = 1 + if d_details: + report_report_detail = self.env['report.report.detail'].sudo().search([('report_detail_id', '=', report_report.id), ('is_report', '=', True)]) + data_line = self.env[d_details].sudo().search([(m_key, 'in', data_id)]) + for s in data_line: + s_detail = {} + s_detail['index'] = str(index) + for report_detail in report_report_detail: + if report_detail.is_report: + s_detail[report_detail.field_params] = str(eval(report_detail.field_value)) + res_detail.append(s_detail) + index += 1 + order_id = int(eval('s.' + m_key + '.id')) + else: + order_id = int(data_id[0]) + # 主表 + p_main = self.env[d_main].sudo().browse(order_id) + data={} + for s in p_main: + for report_main in report_report_main: + if report_main.is_report: + data[report_main.field_params]=str(eval(report_main.field_value)) + + data['res_detail'] = res_detail + return data + diff --git a/reportbro/models/report.py b/reportbro/models/report.py new file mode 100644 index 000000000..c6399ef85 --- /dev/null +++ b/reportbro/models/report.py @@ -0,0 +1,454 @@ +import json, uuid,requests,os +from odoo import models, fields, api +from odoo.exceptions import UserError +from . import datasource +from PyPDF2 import PdfFileWriter, PdfFileReader +FIELD_TYPES = [(key, key) for key in sorted(fields.Field.by_type)] +import tempfile +################# +dir=tempfile.gettempdir() +#dir='/opt/odoo' +login='admin' +password='admin' +################## + +class BaseModelExtend(models.AbstractModel): + _name = 'base.model.extend' + _description = '继承基类模型' + + def _register_hook(self): + + def GetUrl(self): + self._cr.execute("select value from ir_config_parameter where key ='web.base.url'") + dicts = self._cr.dictfetchall() + return str(dicts[0]['value']) + + def getOdooToken(self): + + url = GetUrl(self)+"/web/session/authenticate" + # 传入需要访问的数据库名称,登录账号与密码 + data = { + "params": { + "db": self._cr.dbname, + "login": login, + "password": password, + } + } + + headers = {'Content-type': 'application/json'} + response = requests.post(url, headers=headers, json=data) + return response.cookies["session_id"] + def report_pdf_prints(self, args, types): + ''' + PDF打印公共方法 + :param self: + :return: + ''' + # 模型数据ID + # 模型名称 + report_id = args + print_type = 'pdf' if types == 0 else 'xlsx' + data_id = self._context.get('active_ids', None) + report_report = self.env['report.report'].browse(report_id) + data_model=self._context.get('active_model', None) + data_model_details=report_report.model_detail_id.model if report_report.report_type=='bill' else '' + if data_model is None: + raise UserError('获取数据源失败') + res_id=self.env[data_model].read_group([('id','in',data_id)],[report_report.group_field],[report_report.group_field]) + headers = { + 'Connection': 'keep-alive', + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36', + 'Cookie':'session_id='+getOdooToken(self) + } + + inFileList = [] + for s_code in res_id: + s_id=[] + wheres_id = self.env[data_model].search([('id', 'in', data_id),(report_report.group_field,'=',s_code[report_report.group_field])]) + for wid in wheres_id: + s_id.append(wid.id) + dic = {} + dic['report'] = json.loads(report_report.report_data) + dic['data'] = datasource.wms_datasource.get_data(self,report_id,s_id,data_model,data_model_details,report_report.model_main_key) + dic['outputFormat'] = print_type + dic['isTestData'] = True + res = requests.post(self.GetUrl() + '/api/report/report_server/run', data={'data': str(json.dumps(dic))},headers=headers, allow_redirects=True) + print(res.text) + key = str(res.text.split(':')[1]) + url=self.GetUrl() + '/api/report/report_server/run?key=' + key + '&outputFormat=' + print_type + r = requests.get(url, stream=True,cookies={'session_id':getOdooToken(self)}) + pdf = "/%s.pdf" % key + # 获取temp临时文件路径 + + pdf = dir + pdf + inFileList.append(pdf) + with open(pdf, "wb") as pdf: + for chunk in r.iter_content(chunk_size=1024): + if chunk: + pdf.write(chunk) + filename = dir + "/print.pdf" + pdfFileWriter = PdfFileWriter() + + for inFile in inFileList: + with open(inFile, 'rb') as f: + pdfReader = PdfFileReader(f) + numPages = pdfReader.getNumPages() + for index in range(0, numPages): + pageObj = pdfReader.getPage(index) + pdfFileWriter.addPage(pageObj) + + pdfFileWriter.write(open(filename, 'wb')) + os.unlink(inFile) + with open(filename, 'rb') as pdf_document: + pdf_content = pdf_document.read() + os.unlink(filename) + key_uid = str(uuid.uuid4()) + self.env['wms.print.list'].create({'p_key': key_uid, 'report_file_pdf': pdf_content}) + if print_type == 'pdf': + + #打开新页面 + # return { + # 'type': 'ir.actions.act_url', + # 'title':'打印预览', + # 'name': '打印预览', + # 'url': self.GetUrl() + '/api/report/report_server/run?key=' +key_uid+'&outputFormat=' + print_type + '&report_id='+str(args), + # 'target': 'new' + # + # } + return { + 'type': 'ir.actions.act_window', + 'name': '打印预览', + 'res_model': 'wms.print.list', + 'views': [[False, "form"]], + 'target': 'new', + 'context': { + 'default_name': ' + `; + +actionRegistry.add("report_designer", Dashboard); + + + diff --git a/reportbro/static/src/xml/report_designer.xml b/reportbro/static/src/xml/report_designer.xml new file mode 100644 index 000000000..8b5546c4a --- /dev/null +++ b/reportbro/static/src/xml/report_designer.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/reportbro/templates/report_designer.html b/reportbro/templates/report_designer.html new file mode 100644 index 000000000..3eb3631ae --- /dev/null +++ b/reportbro/templates/report_designer.html @@ -0,0 +1,158 @@ + + + + + + + Index + + + + + + + + + + + + +
+ + diff --git a/reportbro/views/templates.xml b/reportbro/views/templates.xml new file mode 100644 index 000000000..5d17b1337 --- /dev/null +++ b/reportbro/views/templates.xml @@ -0,0 +1,104 @@ + + + + + Report Report Tree + report.report + + + + + + + + + Report Report Form + report.report + +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + 报表设计 + report.report + tree,form + + + + + + wms_print_list + wms.print.list + form + +
+ + + +
+
+ +
+
\ No newline at end of file diff --git a/yuthon_approval/__manifest__.py b/yuthon_approval/__manifest__.py index edf62d46b..c2e215f9f 100644 --- a/yuthon_approval/__manifest__.py +++ b/yuthon_approval/__manifest__.py @@ -18,6 +18,7 @@ 'views/inherit_approval_request_views.xml', 'views/approval_process_views.xml', 'views/demo_approval_request.xml', + 'report/all_demo_report.xml', 'wizard/approval_notice_wizard_views.xml', ], 'installable': True, diff --git a/yuthon_approval/models/demo_approval_request.py b/yuthon_approval/models/demo_approval_request.py index 7c002c352..991f74283 100644 --- a/yuthon_approval/models/demo_approval_request.py +++ b/yuthon_approval/models/demo_approval_request.py @@ -5,7 +5,7 @@ from odoo.exceptions import ValidationError class DemoApprovalRequest(models.Model): _inherit = "approval.request" - _description = '默认审批 demo.approval.request' + _description = '默认审批 approval.request' demo1 = fields.Char(string="demo1") demo2 = fields.Char(string="demo2") diff --git a/yuthon_approval/models/inherit_approval_category.py b/yuthon_approval/models/inherit_approval_category.py index 35e8e25dd..50ea98ca8 100644 --- a/yuthon_approval/models/inherit_approval_category.py +++ b/yuthon_approval/models/inherit_approval_category.py @@ -7,8 +7,9 @@ class ApprovalCategory(models.Model): _description = '审批框架拓展' model_name = fields.Char(string="模型名称") - category_type_number = fields.Char(string="审批类型编号") - approval_process_id = fields.Many2one('approval.process', string='审批流程') + category_type_number = fields.Char(srring='审批视图编号') + approval_department_ids = fields.Many2many('hr.department', string="审批所属部门") + approval_process_id = fields.Many2one('approval.process', string="审批流程") is_enable = fields.Boolean(string="启用", default=True) category_start_date = fields.Date(string="开始日期", default=fields.Date.today()) category_end_date = fields.Date(string="结束日期") diff --git a/yuthon_approval/models/inherit_approval_request.py b/yuthon_approval/models/inherit_approval_request.py index 6987c5c7a..f07df6133 100644 --- a/yuthon_approval/models/inherit_approval_request.py +++ b/yuthon_approval/models/inherit_approval_request.py @@ -6,6 +6,9 @@ from odoo.exceptions import ValidationError class ApprovalRequest(models.Model): _inherit = "approval.request" + def print_approval_report(self): + return self.env.ref('yuthon_approval.all_demo_report_actions').report_action(self) + def action_approve(self): super(ApprovalRequest, self).action_approve() return { diff --git a/yuthon_approval/report/all_demo_report.xml b/yuthon_approval/report/all_demo_report.xml new file mode 100644 index 000000000..5470da522 --- /dev/null +++ b/yuthon_approval/report/all_demo_report.xml @@ -0,0 +1,48 @@ + + + + demo report + approval.request + qweb-pdf + yuthon_approval.all_approval_template + yuthon_approval.all_approval_template + 'Demo - %s' % (object.name).replace('/', '') + + + report + + + + diff --git a/yuthon_approval/views/inherit_approval_category_views.xml b/yuthon_approval/views/inherit_approval_category_views.xml index 5b875e602..373fe639d 100644 --- a/yuthon_approval/views/inherit_approval_category_views.xml +++ b/yuthon_approval/views/inherit_approval_category_views.xml @@ -20,8 +20,9 @@ - - + + + diff --git a/yuthon_approval/views/inherit_approval_request_views.xml b/yuthon_approval/views/inherit_approval_request_views.xml index 994c9202e..75aca1554 100644 --- a/yuthon_approval/views/inherit_approval_request_views.xml +++ b/yuthon_approval/views/inherit_approval_request_views.xml @@ -8,6 +8,10 @@ + +