199 lines
6.6 KiB
Python
199 lines
6.6 KiB
Python
# -*- coding: utf-8; -*-
|
|
################################################################################
|
|
#
|
|
# Rattail -- Retail Software Framework
|
|
# Copyright © 2010-2023 Lance Edgar
|
|
#
|
|
# This file is part of Rattail.
|
|
#
|
|
# Rattail 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 3 of the License, or (at your option) any later
|
|
# version.
|
|
#
|
|
# Rattail 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
|
|
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
################################################################################
|
|
"""
|
|
Report Definitions
|
|
"""
|
|
|
|
from __future__ import unicode_literals, absolute_import
|
|
|
|
from openpyxl.styles import PatternFill
|
|
|
|
from rattail.reporting import Report
|
|
from rattail.excel import ExcelWriter
|
|
from rattail.time import localtime
|
|
|
|
|
|
class ExcelReport(Report):
|
|
"""
|
|
Generic report which knows how to write Excel output file.
|
|
|
|
.. attr:: output_fields
|
|
|
|
Simple list of field names which should be included in the output file.
|
|
|
|
.. attr:: number_formats
|
|
|
|
Optional dictionary specifying "number formats" for any fields. Use the
|
|
field name for dict key, and value should be the Excel-specific number
|
|
format to be applied to all that column's values.
|
|
|
|
.. attr:: totalled_fields
|
|
|
|
Optional list of fields which should be "totalled" and represented in a
|
|
final totals row within the output.
|
|
|
|
.. attr:: write_data_header
|
|
|
|
Boolean flag (true by default) indicating that a header row
|
|
with field names should be written to the data sheet. Set this
|
|
to false if you are using a template file.
|
|
|
|
.. attr:: auto_format_data
|
|
|
|
Boolean flag (true by default) indicating that certain
|
|
"auto-formatting" should be applied to the data sheet.
|
|
|
|
.. attr:: include_summary_sheet
|
|
|
|
Boolean flag (true by default) indicating that a second
|
|
"summary" sheet should be added to the output file.
|
|
"""
|
|
output_fields = []
|
|
number_formats = {}
|
|
totalled_fields = []
|
|
write_data_header = True
|
|
auto_format_data = True
|
|
include_summary_sheet = True
|
|
|
|
def make_filename(self, session, params, data, **kwargs):
|
|
return "{}.xlsx".format(self.name)
|
|
|
|
def get_output_fields(self, params):
|
|
return list(self.output_fields)
|
|
|
|
def make_excel_writer(self, path, params={}, **kwargs):
|
|
"""
|
|
Create the Excel writer instance, for the given path.
|
|
"""
|
|
if 'fields' in kwargs:
|
|
fields = kwargs.pop('fields')
|
|
else:
|
|
fields = self.get_output_fields(params)
|
|
kwargs.setdefault('number_formats', self.number_formats)
|
|
kwargs.setdefault('sheet_title', "Report Data")
|
|
return ExcelWriter(path, fields, **kwargs)
|
|
|
|
def write_data_sheet(self, writer, session, params, data,
|
|
sheet=None, progress=None, **kwargs):
|
|
"""
|
|
Write the primary data sheet for the Excel output file.
|
|
"""
|
|
if not sheet:
|
|
sheet = writer.sheet
|
|
|
|
if self.write_data_header:
|
|
writer.write_header(sheet=sheet)
|
|
fields = self.get_output_fields(params)
|
|
|
|
# convert data to Excel-compatible rows
|
|
data_rows = data if isinstance(data, list) else data['rows']
|
|
xlrows = [
|
|
[row[field] for field in fields]
|
|
for row in data_rows]
|
|
|
|
# write main data rows
|
|
writer.write_rows(xlrows, progress=progress)
|
|
|
|
# maybe add a TOTALS row
|
|
totals = {}
|
|
for field in self.totalled_fields:
|
|
totals[field] = sum([row[field] for row in data_rows])
|
|
if totals:
|
|
|
|
# create totals row data
|
|
rowdata = []
|
|
for field in fields:
|
|
if field in totals:
|
|
rowdata.append(totals[field])
|
|
else:
|
|
rowdata.append(None)
|
|
|
|
# append row to output
|
|
writer.write_row(rowdata, row=len(data_rows) + 2, sheet=sheet)
|
|
|
|
# apply row highlighting
|
|
fill_totals = PatternFill(patternType='solid',
|
|
fgColor='ffee88',
|
|
bgColor='ffee88')
|
|
for col, field in enumerate(fields, 1):
|
|
cell = sheet.cell(row=len(data_rows) + 2, column=col)
|
|
cell.fill = fill_totals
|
|
|
|
if self.auto_format_data:
|
|
writer.auto_freeze()
|
|
writer.auto_filter()
|
|
writer.auto_resize()
|
|
|
|
def write_data_sheets(self, writer, session, params, data,
|
|
progress=None, **kwargs):
|
|
"""
|
|
Write all data sheets for the report.
|
|
"""
|
|
# normally just write one data sheet
|
|
self.write_data_sheet(writer, session, params, data,
|
|
progress=progress, **kwargs)
|
|
|
|
def write_summary_sheet(self, writer, session, params, data,
|
|
progress=None, **kwargs):
|
|
"""
|
|
Write the secondary "summary" sheet for the Excel output file.
|
|
"""
|
|
app = self.config.get_app()
|
|
now = localtime(self.config)
|
|
|
|
summary = writer.create_sheet("Report Summary")
|
|
|
|
summary.append([])
|
|
summary.append([self.name])
|
|
|
|
summary.append([])
|
|
summary.append(["Generated:"])
|
|
summary.append([app.render_datetime(now)])
|
|
|
|
summary.append([])
|
|
summary.append(["Parameters:"])
|
|
for key, value in params.items():
|
|
summary.append([key, value])
|
|
|
|
self.add_more_to_summary(writer, summary, session, params, data)
|
|
|
|
writer.disable_grid_lines()
|
|
writer.auto_resize()
|
|
|
|
def add_more_to_summary(self, writer, summary, session, params, data):
|
|
pass
|
|
|
|
def write_file(self, session, params, data, path, progress=None, **kwargs):
|
|
"""
|
|
Write a basic Excel output file with the given data. Requires at least
|
|
the ``output_fields`` attribute to be set to work correctly.
|
|
"""
|
|
writer = self.make_excel_writer(path, params=params)
|
|
|
|
self.write_data_sheets(writer, session, params, data, progress=progress)
|
|
|
|
if self.include_summary_sheet:
|
|
self.write_summary_sheet(writer, session, params, data,
|
|
progress=progress)
|
|
|
|
writer.save(progress=progress)
|