';
+ }
+ $(this).find('.page-header').remove();
+ }
+
+ // Footer defined for this AR Report?
+ // Note that if the footer of the report is taller than the
+ // margin, the footer will be dismissed
+ var footer_html = '';
+ var footer_height = $(footer_html).outerHeight(true);
+ if ($(this).find('.page-footer').length > 0) {
+ var pgf = $(this).find('.page-footer').first();
+ footer_height = parseFloat($(pgf).outerHeight(true));
+ if (footer_height > mmTopx(dim.marginBottom)) {
+ // Footer too tall
+ footer_html = "";
+ footer_height = parseFloat($(footer_html));
+ } else {
+ footer_html = '';
+ }
+ $(this).find('.page-footer').remove();
+ }
+
+ // Remove undesired and orphan page breaks
+ $(this).find('.page-break').remove();
+ if ($(this).find('div').last().hasClass('manual-page-break')) {
+ $(this).find('div').last().remove();
+ }
+ if ($(this).find('div').first().hasClass('manual-page-break')) {
+ $(this).find('div').first().remove();
+ }
+
+ // Top offset by default. The position in which the report
+ // starts relative to the top of the window. Used later to
+ // calculate when a page-break is needed.
+ var topOffset = $(this).position().top;
+ var maxHeight = mmTopx(dim.height);
+ var elCurrent = null;
+ var elOutHeight = 0;
+ var contentHeight = 0;
+ var pagenum = 1;
+ var pagecounts = Array();
+
+ // Iterate through all div children to find the suitable
+ // page-break points, split the report and add the header
+ // and footer as well as pagination count as required.
+ //
+ // IMPORTANT
+ // Please note that only first-level div elements from
+ // within div.ar_publish_body are checked and will be
+ // treated as nob-breakable elements. So, if a div element
+ // from within a div.ar_publish_body is taller than the
+ // maximum allowed height, that element will be omitted.
+ // Further improvements may solve this and handle deeply
+ // elements from the document, such as tables, etc. Other
+ // elements could be then labeled with "no-break" class to
+ // prevent the system to break them.
+ //console.log("OFF\tABS\tREL\tOUT\tHEI\tMAX");
+ $(this).children('div:visible').each(function(z) {
+
+ // Is the first page?
+ if (elCurrent === null) {
+ // Add page header if required
+ $(header_html).insertBefore($(this));
+ topOffset = $(this).position().top;
+ }
+
+ // Instead of using the height css of each element to
+ // know if the total height at this iteration is above
+ // the maximum health, we use the element's position.
+ // This way, we will prevent underestimations due
+ // non-div elements or plain text directly set inside
+ // the div.ar_publish_body container, not wrapped by
+ // other div element.
+ var elAbsTopPos = $(this).position().top;
+ var elRelTopPos = elAbsTopPos - topOffset;
+ var elNext = $(this).next();
+ elOutHeight = parseFloat($(this).outerHeight(true));
+ if ($(elNext).length > 0) {
+ // Calculate the height of the element according to
+ // the position of the next element instead of
+ // using the outerHeight.
+ elOutHeight = $(elNext).position().top-elAbsTopPos;
+ }
+
+ // The current element is taller than the maximum?
+ if (elOutHeight > maxHeight) {
+ console.warn("Element with id "+$(this).attr('id')+
+ " has a height above the maximum: "+
+ elOutHeight);
+ }
+
+ // Accumulated height
+ contentHeight = elRelTopPos + elOutHeight;
+ /*console.log(Math.floor(topOffset) + "\t" +
+ Math.floor(elAbsTopPos) + "\t" +
+ Math.floor(elRelTopPos) + "\t" +
+ Math.floor(elOutHeight) + "\t" +
+ Math.floor(contentHeight) + "\t" +
+ Math.floor(maxHeight) + "\t" +
+ '#'+$(this).attr('id')+"."+$(this).attr('class'));*/
+
+ if (contentHeight > maxHeight ||
+ $(this).hasClass('manual-page-break')) {
+ // The content is taller than the allowed height
+ // or a manual page break reached. Add a page break.
+ var paddingTopFoot = maxHeight - elRelTopPos;
+ var manualbreak = $(this).hasClass('manual-page-break');
+ var restartcount = manualbreak && $(this).hasClass('restart-page-count');
+ var aboveBreakHtml = "";
+ var pageBreak = "";
+ $(aboveBreakHtml + footer_html + pageBreak + header_html).insertBefore($(this));
+ topOffset = $(this).position().top;
+ if (manualbreak) {
+ $(this).hide();
+ if (restartcount) {
+ // The page count needs to be restarted!
+ pagecounts.push(pagenum);
+ pagenum = 0;
+ }
+ }
+ contentHeight = $(this).outerHeight(true);
+ pagenum += 1;
+ }
+ $(this).css('width', '100%');
+ elCurrent = $(this);
+ });
+
+ // Document end-footer
+ if (elCurrent !== null) {
+ var paddingTopFoot = maxHeight - contentHeight;
+ var aboveBreakHtml = "";
+ var pageBreak = "";
+ pagecounts.push(pagenum);
+ $(aboveBreakHtml + footer_html + pageBreak).insertAfter($(elCurrent));
+ }
+
+ // Wrap all elements in pages
+ var split_at = 'div.page-header';
+ $(this).find(split_at).each(function() {
+ $(this).add($(this).nextUntil(split_at)).wrapAll("");
+ });
+
+ // Move headers and footers out of the wrapping and assign
+ // the top and bottom margins
+ $(this).find('div.page-header').each(function() {
+ var baseheight = $(this).height();
+ $(this).css({'height': pxTomm(baseheight)+"mm",
+ 'margin': 0,
+ 'padding': (pxTomm(mmTopx(dim.marginTop) - baseheight)+"mm 0 0 0")});
+ $(this).parent().before(this);
+ });
+ $(this).find('div.page-break').each(function() {
+ $(this).parent().after(this);
+ });
+ $(this).find('div.page-footer').each(function() {
+ $(this).css({'height': dim.marginBottom+"mm",
+ 'margin': 0,
+ 'padding': 0});
+ $(this).parent().after(this);
+ });
+
+ // Page numbering
+ pagenum = 1;
+ var pagecntidx = 0;
+ $(this).find('.page-current-num,.page-total-count,div.page-break').each(function() {
+ if ($(this).hasClass('page-break')) {
+ if ($(this).hasClass('restart-page-count')) {
+ pagenum = 1;
+ pagecntidx += 1;
+ } else {
+ pagenum = parseInt($(this).attr('data-pagenum')) + 1;
+ }
+ } else if ($(this).hasClass('page-current-num')) {
+ $(this).html(pagenum);
+ } else {
+ $(this).html(pagecounts[pagecntidx]);
+ }
+ });
+ });
+ // Remove manual page breaks
+ $('.manual-page-break').remove();
+ }
+}
+var mmTopx = function(mm) {
+ var px = parseFloat(mm*$('#my_mm').height());
+ return px > 0 ? Math.ceil(px) : Math.floor(px);
+};
+var pxTomm = function(px){
+ var mm = parseFloat(px/$('#my_mm').height());
+ return mm > 0 ? Math.floor(mm) : Math.ceil(mm);
+};
diff --git a/bika/lims/browser/js/bika.lims.utils.calcs.js b/bika/lims/browser/js/bika.lims.utils.calcs.js
index ac3ff715ec..a48f8e388a 100644
--- a/bika/lims/browser/js/bika.lims.utils.calcs.js
+++ b/bika/lims/browser/js/bika.lims.utils.calcs.js
@@ -195,16 +195,7 @@ function CalculationUtils() {
// put result values in their boxes
for(i=0;i<$(data['results']).length;i++){
result = $(data['results'])[i];
- // We have to get the decimals because if they are .0, the will be ignored
- var decimals = 0;
- if (result.result_str.indexOf(".") > -1){
- decimals = result.result_str.split('.')[1].length;
- }
- var result_with_decimals = ""
- if (result.result_str != ""){
- result_with_decimals = parseFloat(result.result_str).toFixed(decimals)
- };
- $("input[uid='"+result.uid+"']").filter("input[field='Result']").val(result_with_decimals);
+ $("input[uid='"+result.uid+"']").filter("input[field='Result']").val(result.result);
$('[type="hidden"]').filter("[field='ResultDM']").filter("[uid='"+result.uid+"']").val(result.dry_result);
$($('[type="hidden"]').filter("[field='ResultDM']").filter("[uid='"+result.uid+"']").siblings()[0]).empty().append(result.dry_result);
@@ -216,7 +207,7 @@ function CalculationUtils() {
$("span[uid='"+result.uid+"']").filter("span[field='formatted_result']").empty().append(result.formatted_result);
// check box
- if (results != '' && results != ""){
+ if (result.result != '' && result.result != ""){
if ($("[id*='cb_"+result.uid+"']").prop("checked") == false) {
$("[id*='cb_"+result.uid+"']").prop('checked', true);
}
diff --git a/bika/lims/browser/js/bika.lims.worksheet.js b/bika/lims/browser/js/bika.lims.worksheet.js
index b168456d15..4366605fa8 100644
--- a/bika/lims/browser/js/bika.lims.worksheet.js
+++ b/bika/lims/browser/js/bika.lims.worksheet.js
@@ -342,25 +342,159 @@ function WorksheetManageResultsView() {
});
}
+ /**
+ * Stores the constraints regarding to methods and instrument assignments to
+ * each analysis. The variable is filled in initializeInstrumentsAndMethods
+ * and is used inside loadMethodEventHandlers.
+ */
+ var mi_constraints = null;
+
+ /**
+ * Applies the rules and constraints to each analysis displayed in the
+ * manage results view regarding to methods, instruments and results.
+ * For example, this service is responsible of disabling the results field
+ * if the analysis has no valid instrument available for the selected
+ * method if the service don't allow manual entry of results. Another
+ * example is that this service is responsible of populating the list of
+ * instruments avialable for an analysis service when the user changes the
+ * method to be used.
+ * See docs/imm_results_entry_behavior.png for detailed information.
+ */
function initializeInstrumentsAndMethods() {
- var instrumentsels = $('table.bika-listing-table select.listing_select_entry[field="Instrument"]');
- $(instrumentsels).each(function() {
- var sel = $(this).val();
- if ($(this).find('option[value=""]').length > 0) {
- $(this).find('option[value=""]').remove();
- $(this).prepend('');
- }
- $(this).val(sel);
+ var auids = [];
+
+ /// Get all the analysis UIDs from this manage results table, cause
+ // we'll need them to retrieve all the IMM constraints/rules to be
+ // applied later.
+ var dictuids = $.parseJSON($('#lab_analyses #item_data, #analyses_form #item_data').val());
+ $.each(dictuids, function(key, value) { auids.push(key); });
+
+ // Retrieve all the rules/constraints to be applied for each analysis
+ // by using an ajax call. The json dictionary returned is assigned to
+ // the variable mi_constraints for further use.
+ // FUTURE: instead of an ajax call to retrieve the dictionary, embed
+ // the dictionary in a div when the bika_listing template is rendered.
+ $.ajax({
+ url: window.portal_url + "/get_method_instrument_constraints",
+ type: 'POST',
+ data: {'_authenticator': $('input[name="_authenticator"]').val(),
+ 'uids': $.toJSON(auids) },
+ dataType: 'json'
+ }).done(function(data) {
+ // Save the constraints in the m_constraints variable
+ mi_constraints = data;
+ $.each(auids, function(index, value) {
+ // Apply the constraints/rules to each analysis.
+ load_analysis_method_constraint(value, null);
+ });
+ }).fail(function() {
+ window.bika.lims.log("bika.lims.worksheet: Something went wrong while retrieving analysis-method-instrument constraints");
});
- var methodsels = $('table.bika-listing-table select.listing_select_entry[field="Method"]');
- $(methodsels).each(function() {
- var sel = $(this).val();
- if ($(this).find('option[value=""]').length > 0) {
- $(this).find('option[value=""]').remove();
- $(this).prepend('');
+ }
+
+ /**
+ * Applies the constraints and rules to the specified analysis regarding to
+ * the method specified. If method is null, the function assumes the rules
+ * must apply for the currently selected method.
+ * The function uses the variable mi_constraints to find out which is the
+ * rule to be applied to the analysis and method specified.
+ * See initializeInstrumentsAndMethods() function for further information
+ * about the constraints and rules retrieval and assignment.
+ * @param {string} analysis_uid - The Analysis UID
+ * @param {string} method_uid - The Method UID. If null, uses the method
+ * that is currently selected for the specified analysis.
+ */
+ function load_analysis_method_constraint(analysis_uid, method_uid) {
+ if (method_uid === null) {
+ // Assume to load the constraints for the currently selected method
+ muid = $('select.listing_select_entry[field="Method"][uid="'+analysis_uid+'"]').val();
+ muid = muid ? muid : '';
+ load_analysis_method_constraint(analysis_uid, muid);
+ return;
+ }
+ andict = mi_constraints[analysis_uid];
+ if (!andict) {
+ return;
+ }
+ constraints = andict[method_uid];
+ if (!constraints || constraints.length < 7) {
+ return;
+ }
+ m_selector = $('select.listing_select_entry[field="Method"][uid="'+analysis_uid+'"]');
+ i_selector = $('select.listing_select_entry[field="Instrument"][uid="'+analysis_uid+'"]');
+
+ // None option in method selector?
+ $(m_selector).find('option[value=""]').remove();
+ if (constraints[1] == 1) {
+ $(m_selector).prepend('');
+ }
+
+ // Select the method
+ $(m_selector).val(method_uid);
+
+ // Method selector visible?
+ // 0: no, 1: yes, 2: label, 3: readonly
+ $(m_selector).prop('disabled', false);
+ $('.method-label[uid="'+analysis_uid+'"]').remove();
+ if (constraints[0] === 0) {
+ $(m_selector).hide();
+ } else if (constraints[0] == 1) {
+ $(m_selector).show();
+ } else if (constraints[0] == 2) {
+ if (andict.length > 1) {
+ $(m_selector).hide();
+ var method_name = $(m_selector).find('option[value="'+method_uid+'"]').innerHtml();
+ $(m_selector).after(''+method_name+'');
}
- $(this).val(sel);
- });
+ } else if (constraints[0] == 3) {
+ //$(m_selector).prop('disabled', true);
+ $(m_selector).show();
+ }
+
+ // Populate instruments list
+ $(i_selector).find('option').remove();
+ console.log(constraints[7]);
+ if (constraints[7]) {
+ $.each(constraints[7], function(key, value) {
+ console.log(key+ ": "+value);
+ $(i_selector).append('');
+ });
+ }
+
+ // None option in instrument selector?
+ if (constraints[3] == 1) {
+ $(i_selector).prepend('');
+ }
+
+ // Select the default instrument
+ $(i_selector).val(constraints[4]);
+
+ // Instrument selector visible?
+ if (constraints[2] === 0) {
+ $(i_selector).hide();
+ } else if (constraints[2] == 1) {
+ $(i_selector).show();
+ }
+
+ // Allow to edit results?
+ if (constraints[5] === 0) {
+ $('.interim input[uid="'+analysis_uid+'"]').val('');
+ $('input[field="Result"][uid="'+analysis_uid+'"]').val('');
+ $('.interim input[uid="'+analysis_uid+'"]').prop('disabled', true);
+ $('input[field="Result"][uid="'+analysis_uid+'"]').prop('disabled', true);
+ } else if (constraints[5] == 1) {
+ $('.interim input[uid="'+analysis_uid+'"]').prop('disabled', false);
+ $('input[field="Result"][uid="'+analysis_uid+'"]').prop('disabled', false);
+ }
+
+ // Info/Warn message?
+ $('.alert-instruments-invalid[uid="'+analysis_uid+'"]').remove();
+ if (constraints[6] && constraints[6] !== '') {
+ $(i_selector).after('');
+ }
+
+ $('.amconstr[uid="'+analysis_uid+'"]').remove();
+ //$(m_selector).before(""+constraints[10]+" ");
}
function loadHeaderEventsHandlers() {
@@ -424,193 +558,9 @@ function WorksheetManageResultsView() {
*/
function loadMethodEventHandlers() {
$('table.bika-listing-table select.listing_select_entry[field="Method"]').change(function() {
- var method = null;
- var service = null;
- var muid = $(this).val();
var auid = $(this).attr('uid');
- var suid = $(this).attr('as_uid');
- var instrselector = $('select.listing_select_entry[field="Instrument"][uid="'+auid+'"]');
- var selectedinstr = $(instrselector).val();
- var m_manualentry = true;
- var s_instrentry = false;
- var qc_analysis = $(this).closest('tr').hasClass('qc-analysis');
- $(instrselector).find('option').remove();
- $(instrselector).prop('disabled', false);
- $('img.alert-instruments-invalid[uid="'+auid+'"]').remove();
- $('.interim input[uid="'+auid+'"]').prop('disabled', false);
- $('.input[field="Result"][uid="'+auid+'"]').prop('disabled', false);
-
- if (muid != '') {
- // Update the instruments selector, but only if the service has AllowInstrumentEntryOfResults enabled.
- // Also, only update with those instruments available for the Analysis Service. If any of the method
- // instruments are available for that Analysis Service, check if the method allows the manual entry
- // of results.
-
- // Is manual entry allowed for this method?
- var request_data = {
- catalog_name: "uid_catalog",
- UID: muid,
- include_fields: ['ManualEntryOfResultsViewField', 'Title']
- };
- window.bika.lims.jsonapi_read(request_data, function(data) {
- method = (data.objects && data.objects.length > 0) ? data.objects[0] : null;
- m_manualentry = (method != null) ? method.ManualEntryOfResultsViewField : true;
- $('.interim input[uid="'+auid+'"]').prop('disabled', !m_manualentry);
- $('.input[field="Result"][uid="'+auid+'"]').prop('disabled', !m_manualentry);
- if (!m_manualentry) {
- // This method doesn't allow the manual entry of Results
- var title = _("Manual entry of results for method ${methodname} is not allowed", {methodname: method.Title});
- $('.input[field="Result"][uid="'+auid+'"]').parent().append('');
- }
-
- // Has the Analysis Service the 'Allow Instrument Entry of Results' enabled?
- var request_data = {
- catalog_name: "uid_catalog",
- UID: suid,
- include_fields: ['InstrumentEntryOfResults']
- };
- window.bika.lims.jsonapi_read(request_data, function(asdata) {
- service = (asdata.objects && asdata.objects.length > 0) ? asdata.objects[0] : null;
- s_instrentry = (service != null) ? service.InstrumentEntryOfResults : false;
- if (!s_instrentry) {
- // The service doesn't allow instrument entry of results.
- // Set instrument selector to None and hide it
- $(instrselector).append("");
- $(instrselector).val('');
- $(instrselector).hide();
- return;
- }
-
- // Get the available instruments for this method and analysis service
- $(instrselector).show();
- $.ajax({
- url: window.portal_url + "/get_method_service_instruments",
- type: 'POST',
- data: {'_authenticator': $('input[name="_authenticator"]').val(),
- 'muid': muid,
- 'suid': suid },
- dataType: 'json'
- }).done(function(idata) {
- var invalid = []
- var valid = false;
-
- // Populate the instrument selector with the instruments retrieved
- $.each(idata, function(index, value) {
- if (value['isvalid'] == true || qc_analysis == true) {
- $(instrselector).append('');
- if (selectedinstr == value['uid']) {
- $(instrselector).val(value['uid'])
- }
- valid = true;
- } else {
- invalid.push(value['title'])
- }
- });
-
- if (!valid) {
- // There isn't any valid instrument found
- $(instrselector).append('');
- $(instrselector).val('');
-
- } else if (m_manualentry) {
- // Some valid instruments found and Manual Entry of Results allowed
- $(instrselector).prepend('');
-
- }
-
- if (invalid.length > 0) {
- // At least one instrument is invalid (out-of-date or qc-fail)
-
- if (valid) {
- // At least one instrument valid found too
- var title = _("Invalid instruments are not shown: ${invalid_list}", {invalid_list: invalid.join(", ")});
- $(instrselector).parent().append('');
-
- } else if (m_manualentry) {
- // All instruments found are invalid, but manual entry is allowed
- var title = _("No valid instruments found: ${invalid_list}", {invalid_list: invalid.join(", ")});
- $(instrselector).parent().append('');
-
- } else {
- // All instruments found are invalid and manual entry not allowed
- var title = _("Manual entry of results for method {methodname} is not allowed and no valid instruments found: ${invalid_list}",
- {methodname: method.Title, invalid_list:invalid.join(", ")});
- $(instrselector).parent().append('');
- $('.interim input[uid="'+auid+'"]').prop('disabled', true);
- $('.input[field="Result"][uid="'+auid+'"]').prop('disabled', true);
- }
- }
-
- }).fail(function() {
- $(instrselector).append('');
- $(instrselector).val("");
- if (!m_manualentry) {
- var title = _("Unable to load instruments: ${invalid_list}", {invalid_list: invalid.join(", ")});
- $(instrselector).parent().append('');
- $(instrselector).prop('disabled', true);
- } else {
- $(instrselector).prop('disabled', false);
- }
- });
-
- });
- });
-
- } else {
- // No method selected. Which are the instruments assigned to the analysis service and without any method assigned?
- $.ajax({
- url: window.portal_url + "/get_method_service_instruments",
- type: 'POST',
- data: {'_authenticator': $('input[name="_authenticator"]').val(),
- 'muid': '0',
- 'suid': suid },
- dataType: 'json'
- }).done(function(idata) {
- var invalid = []
- var valid = false;
-
- // Populate the instrument selector with the instruments retrieved
- $.each(idata, function(index, value) {
- if (value['isvalid'] == true) {
- $(instrselector).append('');
- if (selectedinstr == value['uid']) {
- $(instrselector).val(value['uid'])
- }
- valid = true;
- } else {
- invalid.push(value['title'])
- }
- });
-
- if (!valid) {
- // There isn't any valid instrument found
- $(instrselector).append('');
- $(instrselector).val('');
- } else {
- // Some valid instruments found and Manual Entry of Results allowed
- $(instrselector).prepend('');
- }
-
- if (invalid.length > 0) {
- // At least one instrument is invalid (out-of-date or qc-fail)
- if (valid) {
- // At least one instrument valid found too
- var title = _("Invalid instruments are not shown: ${invalid_list}", {invalid_list: invalid.join(", ")});
- $(instrselector).parent().append('');
- } else {
- // All instruments found are invalid
- var title = _("No valid instruments found: ${invalid_list}", {invalid_list: invalid.join(", ")});
- $(instrselector).parent().append('');
- }
- }
- }).fail(function() {
- $(instrselector).append('');
- $(instrselector).val('');
- var title = _("Unable to load instruments: ${invalid_list}", {invalid_list: invalid.join(", ")});
- $(instrselector).parent().append('');
- $(instrselector).prop('disabled', true);
- });
- }
+ var muid = $(this).val();
+ load_analysis_method_constraint(auid, muid);
});
}
}
diff --git a/bika/lims/browser/samplinground/configure.zcml b/bika/lims/browser/samplinground/configure.zcml
index 5a9a7171a8..53a13499cf 100644
--- a/bika/lims/browser/samplinground/configure.zcml
+++ b/bika/lims/browser/samplinground/configure.zcml
@@ -26,5 +26,12 @@
layer="bika.lims.interfaces.IBikaLIMS"
/>
+
diff --git a/bika/lims/browser/samplinground/printform.py b/bika/lims/browser/samplinground/printform.py
new file mode 100644
index 0000000000..37bf42de6b
--- /dev/null
+++ b/bika/lims/browser/samplinground/printform.py
@@ -0,0 +1,266 @@
+from bika.lims import bikaMessageFactory as _, t
+from Products.CMFCore.utils import getToolByName
+from Products.CMFPlone.utils import safe_unicode
+from bika.lims.utils import to_utf8, createPdf
+from bika.lims.browser import BrowserView
+from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
+from plone.resource.utils import iterDirectoriesOfType, queryResourceDirectory
+import App
+import tempfile
+import os
+import glob
+import traceback
+
+
+class PrintForm(BrowserView):
+ template = ViewPageTemplateFile("templates/print_form.pt")
+ _DEFAULT_TEMPLATE = 'default_form.pt'
+ _TEMPLATES_DIR = 'templates/print'
+ _TEMPLATES_ADDON_DIR = 'samplingrounds'
+ _current_sr_index = 0
+ _samplingrounds = []
+
+ def __call__(self):
+ if self.context.portal_type == 'SamplingRound':
+ self._samplingrounds = [self.context]
+
+ elif self.context.portal_type == 'SamplingRounds' \
+ and self.request.get('items', ''):
+ uids = self.request.get('items').split(',')
+ uc = getToolByName(self.context, 'uid_catalog')
+ self._samplingrounds = [obj.getObject() for obj in uc(UID=uids)]
+
+ else:
+ # Warn and redirect to referer
+ logger.warning('PrintView: type not allowed: %s' %
+ self.context.portal_type)
+ self.destination_url = self.request.get_header("referer",
+ self.context.absolute_url())
+
+ # Do print?
+ if self.request.form.get('pdf', '0') == '1':
+ response = self.request.response
+ response.setHeader("Content-type", "application/pdf")
+ response.setHeader("Content-Disposition", "inline")
+ response.setHeader("filename", "temp.pdf")
+ return self.pdfFromPOST()
+ else:
+ return self.template()
+
+ def getSamplingRoundObj(self):
+ """Returns the sampling round object
+ """
+ return self.context
+
+ def getSRTemplates(self):
+ """
+ Returns a DisplayList with the available templates found in
+ browser/samplinground/templates/
+ """
+ this_dir = os.path.dirname(os.path.abspath(__file__))
+ templates_dir = os.path.join(this_dir, self._TEMPLATES_DIR)
+ tempath = '%s/%s' % (templates_dir, '*.pt')
+ templates = [t.split('/')[-1] for t in glob.glob(tempath)]
+ out = []
+ for template in templates:
+ out.append({'id': template, 'title': template[:-3]})
+ for templates_resource in iterDirectoriesOfType(self._TEMPLATES_ADDON_DIR):
+ prefix = templates_resource.__name__
+ templates = [
+ tpl for tpl in templates_resource.listDirectory()
+ if tpl.endswith('.pt')
+ ]
+ for template in templates:
+ out.append({
+ 'id': '{0}:{1}'.format(prefix, template),
+ 'title': '{0} ({1})'.format(template[:-3], prefix),
+ })
+ return out
+
+ def getFormTemplate(self):
+ """Returns the current samplinground rendered with the template
+ specified in the request (param 'template').
+ Moves the iterator to the next samplinground available.
+ """
+ templates_dir = self._TEMPLATES_DIR
+ embedt = self.request.get('template', self._DEFAULT_TEMPLATE)
+ if embedt.find(':') >= 0:
+ prefix, embedt = embedt.split(':')
+ templates_dir = queryResourceDirectory(self._TEMPLATES_ADDON_DIR, prefix).directory
+ embed = ViewPageTemplateFile(os.path.join(templates_dir, embedt))
+ reptemplate = ""
+ try:
+ reptemplate = embed(self)
+ except:
+ tbex = traceback.format_exc()
+ wsid = self._samplingrounds[self._current_sr_index].id
+ reptemplate = "
%s - %s '%s':
%s
" % (wsid, _("Unable to load the template"), embedt, tbex)
+ if self._current_sr_index < len(self._samplingrounds):
+ self._current_sr_index += 1
+ return reptemplate
+
+ def getCSS(self):
+ """ Returns the css style to be used for the current template.
+ If the selected template is 'default.pt', this method will
+ return the content from 'default.css'. If no css file found
+ for the current template, returns empty string
+ """
+ template = self.request.get('template', self._DEFAULT_TEMPLATE)
+ content = ''
+ if template.find(':') >= 0:
+ prefix, template = template.split(':')
+ resource = queryResourceDirectory(
+ self._TEMPLATES_ADDON_DIR, prefix)
+ css = '{0}.css'.format(template[:-3])
+ if css in resource.listDirectory():
+ content = resource.readFile(css)
+ else:
+ this_dir = os.path.dirname(os.path.abspath(__file__))
+ templates_dir = os.path.join(this_dir, self._TEMPLATES_DIR)
+ path = '%s/%s.css' % (templates_dir, template[:-3])
+ with open(path, 'r') as content_file:
+ content = content_file.read()
+ return content
+
+ def getAnalysisRequestTemplatesInfo(self):
+ """
+ Returns a lost of dicts with the analysis request templates infomration
+ [{'uid':'xxxx','id':'xxxx','title':'xxx','url':'xxx'}, ...]
+ """
+ arts_list = []
+ for art in self.context.ar_templates:
+ pc = getToolByName(self.context, 'portal_catalog')
+ contentFilter = {'portal_type': 'ARTemplate',
+ 'UID': art}
+ art_brain = pc(contentFilter)
+ if len(art_brain) == 1:
+ art_obj = art_brain[0].getObject()
+ arts_list.append({
+ 'uid': art_obj.UID(),
+ 'id': art_obj.id,
+ 'title': art_obj.title,
+ 'url': art_obj.absolute_url(),
+ })
+ return arts_list
+
+ def getAnalysisRequestBySample(self):
+ """
+ Returns a list of dictionaries sorted by Sample Partition/Container
+ [{'requests and partition info'}, ...]
+ """
+ # rows will contain the data for each html row
+ rows = []
+ # columns will be used to sort and define the columns
+ columns = {
+ 'column_order': [
+ 'sample_id',
+ 'sample_type',
+ 'sampling_point',
+ 'sampling_date',
+ 'partition',
+ 'container',
+ 'analyses',
+ ],
+ 'titles': {
+ 'sample_id': _('Sample ID'),
+ 'sample_type': _('Sample Type'),
+ 'sampling_point': _('Sampling Point'),
+ 'sampling_date': _('Sampling Date'),
+ 'partition': _('Partition'),
+ 'container': _('Container'),
+ 'analyses': _('Analysis'),
+ }
+ }
+ ars = self.context.getAnalysisRequests()
+ for ar in ars:
+ ar = ar.getObject()
+ arcell = False
+ numans = len(ar.getAnalyses())
+ for part in ar.getPartitions():
+ partcell = False
+ container = part.getContainer().title \
+ if part.getContainer() else ''
+ partans = part.getAnalyses()
+ numpartans = len(partans)
+ for analysis in partans:
+ service = analysis.getService()
+ row = {
+ 'sample_id': {
+ 'hidden': True if arcell else False,
+ 'rowspan': numans,
+ 'value': ar.getSample().id,
+ },
+ 'sample_type': {
+ 'hidden': True if arcell else False,
+ 'rowspan': numans,
+ 'value': ar.getSampleType().title,
+ },
+ 'sampling_point': {
+ 'hidden': True if arcell else False,
+ 'rowspan': numans,
+ 'value': ar.getSamplePoint().title if ar.getSamplePoint() else '',
+ },
+ 'sampling_date': {
+ 'hidden': True if arcell else False,
+ 'rowspan': numans,
+ 'value': self.context.sampling_date,
+ },
+ 'partition': {
+ 'hidden': True if partcell else False,
+ 'rowspan': numpartans,
+ 'value': part.id,
+ },
+ 'container': {
+ 'hidden': True if partcell else False,
+ 'rowspan': numpartans,
+ 'value': container,
+ },
+ 'analyses': {
+ 'title': service.title,
+ 'units': service.getUnit(),
+ },
+ }
+ rows.append(row)
+ arcell = True
+ partcell = True
+
+ # table will contain the data that from where the html
+ # will take the info
+ table = {
+ 'columns': columns,
+ 'rows': rows,
+ }
+ return table
+
+ def getLab(self):
+ return self.context.bika_setup.laboratory.getLabURL()
+
+ def getLogo(self):
+ portal = self.context.portal_url.getPortalObject()
+ return "%s/logo_print.png" % portal.absolute_url()
+
+ def pdfFromPOST(self):
+ """
+ It returns the pdf for the sampling rounds printed
+ """
+ html = self.request.form.get('html')
+ style = self.request.form.get('style')
+ reporthtml = "%s
%s" % (style, html)
+ return self.printFromHTML(safe_unicode(reporthtml).encode('utf-8'))
+
+ def printFromHTML(self, sr_html):
+ """
+ Tis function generates a pdf file from the html
+ :sr_html: the html to use to generate the pdf
+ """
+ # HTML written to debug file
+ debug_mode = App.config.getConfiguration().debug_mode
+ if debug_mode:
+ tmp_fn = tempfile.mktemp(suffix=".html")
+ open(tmp_fn, "wb").write(sr_html)
+
+ # Creates the pdf
+ # we must supply the file ourself so that createPdf leaves it alone.
+ pdf_fn = tempfile.mktemp(suffix=".pdf")
+ pdf_report = createPdf(htmlreport=sr_html, outfile=pdf_fn)
+ return pdf_report
diff --git a/bika/lims/browser/samplinground/templates/print/default_form.css b/bika/lims/browser/samplinground/templates/print/default_form.css
new file mode 100644
index 0000000000..96c36c588c
--- /dev/null
+++ b/bika/lims/browser/samplinground/templates/print/default_form.css
@@ -0,0 +1,60 @@
+
+.barcode-container {
+ width:100%;
+}
+div.report_body {
+ font-size:0.8em;
+}
+div.report_body a {
+ color:#000;
+ text-decoration:none;
+}
+div.report_body h1 {
+ padding-top:15px;
+ font-size:1.7em;
+}
+div#title{
+ font-size:1.5em;
+}
+.label {
+ font-weight: bold;
+}
+.table-text {
+ position: relative;
+}
+.data-input{
+ border: 1px solid #cdcdcd;
+ height: 20px;
+}
+table.samples-grid tr td {
+ vertical-align:top;
+ border: 1px solid #cdcdcd;
+ padding: 5px;
+}
+table thead th{
+ border: 1px solid #cdcdcd;
+ background-color: #808080;
+}
+#sampling-round-info div {
+ float: left;
+ font-size: 0.85em;
+ position: relative;
+ width: 150px;
+}
+span.units {
+ font-size: 0.7em;
+}
+div.lab-logo {
+ float: right;
+ position: relative;
+}
+p {
+ margin:0;
+ padding:0;
+}
+.clearfix {
+ clear:both;
+}
+#sampling-round-data-entry {
+ clear: both;
+}
diff --git a/bika/lims/browser/samplinground/templates/print/default_form.pt b/bika/lims/browser/samplinground/templates/print/default_form.pt
new file mode 100644
index 0000000000..94eb5cc1db
--- /dev/null
+++ b/bika/lims/browser/samplinground/templates/print/default_form.pt
@@ -0,0 +1,134 @@
+
+
+
+