Migrate to Tryton 4.8

This commit is contained in:
Sebastián Marró 2018-05-23 20:47:26 -03:00
parent 30f3a183df
commit 1630b66fbb
137 changed files with 3655 additions and 4095 deletions

View File

@ -1 +0,0 @@
Version 4.4.0 - 2017-10-08

View File

@ -2248,7 +2248,6 @@ class CreateAnalysisProduct(Wizard):
Template = pool.get('product.template')
TemplateCategory = pool.get('product.template-product.category')
Uom = pool.get('product.uom')
# Distribution = pool.get('analytic_account.distribution')
Lang = pool.get('ir.lang')
Config = pool.get('lims.configuration')
@ -2278,18 +2277,6 @@ class CreateAnalysisProduct(Wizard):
template.account_category = config_.analysis_product_category.id
template.accounts_category = True
if analysis.behavior != 'additional':
if analysis.type != 'group':
laboratory = analysis.laboratories[0].laboratory
else:
laboratory = analysis.included_analysis[0].laboratory
# analytic_distribution = Distribution.search([
# ('code', '=', laboratory.code),
# ])
# if analytic_distribution:
# template.analytic_distribution = analytic_distribution[0]
template.save()
template_category = TemplateCategory()

View File

@ -1,5 +0,0 @@
# This file is part of lims module for Tryton.
# The COPYRIGHT file at the top level of this repository contains
# the full copyright notices and license terms.
from .lims_report import *

View File

@ -1 +0,0 @@
Version 4.4.0 - 2017-10-08

View File

@ -1 +0,0 @@
Version 4.4.0 - 2017-10-08

View File

@ -1,5 +1,5 @@
Copyright (C) 2017 Ignacio Parszyk <iparszyk@kalenislims.com>
Copyright (C) 2015-2017 Sebastián Marró <smarro@kalenislims.com>
Copyright (C) 2017-2018 Ignacio Parszyk <iparszyk@kalenislims.com>
Copyright (C) 2015-2018 Sebastián Marró <smarro@kalenislims.com>
Copyright (C) 2015-2016 Luis Falcon <falcon@gnu.org>
This program is free software: you can redistribute it and/or modify

View File

@ -3,12 +3,12 @@
# the full copyright notices and license terms.
from trytond.pool import Pool
from .lims import *
from .stock import *
from . import lims
from . import stock
def register():
Pool.register(
Location,
Move,
lims.Location,
stock.Move,
module='lims_analytic', type_='model')

View File

@ -1,5 +1,5 @@
[tryton]
version=4.4.0
version=4.8.0
depends:
lims
analytic_account

View File

@ -1 +0,0 @@
Version 4.4.0 - 2017-10-08

View File

@ -1,5 +1,5 @@
Copyright (C) 2017 Ignacio Parszyk <iparszyk@kalenislims.com>
Copyright (C) 2015-2017 Sebastián Marró <smarro@kalenislims.com>
Copyright (C) 2017-2018 Ignacio Parszyk <iparszyk@kalenislims.com>
Copyright (C) 2015-2018 Sebastián Marró <smarro@kalenislims.com>
Copyright (C) 2015 Bruno M. Villasanti <bvillasanti@thymbra.com>
Copyright (C) 2015-2016 Luis Falcon <falcon@gnu.org>

View File

@ -3,21 +3,21 @@
# the full copyright notices and license terms.
from trytond.pool import Pool
from .configuration import *
from .lims import *
from wizard import *
from . import configuration
from . import lims
from . import digital_sign
def register():
Pool.register(
LimsConfiguration,
LimsResultsReportVersionDetail,
LimsResultsReport,
DigitalSignStart,
DigitalSignSucceed,
DigitalSignFailed,
configuration.Configuration,
lims.ResultsReportVersionDetail,
lims.ResultsReport,
digital_sign.DigitalSignStart,
digital_sign.DigitalSignSucceed,
digital_sign.DigitalSignFailed,
module='lims_digital_sign', type_='model')
Pool.register(
LimsResultsReportAnnulation,
DigitalSign,
lims.ResultsReportAnnulation,
digital_sign.DigitalSign,
module='lims_digital_sign', type_='wizard')

View File

@ -6,10 +6,10 @@
from trytond.model import fields
from trytond.pool import PoolMeta
__all__ = ['LimsConfiguration']
__all__ = ['Configuration']
class LimsConfiguration:
class Configuration:
__name__ = 'lims.configuration'
__metaclass__ = PoolMeta

View File

@ -1,6 +1,4 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>

View File

@ -9,8 +9,8 @@ from trytond.model import ModelView, fields
from trytond.wizard import Wizard, StateView, StateTransition, Button
from trytond.pool import Pool
from trytond.transaction import Transaction
from ..lims import HAS_PDFMERGER
from ..lims import HAS_TOKEN
from .lims import HAS_PDFMERGER
from .lims import HAS_TOKEN
__all__ = ['DigitalSignStart', 'DigitalSignSucceed', 'DigitalSignFailed',
'DigitalSign']
@ -61,19 +61,19 @@ class DigitalSign(Wizard):
'''
logger = logging.getLogger('lims_digital_sign')
logger.info('Wizard - Digital Sign:INIT')
LimsResultsReport = Pool().get('lims.results_report')
ResultsReport = Pool().get('lims.results_report')
if not HAS_PDFMERGER:
LimsResultsReport.raise_user_error('missing_module')
ResultsReport.raise_user_error('missing_module')
if not HAS_TOKEN:
LimsResultsReport.raise_user_error('missing_module_token')
ResultsReport.raise_user_error('missing_module_token')
context = Transaction().context
model = context.get('active_model', None)
if model and model == 'ir.ui.menu':
# If it was executed from `menu item`, then get ids
# TODO: Include signed but not sent?
active_ids = [r.id for r in LimsResultsReport.search([
active_ids = [r.id for r in ResultsReport.search([
('signed', '=', False)])]
logger.info('Wizard - Digital Sign:Processing all Results '
'Reports')
@ -86,7 +86,7 @@ class DigitalSign(Wizard):
unsigned_reports = []
unsent_reports = []
for active_id in active_ids:
results_report = LimsResultsReport(active_id)
results_report = ResultsReport(active_id)
logger.info('Wizard - Digital Sign:results_report.number:%s',
results_report.number)
if (results_report.single_sending_report and not

View File

@ -0,0 +1,121 @@
<?xml version="1.0"?>
<tryton>
<data>
<!-- Groups -->
<record model="res.group" id="group_lims_digital_sign">
<field name="name">Lims Digital Sign</field>
</record>
<record model="res.user-res.group" id="user_admin_group_lims_digital_sign">
<field name="user" ref="res.user_admin"/>
<field name="group" ref="group_lims_digital_sign"/>
</record>
<!-- Users -->
<record model="res.user" id="user_digital_sign">
<field name="login">user_cron_digital_sign</field>
<field name="name">Cron Lims Digital Sign</field>
<field name="signature"></field>
<field name="active" eval="False"/>
</record>
<record model="res.user-res.group" id="user_digital_sign_group_digital_sign">
<field name="user" ref="user_digital_sign"/>
<field name="group" ref="group_lims_digital_sign"/>
</record>
<!-- Cron -->
<record model="ir.cron" id="cron_lims_digital_sign_results_reports">
<field name="name">Lims Digital Sign Results Reports</field>
<field name="request_user" ref="res.user_admin"/>
<field name="user" ref="user_digital_sign"/>
<field name="active" eval="True"/>
<field name="interval_number" eval="1"/>
<field name="interval_type">days</field>
<field name="number_calls" eval="-1"/>
<field name="repeat_missed" eval="False"/>
<field name="model">lims.results_report</field>
<field name="function">cron_digital_signs</field>
</record>
<!-- Wizard Digital Sign -->
<record model="ir.ui.view" id="view_digital_sign_start">
<field name="model">lims_digital_sign.digital_sign.start</field>
<field name="type">form</field>
<field name="name">digital_sign_start_form</field>
</record>
<record model="ir.ui.view" id="view_digital_sign_succeed">
<field name="model">lims_digital_sign.digital_sign.succeed</field>
<field name="type">form</field>
<field name="name">digital_sign_succeed_form</field>
</record>
<record model="ir.ui.view" id="view_digital_sign_failed">
<field name="model">lims_digital_sign.digital_sign.failed</field>
<field name="type">form</field>
<field name="name">digital_sign_failed_form</field>
</record>
<record model="ir.action.wizard" id="wizard_digital_sign">
<field name="name">Digital Sign</field>
<field name="wiz_name">lims_digital_sign.digital_sign</field>
</record>
<record model="ir.action.keyword" id="wizard_digital_sign_keyword">
<field name="keyword">form_action</field>
<field name="model">lims.results_report,-1</field>
<field name="action" ref="wizard_digital_sign"/>
</record>
<!-- Access Rights on Menu -->
<!-- Laboratory / Results reports / Digital Sign -->
<record model="ir.ui.menu-res.group" id="menu_laboratory_group_digital_sign">
<field name="menu" ref="lims.lims_laboratory"/>
<field name="group" ref="group_lims_digital_sign"/>
</record>
<record model="ir.ui.menu-res.group" id="menu_laboratory_reports_group_digital_sign">
<field name="menu" ref="lims.lims_laboratory_reports"/>
<field name="group" ref="group_lims_digital_sign"/>
</record>
<!-- Access Rights on Actions -->
<record model="ir.action-res.group" id="wizard_digital_sign_group_digital_sign">
<field name="action" ref="wizard_digital_sign"/>
<field name="group" ref="group_lims_digital_sign"/>
</record>
<!-- Lims -->
<record model="ir.ui.view" id="lims_results_report_view_list">
<field name="model">lims.results_report</field>
<field name="inherit" ref="lims.lims_results_report_view_list"/>
<field name="name">results_report_list</field>
</record>
<!-- Icons -->
<record model="ir.ui.icon" id="digital_sign_icon">
<field name="name">lims-digital_sign</field>
<field name="path">icons/digital_sign.svg</field>
</record>
<!-- Menu -->
<!-- Laboratory / Results reports / Digital Sign -->
<menuitem parent="lims.lims_laboratory_reports" action="wizard_digital_sign"
id="menu_digital_sign" sequence="40"
icon="lims-digital_sign"/>
<record model="ir.ui.menu-res.group" id="menu_digital_sign_group_digital_sign">
<field name="menu" ref="menu_digital_sign"/>
<field name="group" ref="group_lims_digital_sign"/>
</record>
</data>
</tryton>

View File

@ -1,23 +0,0 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<!-- Cron -->
<record model="ir.cron" id="cron_lims_digital_sign_results_reports">
<field name="name">Lims Digital Sign Results Reports</field>
<field name="request_user" ref="res.user_admin"/>
<field name="user" ref="user_digital_sign"/>
<field name="active" eval="True"/>
<field name="interval_number" eval="1"/>
<field name="interval_type">days</field>
<field name="number_calls" eval="-1"/>
<field name="repeat_missed" eval="False"/>
<field name="model">lims.results_report</field>
<field name="function">cron_digital_signs</field>
</record>
</data>
</tryton>

View File

@ -19,7 +19,8 @@ from trytond.transaction import Transaction
from trytond.tools import get_smtp_server
from trytond.config import config as tconfig
__all__ = ['LimsResultsReportVersionDetail', 'LimsResultsReport']
__all__ = ['ResultsReportVersionDetail', 'ResultsReport',
'ResultsReportAnnulation']
HAS_PDFMERGER = False
try:
@ -42,7 +43,7 @@ except ImportError:
exc_info=True)
class LimsResultsReportVersionDetail:
class ResultsReportVersionDetail:
__name__ = 'lims.results_report.version.detail'
__metaclass__ = PoolMeta
@ -62,19 +63,19 @@ class LimsResultsReportVersionDetail:
@classmethod
@ModelView.button
def revise(cls, details):
super(LimsResultsReportVersionDetail, cls).revise(details)
super(ResultsReportVersionDetail, cls).revise(details)
for detail in details:
detail.unsign()
@classmethod
@ModelView.button
def revise_all_lang(cls, details):
super(LimsResultsReportVersionDetail, cls).revise_all_lang(details)
super(ResultsReportVersionDetail, cls).revise_all_lang(details)
for detail in details:
detail.unsign()
class LimsResultsReport:
class ResultsReport:
__name__ = 'lims.results_report'
__metaclass__ = PoolMeta
@ -85,7 +86,7 @@ class LimsResultsReport:
@classmethod
def __setup__(cls):
super(LimsResultsReport, cls).__setup__()
super(ResultsReport, cls).__setup__()
cls._error_messages.update({
'missing_module_token': 'Missing tokenclient module',
'polisample': 'Polisample',
@ -93,7 +94,7 @@ class LimsResultsReport:
@classmethod
def _get_modified_fields(cls):
fields = super(LimsResultsReport, cls)._get_modified_fields()
fields = super(ResultsReport, cls)._get_modified_fields()
fields.extend([
'signed',
'signed_date',
@ -110,16 +111,16 @@ class LimsResultsReport:
logging.getLogger('lims_digital_sign').info(
'Cron - Digital Signs:INIT')
pool = Pool()
LimsResultsReport = pool.get('lims.results_report')
ResultsReport = pool.get('lims.results_report')
DigitalSign = pool.get('lims_digital_sign.digital_sign', type='wizard')
if not HAS_PDFMERGER:
LimsResultsReport.raise_user_error('missing_module')
ResultsReport.raise_user_error('missing_module')
if not HAS_TOKEN:
LimsResultsReport.raise_user_error('missing_module_token')
ResultsReport.raise_user_error('missing_module_token')
results_reports = LimsResultsReport.search([
('signed', '=', False)]) # TODO: Include signed but not sent?
results_reports = ResultsReport.search([
('signed', '=', False)])
session_id, _, _ = DigitalSign.create()
digital_sign = DigitalSign(session_id)
@ -149,13 +150,13 @@ class LimsResultsReport:
:return: list of details
'''
pool = Pool()
LimsResultsReportVersionDetail = pool.get(
ResultsReportVersionDetail = pool.get(
'lims.results_report.version.detail')
format_field = 'report_format'
if english_report:
format_field = 'report_format_eng'
details = LimsResultsReportVersionDetail.search([
details = ResultsReportVersionDetail.search([
('report_version.results_report.id', '=', self.id),
('valid', '=', True),
(format_field, '=', 'pdf'),
@ -247,8 +248,8 @@ class LimsResultsReport:
for line in detail.notebook_lines:
to_addrs.extend([c.contact.email for c
in line.notebook_line.fraction.entry.report_contacts
if c.contact.report_contact
and not c.entry.invoice_party.block_reports_automatic_sending])
if c.contact.report_contact and not
c.entry.invoice_party.block_reports_automatic_sending])
entries.append(line.notebook_line.fraction.entry.number) # TODO: Debug line
logging.getLogger('lims_digital_sign').info(
'Cron - Digital Signs:results_report.number:%s:to_addrs:%s'
@ -322,12 +323,14 @@ class LimsResultsReport:
def attachment(self, english_report=False):
suffix = 'eng' if english_report else 'esp'
data = {
'content': (english_report
and self.report_cache_eng
or self.report_cache),
'format': (english_report
and self.report_format_eng
or self.report_format),
'content': (
english_report and
self.report_cache_eng or
self.report_cache),
'format': (
english_report and
self.report_format_eng or
self.report_format),
'mimetype': 'pdf',
'filename': unicode(self.number) + '-' + suffix + '.pdf',
'name': unicode(self.number),
@ -341,7 +344,7 @@ class LimsResultsReport:
msg = MIMEMultipart()
msg['From'] = from_addr
hidden = True # TODO: HARDCODE!
hidden = True
if not hidden:
msg['To'] = ', '.join(to_addrs)
msg['Subject'] = subject
@ -416,3 +419,41 @@ class LimsResultsReport:
if attachment:
Attachment.delete(attachment)
return True
class ResultsReportAnnulation:
__name__ = 'lims.results_report_annulation'
__metaclass__ = PoolMeta
def transition_annul(self):
logging.getLogger('lims_digital_sign').info(
'transition_annul():INIT')
super(ResultsReportAnnulation, self).transition_annul()
logging.getLogger('lims_digital_sign').info(
'transition_annul():INHERIT')
ResultsReportVersionDetail = Pool().get(
'lims.results_report.version.detail')
# Check if the detail was annulled
detail_annulled = ResultsReportVersionDetail.search([
('id', 'in', Transaction().context['active_ids']),
('state', '=', 'annulled'),
])
for detail in detail_annulled:
detail.unsign()
# Check if the report is not longer valid details
if detail_annulled:
results_report = detail_annulled[0].report_version.results_report
detail_valid = ResultsReportVersionDetail.search([
('report_version.results_report.id', '=', results_report.id),
('state', '!=', 'annulled'),
('valid', '=', True),
])
if not detail_valid:
results_report.clean_attachments_reports()
logging.getLogger('lims_digital_sign').info(
'transition_annul():END')
return 'end'

View File

@ -1,20 +0,0 @@
<?xml version="1.0"?>
<tryton>
<data>
<!-- Icons -->
<record model="ir.ui.icon" id="digital_sign_icon">
<field name="name">lims-digital_sign</field>
<field name="path">icons/digital_sign.svg</field>
</record>
<!-- Menu -->
<!-- Laboratory / Results reports / Digital Sign -->
<menuitem parent="lims.lims_laboratory_reports" action="wizard_digital_sign"
id="menu_digital_sign" sequence="40"
icon="lims-digital_sign"/>
</data>
</tryton>

View File

@ -1,16 +0,0 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<!-- Lims -->
<record model="ir.ui.view" id="lims_results_report_view_list">
<field name="model">lims.results_report</field>
<field name="inherit" ref="lims.lims_results_report_view_list"/>
<field name="name">results_report_list</field>
</record>
</data>
</tryton>

View File

@ -1,39 +0,0 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<!-- Access Rights on Menu -->
<!-- Laboratory / Results reports / Digital Sign -->
<record model="ir.ui.menu-res.group" id="menu_lims_group_digital_sign">
<field name="menu" ref="lims.lims_menu"/>
<field name="group" ref="group_lims_digital_sign"/>
</record>
<record model="ir.ui.menu-res.group" id="menu_laboratory_group_digital_sign">
<field name="menu" ref="lims.lims_laboratory"/>
<field name="group" ref="group_lims_digital_sign"/>
</record>
<record model="ir.ui.menu-res.group" id="menu_laboratory_reports_group_digital_sign">
<field name="menu" ref="lims.lims_laboratory_reports"/>
<field name="group" ref="group_lims_digital_sign"/>
</record>
<record model="ir.ui.menu-res.group" id="menu_digital_sign_group_digital_sign">
<field name="menu" ref="menu_digital_sign"/>
<field name="group" ref="group_lims_digital_sign"/>
</record>
<!-- Access Rights on Actions -->
<record model="ir.action-res.group" id="wizard_digital_sign_group_digital_sign">
<field name="action" ref="wizard_digital_sign"/>
<field name="group" ref="group_lims_digital_sign"/>
</record>
</data>
</tryton>

View File

@ -1,31 +0,0 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<!-- Groups -->
<record model="res.group" id="group_lims_digital_sign">
<field name="name">Lims Digital Sign</field>
</record>
<record model="res.user-res.group" id="user_admin_group_lims_digital_sign">
<field name="user" ref="res.user_admin"/>
<field name="group" ref="group_lims_digital_sign"/>
</record>
<!-- Users -->
<record model="res.user" id="user_digital_sign">
<field name="login">user_cron_digital_sign</field>
<field name="name">Cron Lims Digital Sign</field>
<field name="signature"></field>
<field name="active" eval="False"/>
</record>
<record model="res.user-res.group" id="user_digital_sign_group_digital_sign">
<field name="user" ref="user_digital_sign"/>
<field name="group" ref="group_lims_digital_sign"/>
</record>
</data>
</tryton>

View File

@ -1,12 +1,7 @@
[tryton]
version=4.4.0
version=4.8.0
depends:
lims
xml:
security/users.xml
digital_sign_view.xml
configuration_view.xml
lims_view.xml
wizard/digital_sign.xml
lims_menu.xml
security/access_rights.xml
digital_sign.xml
configuration.xml

View File

@ -1,6 +0,0 @@
# This file is part of lims_digital_sign module for Tryton.
# The COPYRIGHT file at the top level of this repository contains
# the full copyright notices and license terms.
from .digital_sign import *
from .lims import *

View File

@ -1,40 +0,0 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<!-- Menu items in lims_menu.xml -->
<!-- Wizard Digital Sign -->
<record model="ir.ui.view" id="view_digital_sign_start">
<field name="model">lims_digital_sign.digital_sign.start</field>
<field name="type">form</field>
<field name="name">digital_sign_start_form</field>
</record>
<record model="ir.ui.view" id="view_digital_sign_succeed">
<field name="model">lims_digital_sign.digital_sign.succeed</field>
<field name="type">form</field>
<field name="name">digital_sign_succeed_form</field>
</record>
<record model="ir.ui.view" id="view_digital_sign_failed">
<field name="model">lims_digital_sign.digital_sign.failed</field>
<field name="type">form</field>
<field name="name">digital_sign_failed_form</field>
</record>
<record model="ir.action.wizard" id="wizard_digital_sign">
<field name="name">Digital Sign</field>
<field name="wiz_name">lims_digital_sign.digital_sign</field>
</record>
<record model="ir.action.keyword" id="wizard_digital_sign_keyword">
<field name="keyword">form_action</field>
<field name="model">lims.results_report,-1</field>
<field name="action" ref="wizard_digital_sign"/>
</record>
</data>
</tryton>

View File

@ -1,48 +0,0 @@
# -*- coding: utf-8 -*-
# This file is part of lims_digital_sign module for Tryton.
# The COPYRIGHT file at the top level of this repository contains
# the full copyright notices and license terms.
import logging
from trytond.pool import Pool, PoolMeta
from trytond.transaction import Transaction
__all__ = ['LimsResultsReportAnnulation']
class LimsResultsReportAnnulation:
__name__ = 'lims.results_report_annulation'
__metaclass__ = PoolMeta
def transition_annul(self):
logging.getLogger('lims_digital_sign').info(
'transition_annul():INIT')
super(LimsResultsReportAnnulation, self).transition_annul()
logging.getLogger('lims_digital_sign').info(
'transition_annul():INHERIT')
LimsResultsReportVersionDetail = Pool().get(
'lims.results_report.version.detail')
# Check if the detail was annulled
detail_annulled = LimsResultsReportVersionDetail.search([
('id', 'in', Transaction().context['active_ids']),
('state', '=', 'annulled'),
])
for detail in detail_annulled:
detail.unsign()
# Check if the report is not longer valid details
if detail_annulled:
results_report = detail_annulled[0].report_version.results_report
detail_valid = LimsResultsReportVersionDetail.search([
('report_version.results_report.id', '=', results_report.id),
('state', '!=', 'annulled'),
('valid', '=', True),
])
if not detail_valid:
results_report.clean_attachments_reports()
logging.getLogger('lims_digital_sign').info(
'transition_annul():END')
return 'end'

View File

@ -1 +0,0 @@
Version 4.4.0 - 2017-10-08

View File

@ -1,5 +1,5 @@
Copyright (C) 2017 Ignacio Parszyk <iparszyk@kalenislims.com>
Copyright (C) 2013-2017 Sebastián Marró <smarro@kalenislims.com>
Copyright (C) 2017-2018 Ignacio Parszyk <iparszyk@kalenislims.com>
Copyright (C) 2013-2018 Sebastián Marró <smarro@kalenislims.com>
Copyright (C) 2013-2016 Luis Falcon <falcon@gnu.org>
This program is free software: you can redistribute it and/or modify

View File

@ -3,21 +3,20 @@
# the full copyright notices and license terms.
from trytond.pool import Pool
from .resultsimport import *
from wizard import *
from . import resultsimport
def register():
Pool.register(
LimsNotebookLine,
LimsResultsImport,
LimsNotebookLoadResultsFileStart,
LimsNotebookLoadResultsFileStartLine,
LimsNotebookLoadResultsFileEmpty,
LimsNotebookLoadResultsFileResult,
LimsNotebookLoadResultsFileWarning,
LimsNotebookLoadResultsFileExport,
resultsimport.NotebookLine,
resultsimport.ResultsImport,
resultsimport.NotebookLoadResultsFileStart,
resultsimport.NotebookLoadResultsFileStartLine,
resultsimport.NotebookLoadResultsFileEmpty,
resultsimport.NotebookLoadResultsFileResult,
resultsimport.NotebookLoadResultsFileWarning,
resultsimport.NotebookLoadResultsFileExport,
module='lims_instrument', type_='model')
Pool.register(
LimsNotebookLoadResultsFile,
resultsimport.NotebookLoadResultsFile,
module='lims_instrument', type_='wizard')

View File

@ -1,26 +0,0 @@
Módulo Lims Instrument
######################
Este módulo define la base para poder agregar importadores de resultados.
Un importador de resultados es un proceso capaz de leer determinado tipo de
archivo, extraer información e importarla en el sistema.
Los archivos que se procesan son usualmente de tipo CSV o XLS, generados por
instrumentos o equipos de laboratorio a partir de pruebas realizadas.
Importadores de resultados
**************************
En Lims > Configuración > Importadores de resultados se pueden definir y
listar un importador de resultados.
Estos importadores se definen en módulos específicos, que extienden a este.
Asistente para carga de resultados desde archivo
********************************************
Desde Lims > Laboratorio > Ingreso de resultados > Carga de resultados desde
archivo se puede lanzar el asistente de importación.
Es necesario definir un Importador de resultados y un archivo a ser importado.

View File

@ -1,24 +0,0 @@
<?xml version="1.0"?>
<tryton>
<data>
<!-- Icons -->
<record model="ir.ui.icon" id="notebook_load_results_file_icon">
<field name="name">lims-notebook_load_results_file</field>
<field name="path">icons/notebook_load_results_file.svg</field>
</record>
<!-- Menu -->
<!-- Configuration / Laboratory / Results Importers -->
<menuitem parent="lims.lims_config_laboratory" action="act_lims_resultsimport_list"
id="lims_resultsimport_menu" sequence="60"/>
<!-- Laboratory / Results entry / Load Results from File -->
<menuitem parent="lims.lims_laboratory_results" action="wiz_lims_notebook_load_results_file"
id="lims_notebook_load_results_file_menu" sequence="30"
icon="lims-notebook_load_results_file"/>
</data>
</tryton>

View File

@ -2,15 +2,28 @@
# This file is part of lims_instrument module for Tryton.
# The COPYRIGHT file at the top level of this repository contains
# the full copyright notices and license terms.
try:
import cStringIO as StringIO
except ImportError:
import StringIO
import traceback
import xlrd
from xlutils.copy import copy
from datetime import datetime
from trytond.model import ModelView, ModelSQL, fields, Unique
from trytond.pool import PoolMeta
__all__ = ['LimsNotebookLine', 'LimsResultsImport']
from trytond.wizard import Wizard, StateView, StateTransition, Button
from trytond.pool import Pool, PoolMeta
from trytond.transaction import Transaction
class LimsNotebookLine:
__all__ = ['NotebookLine', 'ResultsImport', 'NotebookLoadResultsFileStart',
'NotebookLoadResultsFileStartLine', 'NotebookLoadResultsFileEmpty',
'NotebookLoadResultsFileResult', 'NotebookLoadResultsFileWarning',
'NotebookLoadResultsFileExport', 'NotebookLoadResultsFile']
class NotebookLine:
__name__ = 'lims.notebook.line'
__metaclass__ = PoolMeta
@ -24,7 +37,7 @@ class LimsNotebookLine:
imported_rm_correction_formula = fields.Char('RM Correction Formula')
class LimsResultsImport(ModelSQL, ModelView):
class ResultsImport(ModelSQL, ModelView):
'Results Import'
__name__ = 'lims.resultsimport'
_rec_name = 'description'
@ -41,7 +54,7 @@ class LimsResultsImport(ModelSQL, ModelView):
@classmethod
def __setup__(cls):
super(LimsResultsImport, cls).__setup__()
super(ResultsImport, cls).__setup__()
t = cls.__table__()
cls._sql_constraints += [
('name_uniq', Unique(t, t.name),
@ -96,3 +109,409 @@ class LimsResultsImport(ModelSQL, ModelView):
return self.controller.exportResults(self)
except AttributeError:
return False
class NotebookLoadResultsFileStart(ModelView):
'Load Results from File'
__name__ = 'lims.notebook.load_results_file.start'
results_importer = fields.Many2One('lims.resultsimport',
'Results importer', required=True)
lines = fields.One2Many('lims.notebook.load_results_file.start.line',
None, 'Files', required=True)
class NotebookLoadResultsFileStartLine(ModelView):
'Load Results from File'
__name__ = 'lims.notebook.load_results_file.start.line'
infile = fields.Binary('File', required=True, filename='name')
name = fields.Char('Name', readonly=True)
class NotebookLoadResultsFileEmpty(ModelView):
'Load Results from File Empty'
__name__ = 'lims.notebook.load_results_file.empty'
class NotebookLoadResultsFileResult(ModelView):
'Process Results from File'
__name__ = 'lims.notebook.load_results_file.result'
result_lines = fields.One2Many('lims.notebook.line', None, 'Lines',
readonly=True)
class NotebookLoadResultsFileWarning(ModelView):
'Load Results from File Warning'
__name__ = 'lims.notebook.load_results_file.warning'
msg = fields.Text('Message')
class NotebookLoadResultsFileExport(ModelView):
"Export Results from File"
__name__ = 'lims.notebook.load_results_file.export'
file = fields.Binary('File', readonly=True)
class NotebookLoadResultsFile(Wizard):
'Load Results from File'
__name__ = 'lims.notebook.load_results_file'
start = StateView('lims.notebook.load_results_file.start',
'lims_instrument.lims_load_results_file_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Collect', 'collect', 'tryton-go-next', default=True),
])
collect = StateTransition()
empty = StateView('lims.notebook.load_results_file.empty',
'lims_instrument.lims_load_results_file_empty_view_form', [
Button('Try again', 'start', 'tryton-go-next'),
Button('Close', 'end', 'tryton-close', default=True),
])
result = StateView('lims.notebook.load_results_file.result',
'lims_instrument.lims_load_results_file_result_view_form', [
Button('Cancel', 'cancel', 'tryton-cancel'),
Button('Confirm', 'confirm', 'tryton-ok', default=True),
])
confirm = StateTransition()
cancel = StateTransition()
warning = StateView('lims.notebook.load_results_file.warning',
'lims_instrument.lims_load_results_file_warning_view_form', [
Button('Ok', 'close', 'tryton-ok'),
])
close = StateTransition()
export = StateView('lims.notebook.load_results_file.export',
'lims_instrument.lims_load_results_file_export_view_form', [
Button('Done', 'end', 'tryton-close', default=True),
])
def transition_collect(self):
cursor = Transaction().connection.cursor()
pool = Pool()
Fraction = pool.get('lims.fraction')
Notebook = pool.get('lims.notebook')
NotebookLine = pool.get('lims.notebook.line')
Analysis = pool.get('lims.analysis')
lines = []
for fline in self.start.lines:
self.start.results_importer.rawresults = {}
self.start.results_importer.parse(fline.infile)
raw_results = self.start.results_importer.rawresults
fractions_numbers = list(raw_results.keys())
if not fractions_numbers:
continue
numbers = '\', \''.join(str(n) for n in fractions_numbers)
cursor.execute('SELECT id, number '
'FROM "' + Fraction._table + '" '
'WHERE number IN (\'' + numbers + '\') '
'ORDER BY number ASC')
for f in cursor.fetchall():
cursor.execute('SELECT id '
'FROM "' + Notebook._table + '" '
'WHERE fraction = %s '
'LIMIT 1', (f[0],))
notebook = cursor.fetchone()
if not notebook:
continue
for analysis in raw_results[f[1]].keys():
cursor.execute('SELECT id '
'FROM "' + Analysis._table + '" '
'WHERE code = %s '
'AND automatic_acquisition = TRUE '
'LIMIT 1', (analysis,))
if not cursor.fetchone():
continue
for rep in raw_results[f[1]][analysis].keys():
clause = [
('notebook', '=', notebook[0]),
('analysis', '=', analysis),
('repetition', '=', rep),
('result', 'in', [None, '']),
('converted_result', 'in', [None, '']),
('literal_result', 'in', [None, '']),
('result_modifier', 'not in', ['nd', 'pos', 'neg',
'ni', 'abs', 'pre', 'na']),
('converted_result_modifier', 'not in',
['nd', 'pos', 'neg', 'ni', 'abs', 'pre']),
]
line = NotebookLine.search(clause)
if line:
data = raw_results[f[1]][analysis][rep]
res = self.get_results(line[0], data)
if res:
NotebookLine.write([line[0]], res)
lines.append(line[0])
if lines:
self.result.result_lines = [l.id for l in lines]
return 'result'
return 'empty'
def get_results(self, line, data):
pool = Pool()
Device = pool.get('lims.lab.device')
res = {}
if 'result' in data or 'literal_result' in data:
if 'result' in data:
res['imported_result'] = unicode(float(data['result']))
if 'literal_result' in data:
res['imported_literal_result'] = data['literal_result']
res['imported_end_date'] = (data['end_date'] if 'end_date' in data
else line.end_date)
if 'professionals' in data:
res['imported_professionals'] = data['professionals']
if 'chromatogram' in data:
res['imported_chromatogram'] = data['chromatogram']
device = data['device'] if 'device' in data else None
if device:
dev = Device.search([('code', '=', device)])
if dev:
res['imported_device'] = dev[0].id
if 'dilution_factor' in data:
res['imported_dilution_factor'] = data['dilution_factor']
if 'rm_correction_formula' in data:
res['imported_rm_correction_formula'] = (
data['rm_correction_formula'])
return res
def default_result(self, fields):
default = {}
default['result_lines'] = [l.id for l in self.result.result_lines]
return default
def get_professionals(self, professionals_codes):
'''
This function gets a string with one or more professionals codes,
separated by commas, like: 'ABC' or 'JLB, ABC'
It returns the professionals
'''
cursor = Transaction().connection.cursor()
pool = Pool()
Professional = pool.get('lims.laboratory.professional')
res = []
professionals = ''.join(professionals_codes.split())
professionals = professionals.split(',')
for professional in professionals:
cursor.execute('SELECT id, code '
'FROM "' + Professional._table + '" '
'WHERE code = %s '
'LIMIT 1', (professional,))
prof = cursor.fetchone()
if prof:
res.append(prof)
return res
def check_professionals(self, professionals, method):
cursor = Transaction().connection.cursor()
pool = Pool()
LabProfessionalMethod = pool.get('lims.lab.professional.method')
validated = False
msg = ''
for professional in professionals:
cursor.execute('SELECT state '
'FROM "' + LabProfessionalMethod._table + '" '
'WHERE professional = %s '
'AND method = %s '
'AND type = \'analytical\' '
'LIMIT 1', (professional[0], method.id))
qualification = cursor.fetchone()
if not qualification:
validated = False
msg += '%s not qualified for method: %s' % (
professional[1], method.code)
return validated, msg
elif qualification[0] == 'training':
if not validated:
msg += '%s in training for method: %s. ' \
'Add qualified professional' % (
professional[1], method.code)
elif (qualification[0] in ('qualified', 'requalified')):
validated = True
return validated, msg
def transition_confirm(self):
pool = Pool()
NotebookLine = pool.get('lims.notebook.line')
NOW = datetime.now()
warnings = False
messages = ''
# Write Results to Notebook lines
for line in self.result.result_lines:
notebook_line_write = {
'imported_result': None,
'imported_literal_result': None,
'imported_end_date': None,
'imported_professionals': None,
'imported_chromatogram': None,
'imported_device': None,
'imported_dilution_factor': None,
'imported_rm_correction_formula': None,
}
prevent_line = False
outcome = ''
if line.result != line.imported_result:
if line.imported_result != '-1000.0':
notebook_line_write['result'] = line.imported_result
else:
notebook_line_write['result'] = None
notebook_line_write['result_modifier'] = 'na'
notebook_line_write['report'] = False
notebook_line_write['annulled'] = True
notebook_line_write['annulment_date'] = NOW
if line.literal_result != line.imported_literal_result:
notebook_line_write['literal_result'] = (
line.imported_literal_result)
if line.end_date != line.imported_end_date:
if line.imported_result != '-1000.0':
if (line.start_date and
line.start_date <= line.imported_end_date):
notebook_line_write['end_date'] = (
line.imported_end_date)
else:
prevent_line = True
outcome = 'End date cannot be lower than Start date'
else:
notebook_line_write['end_date'] = None
if line.chromatogram != line.imported_chromatogram:
notebook_line_write['chromatogram'] = (
line.imported_chromatogram)
if line.device != line.imported_device:
notebook_line_write['device'] = line.imported_device
if line.dilution_factor != line.imported_dilution_factor:
notebook_line_write['dilution_factor'] = (
line.imported_dilution_factor)
if (line.rm_correction_formula !=
line.imported_rm_correction_formula):
notebook_line_write['rm_correction_formula'] = (
line.imported_rm_correction_formula)
if line.imported_professionals:
profs = self.get_professionals(line.imported_professionals)
if profs:
validated, msg = self.check_professionals(
profs, line.method)
if validated:
professionals = [{'professional': p[0]}
for p in profs]
notebook_line_write['professionals'] = (
[('delete', [p.id for p in line.professionals])]
+ [('create', professionals)])
else:
prevent_line = True
outcome = msg
else:
prevent_line = True
outcome = ('Professional(s) with code '
+ unicode(line.imported_professionals)
+ ' not identified')
if not prevent_line:
try:
NotebookLine.write([line], notebook_line_write)
except Exception as e:
prevent_line = True
outcome = unicode(e)
original_profs = [{'professional': p.professional}
for p in line.professionals]
notebook_line_original_values = {
'result': line.result,
'literal_result': line.literal_result,
'end_date': line.end_date,
'professionals': (
[('delete', [p.id for p in line.professionals])]
+ [('create', original_profs,)]),
'chromatogram': line.chromatogram,
'device': line.device,
'dilution_factor': line.dilution_factor,
'rm_correction_formula': line.rm_correction_formula,
}
NotebookLine.write(
[line], notebook_line_original_values)
else:
outcome = 'OK'
# Update rawresults
row_num = 0
if self.start.results_importer.exportResults() or prevent_line:
rawresults = self.start.results_importer.rawresults
number = line.fraction.number
if number in rawresults:
code = line.analysis.code
if code in rawresults[number]:
rep = line.repetition
if rep in rawresults[number][code]:
rawresults[number][code][rep]['outcome'] = outcome
row_num = rawresults[number][code][rep][
'row_number']
if prevent_line:
warnings = True
messages += str(row_num) + ': ' + outcome + '\n'
if warnings:
self.warning.msg = messages
return 'warning'
else:
if self.start.results_importer.exportResults():
return 'end' # 'export'
return 'end'
def transition_cancel(self):
NotebookLine = Pool().get('lims.notebook.line')
# Clean results froms Notebook lines
notebook_line_clean = {
'imported_result': None,
'imported_literal_result': None,
'imported_end_date': None,
'imported_professionals': None,
'imported_chromatogram': None,
'imported_device': None,
'imported_dilution_factor': None,
'imported_rm_correction_formula': None,
}
NotebookLine.write(
list(self.result.result_lines), notebook_line_clean)
return 'end'
def default_warning(self, fields):
defaults = {}
if self.warning.msg:
defaults['msg'] = self.warning.msg
return defaults
def transition_close(self):
if self.start.results_importer.exportResults():
return 'end' # 'export'
return 'end'
def default_export(self, fields):
rawresults = self.start.results_importer.rawresults
filedata = StringIO.StringIO(self.start.infile) # TODO: refactoring
workbook = xlrd.open_workbook(file_contents=filedata.getvalue(),
formatting_info=True)
wb_copy = copy(workbook)
for fraction in rawresults:
for analysis in rawresults[fraction]:
for rep in rawresults[fraction][analysis]:
repetition = rawresults[fraction][analysis][rep]
if 'outcome' in repetition and 'status_cell' in repetition:
sheet, row, col = repetition['status_cell']
wb_sheet = wb_copy.get_sheet(sheet)
wb_sheet.write(row, col, repetition['outcome'])
output = StringIO.StringIO()
wb_copy.save(output)
return {'file': bytearray(output.getvalue())}

View File

@ -4,6 +4,34 @@
<!-- Menu items in lims_menu.xml -->
<!-- Results Import -->
<record model="ir.ui.view" id="lims_resultsimport_view_form">
<field name="model">lims.resultsimport</field>
<field name="type">form</field>
<field name="name">resultsimport_form</field>
</record>
<record model="ir.ui.view" id="lims_resultsimport_view_list">
<field name="model">lims.resultsimport</field>
<field name="type">tree</field>
<field name="name">resultsimport_list</field>
</record>
<record model="ir.action.act_window" id="act_lims_resultsimport_list">
<field name="name">Results Importers</field>
<field name="res_model">lims.resultsimport</field>
</record>
<record model="ir.action.act_window.view" id="act_lims_resultsimport_view_list">
<field name="sequence" eval="10"/>
<field name="view" ref="lims_resultsimport_view_list"/>
<field name="act_window" ref="act_lims_resultsimport_list"/>
</record>
<record model="ir.action.act_window.view" id="act_lims_resultsimport_view_form">
<field name="sequence" eval="20"/>
<field name="view" ref="lims_resultsimport_view_form"/>
<field name="act_window" ref="act_lims_resultsimport_list"/>
</record>
<!-- Wizard Load Results from File -->
<record model="ir.ui.view" id="lims_load_results_file_start_view_form">
@ -47,5 +75,23 @@
<field name="wiz_name">lims.notebook.load_results_file</field>
</record>
<!-- Icons -->
<record model="ir.ui.icon" id="notebook_load_results_file_icon">
<field name="name">lims-notebook_load_results_file</field>
<field name="path">icons/notebook_load_results_file.svg</field>
</record>
<!-- Menu -->
<!-- Configuration / Laboratory / Results Importers -->
<menuitem parent="lims.lims_config_laboratory" action="act_lims_resultsimport_list"
id="lims_resultsimport_menu" sequence="60"/>
<!-- Laboratory / Results entry / Load Results from File -->
<menuitem parent="lims.lims_laboratory_results" action="wiz_lims_notebook_load_results_file"
id="lims_notebook_load_results_file_menu" sequence="30"
icon="lims-notebook_load_results_file"/>
</data>
</tryton>

View File

@ -1,36 +0,0 @@
<?xml version="1.0"?>
<tryton>
<data>
<!-- Menu items in lims_menu.xml -->
<!-- Results Import -->
<record model="ir.ui.view" id="lims_resultsimport_view_form">
<field name="model">lims.resultsimport</field>
<field name="type">form</field>
<field name="name">resultsimport_form</field>
</record>
<record model="ir.ui.view" id="lims_resultsimport_view_list">
<field name="model">lims.resultsimport</field>
<field name="type">tree</field>
<field name="name">resultsimport_list</field>
</record>
<record model="ir.action.act_window" id="act_lims_resultsimport_list">
<field name="name">Results Importers</field>
<field name="res_model">lims.resultsimport</field>
</record>
<record model="ir.action.act_window.view" id="act_lims_resultsimport_view_list">
<field name="sequence" eval="10"/>
<field name="view" ref="lims_resultsimport_view_list"/>
<field name="act_window" ref="act_lims_resultsimport_list"/>
</record>
<record model="ir.action.act_window.view" id="act_lims_resultsimport_view_form">
<field name="sequence" eval="20"/>
<field name="view" ref="lims_resultsimport_view_form"/>
<field name="act_window" ref="act_lims_resultsimport_list"/>
</record>
</data>
</tryton>

View File

@ -1,8 +1,6 @@
[tryton]
version=4.4.0
version=4.8.0
depends:
lims
xml:
resultsimport_view.xml
wizard/resultsimport.xml
lims_menu.xml
resultsimport.xml

View File

@ -1,5 +0,0 @@
# This file is part of lims_instrument module for Tryton.
# The COPYRIGHT file at the top level of this repository contains
# the full copyright notices and license terms.
from .resultsimport import *

View File

@ -1,427 +0,0 @@
# -*- coding: utf-8 -*-
# This file is part of lims_instrument module for Tryton.
# The COPYRIGHT file at the top level of this repository contains
# the full copyright notices and license terms.
try:
import cStringIO as StringIO
except ImportError:
import StringIO
import xlrd
from xlutils.copy import copy
from datetime import datetime
from trytond.model import ModelView, fields
from trytond.wizard import Wizard, StateView, StateTransition, Button
from trytond.pool import Pool
from trytond.transaction import Transaction
__all__ = ['LimsNotebookLoadResultsFileStart',
'LimsNotebookLoadResultsFileStartLine', 'LimsNotebookLoadResultsFileEmpty',
'LimsNotebookLoadResultsFileResult', 'LimsNotebookLoadResultsFileWarning',
'LimsNotebookLoadResultsFileExport', 'LimsNotebookLoadResultsFile']
class LimsNotebookLoadResultsFileStart(ModelView):
'Load Results from File'
__name__ = 'lims.notebook.load_results_file.start'
results_importer = fields.Many2One('lims.resultsimport',
'Results importer', required=True)
lines = fields.One2Many('lims.notebook.load_results_file.start.line',
None, 'Files', required=True)
class LimsNotebookLoadResultsFileStartLine(ModelView):
'Load Results from File'
__name__ = 'lims.notebook.load_results_file.start.line'
infile = fields.Binary('File', required=True, filename='name')
name = fields.Char('Name', readonly=True)
class LimsNotebookLoadResultsFileEmpty(ModelView):
'Load Results from File Empty'
__name__ = 'lims.notebook.load_results_file.empty'
class LimsNotebookLoadResultsFileResult(ModelView):
'Process Results from File'
__name__ = 'lims.notebook.load_results_file.result'
result_lines = fields.One2Many('lims.notebook.line', None, 'Lines',
readonly=True)
class LimsNotebookLoadResultsFileWarning(ModelView):
'Load Results from File Warning'
__name__ = 'lims.notebook.load_results_file.warning'
msg = fields.Text('Message')
class LimsNotebookLoadResultsFileExport(ModelView):
"Export Results from File"
__name__ = 'lims.notebook.load_results_file.export'
file = fields.Binary('File', readonly=True)
class LimsNotebookLoadResultsFile(Wizard):
'Load Results from File'
__name__ = 'lims.notebook.load_results_file'
start = StateView('lims.notebook.load_results_file.start',
'lims_instrument.lims_load_results_file_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Collect', 'collect', 'tryton-go-next', default=True),
])
collect = StateTransition()
empty = StateView('lims.notebook.load_results_file.empty',
'lims_instrument.lims_load_results_file_empty_view_form', [
Button('Try again', 'start', 'tryton-go-next'),
Button('Close', 'end', 'tryton-close', default=True),
])
result = StateView('lims.notebook.load_results_file.result',
'lims_instrument.lims_load_results_file_result_view_form', [
Button('Cancel', 'cancel', 'tryton-cancel'),
Button('Confirm', 'confirm', 'tryton-ok', default=True),
])
confirm = StateTransition()
cancel = StateTransition()
warning = StateView('lims.notebook.load_results_file.warning',
'lims_instrument.lims_load_results_file_warning_view_form', [
Button('Ok', 'close', 'tryton-ok'),
])
close = StateTransition()
export = StateView('lims.notebook.load_results_file.export',
'lims_instrument.lims_load_results_file_export_view_form', [
Button('Done', 'end', 'tryton-close', default=True),
])
def transition_collect(self):
cursor = Transaction().connection.cursor()
pool = Pool()
LimsFraction = pool.get('lims.fraction')
LimsNotebook = pool.get('lims.notebook')
LimsNotebookLine = pool.get('lims.notebook.line')
LimsAnalysis = pool.get('lims.analysis')
lines = []
for fline in self.start.lines:
self.start.results_importer.rawresults = {}
self.start.results_importer.parse(fline.infile)
raw_results = self.start.results_importer.rawresults
fractions_numbers = list(raw_results.keys())
if not fractions_numbers:
continue
numbers = '\', \''.join(str(n) for n in fractions_numbers)
cursor.execute('SELECT id, number '
'FROM "' + LimsFraction._table + '" '
'WHERE number IN (\'' + numbers + '\') '
'ORDER BY number ASC')
for f in cursor.fetchall():
cursor.execute('SELECT id '
'FROM "' + LimsNotebook._table + '" '
'WHERE fraction = %s '
'LIMIT 1', (f[0],))
notebook = cursor.fetchone()
if not notebook:
continue
for analysis in raw_results[f[1]].keys():
cursor.execute('SELECT id '
'FROM "' + LimsAnalysis._table + '" '
'WHERE code = %s '
'AND automatic_acquisition = TRUE '
'LIMIT 1', (analysis,))
if not cursor.fetchone():
continue
for rep in raw_results[f[1]][analysis].keys():
clause = [
('notebook', '=', notebook[0]),
('analysis', '=', analysis),
('repetition', '=', rep),
('result', 'in', [None, '']),
('converted_result', 'in', [None, '']),
('literal_result', 'in', [None, '']),
('result_modifier', 'not in', ['nd', 'pos', 'neg',
'ni', 'abs', 'pre', 'na']),
('converted_result_modifier', 'not in',
['nd', 'pos', 'neg', 'ni', 'abs', 'pre']),
]
line = LimsNotebookLine.search(clause)
if line:
data = raw_results[f[1]][analysis][rep]
res = self.get_results(line[0], data)
if res:
LimsNotebookLine.write([line[0]], res)
lines.append(line[0])
if lines:
self.result.result_lines = [l.id for l in lines]
return 'result'
return 'empty'
def get_results(self, line, data):
pool = Pool()
LimsDevice = pool.get('lims.lab.device')
res = {}
if 'result' in data or 'literal_result' in data:
if 'result' in data:
res['imported_result'] = unicode(float(data['result']))
if 'literal_result' in data:
res['imported_literal_result'] = data['literal_result']
res['imported_end_date'] = (data['end_date'] if 'end_date' in data
else line.end_date)
if 'professionals' in data:
res['imported_professionals'] = data['professionals']
if 'chromatogram' in data:
res['imported_chromatogram'] = data['chromatogram']
device = data['device'] if 'device' in data else None
if device:
dev = LimsDevice.search([('code', '=', device)])
if dev:
res['imported_device'] = dev[0].id
if 'dilution_factor' in data:
res['imported_dilution_factor'] = data['dilution_factor']
if 'rm_correction_formula' in data:
res['imported_rm_correction_formula'] = (
data['rm_correction_formula'])
return res
def default_result(self, fields):
default = {}
default['result_lines'] = [l.id for l in self.result.result_lines]
return default
def get_professionals(self, professionals_codes):
'''
This function gets a string with one or more professionals codes,
separated by commas, like: 'ABC' or 'JLB, ABC'
It returns the professionals
'''
cursor = Transaction().connection.cursor()
pool = Pool()
Professional = pool.get('lims.laboratory.professional')
res = []
professionals = ''.join(professionals_codes.split())
professionals = professionals.split(',')
for professional in professionals:
cursor.execute('SELECT id, code '
'FROM "' + Professional._table + '" '
'WHERE code = %s '
'LIMIT 1', (professional,))
prof = cursor.fetchone()
if prof:
res.append(prof)
return res
def check_professionals(self, professionals, method):
cursor = Transaction().connection.cursor()
pool = Pool()
LabProfessionalMethod = pool.get('lims.lab.professional.method')
validated = False
msg = ''
for professional in professionals:
cursor.execute('SELECT state '
'FROM "' + LabProfessionalMethod._table + '" '
'WHERE professional = %s '
'AND method = %s '
'AND type = \'analytical\' '
'LIMIT 1', (professional[0], method.id))
qualification = cursor.fetchone()
if not qualification:
validated = False
msg += '%s not qualified for method: %s' % (
professional[1], method.code)
return validated, msg
elif qualification[0] == 'training':
if not validated:
msg += '%s in training for method: %s. ' \
'Add qualified professional' % (
professional[1], method.code)
elif (qualification[0] in ('qualified', 'requalified')):
validated = True
return validated, msg
def transition_confirm(self):
pool = Pool()
LimsNotebookLine = pool.get('lims.notebook.line')
NOW = datetime.now()
warnings = False
messages = ''
# Write Results to Notebook lines
for line in self.result.result_lines:
notebook_line_write = {
'imported_result': None,
'imported_literal_result': None,
'imported_end_date': None,
'imported_professionals': None,
'imported_chromatogram': None,
'imported_device': None,
'imported_dilution_factor': None,
'imported_rm_correction_formula': None,
}
prevent_line = False
outcome = ''
if line.result != line.imported_result:
if line.imported_result != '-1000.0':
notebook_line_write['result'] = line.imported_result
else:
notebook_line_write['result'] = None
notebook_line_write['result_modifier'] = 'na'
notebook_line_write['report'] = False
notebook_line_write['annulled'] = True
notebook_line_write['annulment_date'] = NOW
if line.literal_result != line.imported_literal_result:
notebook_line_write['literal_result'] = (
line.imported_literal_result)
if line.end_date != line.imported_end_date:
if line.imported_result != '-1000.0':
if (line.start_date and
line.start_date <= line.imported_end_date):
notebook_line_write['end_date'] = (
line.imported_end_date)
else:
prevent_line = True
outcome = 'End date cannot be lower than Start date'
else:
notebook_line_write['end_date'] = None
if line.chromatogram != line.imported_chromatogram:
notebook_line_write['chromatogram'] = (
line.imported_chromatogram)
if line.device != line.imported_device:
notebook_line_write['device'] = line.imported_device
if line.dilution_factor != line.imported_dilution_factor:
notebook_line_write['dilution_factor'] = (
line.imported_dilution_factor)
if (line.rm_correction_formula !=
line.imported_rm_correction_formula):
notebook_line_write['rm_correction_formula'] = (
line.imported_rm_correction_formula)
if line.imported_professionals:
profs = self.get_professionals(line.imported_professionals)
if profs:
validated, msg = self.check_professionals(
profs, line.method)
if validated:
professionals = [{'professional': p[0]}
for p in profs]
notebook_line_write['professionals'] = (
[('delete', [p.id for p in line.professionals])]
+ [('create', professionals)])
else:
prevent_line = True
outcome = msg
else:
prevent_line = True
outcome = ('Professional(s) with code '
+ unicode(line.imported_professionals)
+ ' not identified')
if not prevent_line:
try:
LimsNotebookLine.write([line], notebook_line_write)
except Exception as e:
prevent_line = True
outcome = unicode(e)
original_profs = [{'professional': p.professional}
for p in line.professionals]
notebook_line_original_values = {
'result': line.result,
'literal_result': line.literal_result,
'end_date': line.end_date,
'professionals': (
[('delete', [p.id for p in line.professionals])]
+ [('create', original_profs,)]),
'chromatogram': line.chromatogram,
'device': line.device,
'dilution_factor': line.dilution_factor,
'rm_correction_formula': line.rm_correction_formula,
}
LimsNotebookLine.write(
[line], notebook_line_original_values)
else:
outcome = 'OK'
# Update rawresults
row_num = 0
if self.start.results_importer.exportResults() or prevent_line:
rawresults = self.start.results_importer.rawresults
number = line.fraction.number
if number in rawresults:
code = line.analysis.code
if code in rawresults[number]:
rep = line.repetition
if rep in rawresults[number][code]:
rawresults[number][code][rep]['outcome'] = outcome
row_num = rawresults[number][code][rep][
'row_number']
if prevent_line:
warnings = True
messages += str(row_num) + ': ' + outcome + '\n'
if warnings:
self.warning.msg = messages
return 'warning'
else:
if self.start.results_importer.exportResults():
return 'end' # 'export'
return 'end'
def transition_cancel(self):
LimsNotebookLine = Pool().get('lims.notebook.line')
# Clean results froms Notebook lines
notebook_line_clean = {
'imported_result': None,
'imported_literal_result': None,
'imported_end_date': None,
'imported_professionals': None,
'imported_chromatogram': None,
'imported_device': None,
'imported_dilution_factor': None,
'imported_rm_correction_formula': None,
}
LimsNotebookLine.write(
list(self.result.result_lines), notebook_line_clean)
return 'end'
def default_warning(self, fields):
defaults = {}
if self.warning.msg:
defaults['msg'] = self.warning.msg
return defaults
def transition_close(self):
if self.start.results_importer.exportResults():
return 'end' # 'export'
return 'end'
def default_export(self, fields):
rawresults = self.start.results_importer.rawresults
filedata = StringIO.StringIO(self.start.infile) # TODO: refactoring
workbook = xlrd.open_workbook(file_contents=filedata.getvalue(),
formatting_info=True)
wb_copy = copy(workbook)
for fraction in rawresults:
for analysis in rawresults[fraction]:
for rep in rawresults[fraction][analysis]:
repetition = rawresults[fraction][analysis][rep]
if 'outcome' in repetition and 'status_cell' in repetition:
sheet, row, col = repetition['status_cell']
wb_sheet = wb_copy.get_sheet(sheet)
wb_sheet.write(row, col, repetition['outcome'])
output = StringIO.StringIO()
wb_copy.save(output)
return {'file': bytearray(output.getvalue())}

View File

@ -1 +0,0 @@
Version 4.4.0 - 2017-10-08

View File

@ -1,5 +1,5 @@
Copyright (C) 2017 Ignacio Parszyk <iparszyk@kalenislims.com>
Copyright (C) 2014-2017 Sebastián Marró <smarro@kalenislims.com>
Copyright (C) 2017-2018 Ignacio Parszyk <iparszyk@kalenislims.com>
Copyright (C) 2014-2018 Sebastián Marró <smarro@kalenislims.com>
Copyright (C) 2014-2016 Luis Falcon <falcon@gnu.org>
This program is free software: you can redistribute it and/or modify

View File

@ -3,10 +3,10 @@
# the full copyright notices and license terms.
from trytond.pool import Pool
from .resultsimport import *
from . import resultsimport
def register():
Pool.register(
LimsResultsImport,
resultsimport.ResultsImport,
module='lims_instrument_custom_set', type_='model')

View File

@ -23,7 +23,7 @@ def getControllerName():
def parse(self, infile):
LimsLabWorkYear = Pool().get('lims.lab.workyear')
LabWorkYear = Pool().get('lims.lab.workyear')
filedata = StringIO.StringIO(infile)
workbook = xlrd.open_workbook(file_contents=filedata.getvalue())
@ -69,7 +69,7 @@ def parse(self, infile):
header_found = False
continue
workyear = LimsLabWorkYear.search(
workyear = LabWorkYear.search(
['code', '=', str(int(row[1]))
])
padding = None

View File

@ -1,82 +0,0 @@
Módulo Lims Instrument Custom Set
#################################
Este módulo define dos importadores de resultados:
-Planilla personalizada - CSV (no implementada aún)
-Planilla personalizada - XLS
Se trata en ambos casos de planillas definidas por el usuario, sin relación
con archivos exportados por un instrumento particular.
Planilla personalizada - XLS
****************************
Este importador es capaz de interpretar una planilla de Excel a partir de
ciertos parámetros que puede definir el usuario, es decir que no responden al
esquema de ningún instrumento o equipo.
Utiliza la librería 'xlrd' para extraer la información de las planillas Excel:
http://www.python-excel.org/
Los parámetros que es necesario definir en cada hoja son:
- 'Analysis Code': indica el código del análisis al que corresponden los
valores expuestos en la hoja. Si en una celda se pone 'Analysis Code', en la
celda que esté a su derecha se buscará el código del análisis.
- 'Data Header': si se indica este valor en una celda, el importador buscará
en la fila inmediata inferior la cabecera de los valores que se
pretende ingresar.
De esta cabecera, las cuatro primeras columnas deben corresponder a:
- Muestra
- Año
- Fracción
- Repetición
Sin importar cuáles sean los nombres definidos para esas columnas, los cuatro
valores se harán corresponder, en ese orden, con esos datos, que son los que
permiten identificar una línea de cuaderno.
De ahí en más, las restantes columnas pueden corresponder a cualquier valor
que determine el usuario, sin límites.
Para los nombres de las columnas pueden utilizarse espacios y puntos, pero se
recomienda evitar cualquier caracter que pueda confundirse con una fórmula o
expresión algebráica, por ejemplo: / + - ( ) *
También es preferible no utilizar caracteres no ASCII, ya que el parseador
de fórmulas pueden rechazarlo por inválido e ignorarlo.
- 'Formula': a la derecha de aquella celda que contenga el valor 'Formula' se
deberá definir la fórmula de cálculo.
Para que la fórmula pueda ser correctamente mapeada, las variables deben
coincidir con el nombre de alguna de las columnas cabecera definidas como
'Data Header'.
La cabecera puede contener columnas que no son utilizadas en la fórmula, pero
para que la fórmula se aplique correctamente todas sus variables deben estar
representadas en la cabecera.
Si al analizar la hoja no se pueden determinar estos tres parámetros (
'Analysis Code', 'Data Header' y 'Formula'), el importador de datos concluirá
la lectura sin tomar datos.
Hay un último valor, opcional, que tiene una función especial:
- '###': si se pone ### en la primera celda (A1), la hoja es ignorada; puede
ser útil si se desea que una hoja no sea tenida en cuenta durante la
importación.
Carga de datos
**************
Para empezar a cargar valores es necesario copiar la cabecera definida como
'Data Header' y en las filas que siguen cargar los datos.
Los datos deben ser númericos y deben existir (no pueden dejarse vacíos).
Si alguna fila se deja en blanco, el importador de datos continuará con las
siguientes hasta volver a encontrar otra vez la cabecera, circunstancia en la
cual continuará con la captación de valores.
Si ya no se encuentra la cabecera, la lectura seguirá sin captar valores,
hasta llegar al final de la hoja. Una vez concluida una hoja se sigue con la
siguiente.
En distintas hojas de la planilla de cálculo se puede hacer referencia a la
misma fracción, quizá con un análisis distinto.

View File

@ -7,10 +7,10 @@ from trytond.pool import PoolMeta
import custom_set_csv
import custom_set_xls
__all__ = ['LimsResultsImport']
__all__ = ['ResultsImport']
class LimsResultsImport:
class ResultsImport:
__name__ = 'lims.resultsimport'
__metaclass__ = PoolMeta
@ -19,7 +19,7 @@ class LimsResultsImport:
@classmethod
def __setup__(cls):
super(LimsResultsImport, cls).__setup__()
super(ResultsImport, cls).__setup__()
controllers = [
('custom_set_csv', 'Custom Set - CSV'),
('custom_set_xls', 'Custom Set - XLS'),
@ -34,7 +34,7 @@ class LimsResultsImport:
elif self.name == 'custom_set_xls':
self.controller = custom_set_xls
else:
return super(LimsResultsImport, self).loadController()
return super(ResultsImport, self).loadController()
def getAnalysisCode(self, row):
return self.controller.getAnalysisCode(self, row)

View File

@ -1,5 +1,5 @@
[tryton]
version=4.4.0
version=4.8.0
depends:
lims_instrument

View File

@ -1 +0,0 @@
Version 4.4.0 - 2017-10-08

View File

@ -1,5 +1,5 @@
Copyright (C) 2017 Ignacio Parszyk <iparszyk@kalenislims.com>
Copyright (C) 2014-2017 Sebastián Marró <smarro@kalenislims.com>
Copyright (C) 2017-2018 Ignacio Parszyk <iparszyk@kalenislims.com>
Copyright (C) 2014-2018 Sebastián Marró <smarro@kalenislims.com>
Copyright (C) 2014-2016 Luis Falcon <falcon@gnu.org>
This program is free software: you can redistribute it and/or modify

View File

@ -3,10 +3,10 @@
# the full copyright notices and license terms.
from trytond.pool import Pool
from .resultsimport import *
from . import resultsimport
def register():
Pool.register(
LimsResultsImport,
resultsimport.ResultsImport,
module='lims_instrument_generic_form', type_='model')

View File

@ -26,7 +26,7 @@ def getControllerName():
def parse(self, infile):
LimsLabWorkYear = Pool().get('lims.lab.workyear')
LabWorkYear = Pool().get('lims.lab.workyear')
filedata = StringIO.StringIO(infile)
workbook = xlrd.open_workbook(file_contents=filedata.getvalue())
@ -59,7 +59,7 @@ def parse(self, infile):
row[COL['E']].ctype == xlrd.XL_CELL_NUMBER) else None
year = int(row[COL['F']].value) if (
row[COL['F']].ctype == xlrd.XL_CELL_NUMBER) else None
workyear = LimsLabWorkYear.search(
workyear = LabWorkYear.search(
['code', '=', str(year)])
padding = None
if workyear and workyear[0] and workyear[0].sample_sequence:

View File

@ -6,16 +6,16 @@
from trytond.pool import PoolMeta
import generic_form_xls
__all__ = ['LimsResultsImport']
__all__ = ['ResultsImport']
class LimsResultsImport:
class ResultsImport:
__name__ = 'lims.resultsimport'
__metaclass__ = PoolMeta
@classmethod
def __setup__(cls):
super(LimsResultsImport, cls).__setup__()
super(ResultsImport, cls).__setup__()
controllers = [
('generic_form_xls', 'Generic Form - XLS'),
]
@ -27,4 +27,4 @@ class LimsResultsImport:
if self.name == 'generic_form_xls':
self.controller = generic_form_xls
else:
return super(LimsResultsImport, self).loadController()
return super(ResultsImport, self).loadController()

View File

@ -1,5 +1,5 @@
[tryton]
version=4.4.0
version=4.8.0
depends:
lims_instrument

View File

@ -1 +0,0 @@
Version 4.4.0 - 2017-10-08

View File

@ -1,5 +1,5 @@
Copyright (C) 2017 Ignacio Parszyk <iparszyk@kalenislims.com>
Copyright (C) 2014-2017 Sebastián Marró <smarro@kalenislims.com>
Copyright (C) 2017-2018 Ignacio Parszyk <iparszyk@kalenislims.com>
Copyright (C) 2014-2018 Sebastián Marró <smarro@kalenislims.com>
Copyright (C) 2014-2016 Luis Falcon <falcon@gnu.org>
This program is free software: you can redistribute it and/or modify

View File

@ -3,10 +3,10 @@
# the full copyright notices and license terms.
from trytond.pool import Pool
from .resultsimport import *
from . import resultsimport
def register():
Pool.register(
LimsResultsImport,
resultsimport.ResultsImport,
module='lims_instrument_generic_service', type_='model')

View File

@ -25,7 +25,7 @@ def getControllerName():
def parse(self, infile):
LimsLabWorkYear = Pool().get('lims.lab.workyear')
LabWorkYear = Pool().get('lims.lab.workyear')
filedata = StringIO.StringIO(infile)
workbook = xlrd.open_workbook(file_contents=filedata.getvalue())
@ -58,7 +58,7 @@ def parse(self, infile):
row4th[COL['E']].ctype == xlrd.XL_CELL_NUMBER) else None
year = int(row4th[COL['F']].value) if (
row4th[COL['F']].ctype == xlrd.XL_CELL_NUMBER) else None
workyear = LimsLabWorkYear.search(
workyear = LabWorkYear.search(
['code', '=', str(year)])
padding = None
if workyear and workyear[0] and workyear[0].sample_sequence:

View File

@ -6,16 +6,16 @@
from trytond.pool import PoolMeta
import generic_service_xls
__all__ = ['LimsResultsImport']
__all__ = ['ResultsImport']
class LimsResultsImport:
class ResultsImport:
__name__ = 'lims.resultsimport'
__metaclass__ = PoolMeta
@classmethod
def __setup__(cls):
super(LimsResultsImport, cls).__setup__()
super(ResultsImport, cls).__setup__()
controllers = [
('generic_service_xls', 'Generic Service Form - XLS'),
]
@ -27,4 +27,4 @@ class LimsResultsImport:
if self.name == 'generic_service_xls':
self.controller = generic_service_xls
else:
return super(LimsResultsImport, self).loadController()
return super(ResultsImport, self).loadController()

View File

@ -1,5 +1,5 @@
[tryton]
version=4.4.0
version=4.8.0
depends:
lims_instrument

View File

@ -1 +0,0 @@
Version 4.4.0 - 2017-10-08

View File

@ -1,5 +1,5 @@
Copyright (C) 2017 Ignacio Parszyk <iparszyk@kalenislims.com>
Copyright (C) 2013-2017 Sebastián Marró <smarro@kalenislims.com>
Copyright (C) 2017-2018 Ignacio Parszyk <iparszyk@kalenislims.com>
Copyright (C) 2013-2018 Sebastián Marró <smarro@kalenislims.com>
Copyright (C) 2013-2016 Luis Falcon <falcon@gnu.org>
This program is free software: you can redistribute it and/or modify

View File

@ -3,34 +3,32 @@
# the full copyright notices and license terms.
from trytond.pool import Pool
from .stock import *
from .configuration import *
from .production import *
from report import *
from . import stock
from . import configuration
from . import production
def register():
Pool.register(
PurityDegree,
Brand,
FamilyEquivalent,
LotCategory,
Lot,
Move,
ShipmentIn,
Template,
Product,
ProductionConfiguration,
ProductionConfigurationLotSequence,
LimsConfiguration,
LimsConfigurationSolvents,
BOM,
Production,
stock.PurityDegree,
stock.Brand,
stock.FamilyEquivalent,
stock.LotCategory,
stock.Lot,
stock.Move,
stock.ShipmentIn,
stock.Template,
stock.Product,
configuration.ProductionConfiguration,
configuration.ProductionConfigurationLotSequence,
configuration.Configuration,
configuration.ConfigurationSolvents,
production.BOM,
production.Production,
module='lims_production', type_='model')
Pool.register(
UpdateCostPrice,
LimsMoveProductionRelated,
stock.MoveProductionRelated,
module='lims_production', type_='wizard')
Pool.register(
FamilyEquivalentReport,
production.FamilyEquivalentReport,
module='lims_production', type_='report')

View File

@ -3,15 +3,13 @@
# The COPYRIGHT file at the top level of this repository contains
# the full copyright notices and license terms.
from trytond import backend
from trytond.model import ModelSQL, fields
from trytond.pool import PoolMeta, Pool
from trytond.pyson import Eval
from trytond.tools.multivalue import migrate_property
from trytond.modules.company.model import CompanyValueMixin
__all__ = ['ProductionConfiguration', 'ProductionConfigurationLotSequence',
'LimsConfiguration', 'LimsConfigurationSolvents']
'Configuration', 'ConfigurationSolvents']
class ProductionConfiguration:
@ -59,7 +57,7 @@ class ProductionConfigurationLotSequence(ModelSQL, CompanyValueMixin):
return None
class LimsConfiguration:
class Configuration:
__name__ = 'lims.configuration'
__metaclass__ = PoolMeta
@ -94,7 +92,7 @@ class LimsConfiguration:
return res
class LimsConfigurationSolvents(ModelSQL):
class ConfigurationSolvents(ModelSQL):
'Configuration - Solvents'
__name__ = 'lims.configuration.solvents'

View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<tryton>
<data noupdate="1">
<!-- Sequences for Lot -->
<record model="ir.sequence.type" id="seq_type_lot">
<field name="name">Lot</field>
<field name="code">stock.lot</field>
</record>
<record model="ir.sequence.type-res.group"
id="seq_type_lot_group_admin">
<field name="sequence_type" ref="seq_type_lot"/>
<field name="group" ref="res.group_admin"/>
</record>
<record model="ir.sequence" id="seq_lot">
<field name="name">Lot</field>
<field name="code">stock.lot</field>
</record>
</data>
</tryton>

View File

@ -1,23 +0,0 @@
<?xml version="1.0"?>
<tryton>
<data>
<!-- Menu -->
<!-- Configuration / Material -->
<menuitem name="Material" parent="lims.lims_config"
id="lims_config_material" sequence="50"/>
<menuitem parent="lims_config_material" action="act_lims_brand_list"
id="lims_brand_menu" sequence="10"/>
<menuitem parent="lims_config_material" action="act_lims_purity_degree_list"
id="lims_purity_degree_menu" sequence="20"/>
<menuitem parent="lims_config_material" action="act_lims_family_equivalent_list"
id="lims_family_equivalent_menu" sequence="30"/>
<!-- Other menu items -->
<menuitem parent="stock.menu_configuration" action="act_lims_lot_category_list"
id="lims_lot_category_menu" sequence="10"/>
</data>
</tryton>

View File

@ -1,20 +0,0 @@
<?xml version="1.0"?>
<tryton>
<data>
<!-- Family/Equivalents Report -->
<record model="ir.action.report" id="report_family_equivalent">
<field name="name">Family/Equivalents</field>
<field name="report_name">lims.family.equivalent.report</field>
<field name="report">lims_production/report/family_equivalent.odt</field>
<field name="extension">pdf</field>
</record>
<record model="ir.action.keyword" id="report_family_equivalent_keyword">
<field name="keyword">form_print</field>
<field name="model">lims.family.equivalent,-1</field>
<field name="action" ref="report_family_equivalent"/>
</record>
</data>
</tryton>

View File

@ -7,8 +7,10 @@ from decimal import Decimal
from trytond.model import fields
from trytond.pyson import Eval, Bool
from trytond.pool import PoolMeta, Pool
from trytond.transaction import Transaction
from trytond.report import Report
__all__ = ['BOM', 'Production']
__all__ = ['BOM', 'Production', 'FamilyEquivalentReport']
class BOM:
@ -229,3 +231,45 @@ class Production:
from_location = product_location
return super(Production, self)._explode_move_values(from_location,
to_location, company, bom_io, quantity)
class FamilyEquivalentReport(Report):
'Family/Equivalent'
__name__ = 'lims.family.equivalent.report'
@classmethod
def get_context(cls, records, data):
pool = Pool()
Company = pool.get('company.company')
report_context = super(FamilyEquivalentReport, cls).get_context(
records, data)
report_context['company'] = Company(Transaction().context['company'])
report_context['records'] = cls._get_family_records(records)
report_context['compute_qty'] = cls.compute_qty
return report_context
@classmethod
def _get_family_records(cls, records):
pool = Pool()
Location = pool.get('stock.location')
Date_ = pool.get('ir.date')
FamilyEquivalent = pool.get('lims.family.equivalent')
locations = Location.search([
('type', '=', 'storage'),
])
context = {}
context['locations'] = [l.id for l in locations]
context['stock_date_end'] = Date_.today()
with Transaction().set_context(context):
res = FamilyEquivalent.browse(records)
return res
@classmethod
def compute_qty(cls, from_uom, qty, to_uom):
pool = Pool()
Uom = pool.get('product.uom')
return Uom.compute_qty(from_uom, qty, to_uom)

View File

@ -1,9 +1,144 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<?xml version="1.0"?>
<tryton>
<data>
<!-- Sequences for Lot -->
<record model="ir.sequence.type" id="seq_type_lot">
<field name="name">Lot</field>
<field name="code">stock.lot</field>
</record>
<record model="ir.sequence.type-res.group"
id="seq_type_lot_group_admin">
<field name="sequence_type" ref="seq_type_lot"/>
<field name="group" ref="res.group_admin"/>
</record>
<record model="ir.sequence" id="seq_lot">
<field name="name">Lot</field>
<field name="code">stock.lot</field>
</record>
<!-- Groups -->
<record model="res.group" id="group_lims_conf_material_readonly">
<field name="name">Lims Configuration Material Read Only</field>
</record>
<record model="res.group" id="group_lims_conf_material_admin">
<field name="name">Lims Configuration Material Admin</field>
</record>
<record model="res.user-res.group" id="user_admin_group_lims_conf_material_admin">
<field name="user" ref="res.user_admin"/>
<field name="group" ref="group_lims_conf_material_admin"/>
</record>
<record model="res.group" id="group_lims_lot_input_prod">
<field name="name">Lims Lot Input for production</field>
</record>
<record model="res.user-res.group" id="user_admin_group_lot_input_prod">
<field name="user" ref="res.user_admin"/>
<field name="group" ref="group_lims_lot_input_prod"/>
</record>
<record model="res.group" id="group_lims_lot_prod_sale">
<field name="name">Lims Lot Production for sale</field>
</record>
<record model="res.user-res.group" id="user_admin_group_lot_prod_sale">
<field name="user" ref="res.user_admin"/>
<field name="group" ref="group_lims_lot_prod_sale"/>
</record>
<record model="res.group" id="group_lims_lot_domestic_use">
<field name="name">Lims Lot Production for domestic use</field>
</record>
<record model="res.user-res.group" id="user_admin_group_lot_domestic_use">
<field name="user" ref="res.user_admin"/>
<field name="group" ref="group_lims_lot_domestic_use"/>
</record>
<!-- Production -->
<record model="ir.ui.view" id="lims_production_view_form">
<field name="model">production</field>
<field name="inherit" ref="production.production_view_form"/>
<field name="name">production_production_form</field>
</record>
<record model="ir.ui.view" id="lims_production_view_list">
<field name="model">production</field>
<field name="inherit" ref="production.production_view_list"/>
<field name="name">production_production_list</field>
</record>
<!-- BOM -->
<record model="ir.ui.view" id="lims_bom_view_form">
<field name="model">production.bom</field>
<field name="inherit" ref="production.bom_view_form"/>
<field name="name">production_bom_form</field>
</record>
<record model="ir.ui.view" id="lims_bom_view_list">
<field name="model">production.bom</field>
<field name="inherit" ref="production.bom_view_list"/>
<field name="name">production_bom_list</field>
</record>
<!-- Move actions related -->
<record model="ir.action.act_window" id="act_production_related">
<field name="name">Related Productions</field>
<field name="res_model">production</field>
</record>
<record model="ir.action.act_window.view" id="act_production_related_list_view">
<field name="sequence" eval="10"/>
<field name="view" ref="production.production_view_list"/>
<field name="act_window" ref="act_production_related"/>
</record>
<record model="ir.action.act_window.view" id="act_production_related_form_view">
<field name="sequence" eval="20"/>
<field name="view" ref="production.production_view_form"/>
<field name="act_window" ref="act_production_related"/>
</record>
<record model="ir.action.wizard" id="wizard_move_production_related">
<field name="name">Related Productions</field>
<field name="wiz_name">lims.move.production_related</field>
</record>
<record model="ir.action.keyword" id="act_open_production_keyword">
<field name="keyword">form_relate</field>
<field name="model">stock.move,-1</field>
<field name="action" ref="wizard_move_production_related"/>
</record>
<!-- Menu -->
<!-- Configuration / Material -->
<menuitem name="Material" parent="lims.lims_config"
id="lims_config_material" sequence="50"/>
<menuitem parent="lims_config_material" action="act_lims_brand_list"
id="lims_brand_menu" sequence="10"/>
<menuitem parent="lims_config_material" action="act_lims_purity_degree_list"
id="lims_purity_degree_menu" sequence="20"/>
<menuitem parent="lims_config_material" action="act_lims_family_equivalent_list"
id="lims_family_equivalent_menu" sequence="30"/>
<!-- Other menu items -->
<menuitem parent="stock.menu_configuration" action="act_lims_lot_category_list"
id="lims_lot_category_menu" sequence="10"/>
<!-- Family/Equivalents Report -->
<record model="ir.action.report" id="report_family_equivalent">
<field name="name">Family/Equivalents</field>
<field name="report_name">lims.family.equivalent.report</field>
<field name="report">lims_production/report/family_equivalent.odt</field>
<field name="extension">pdf</field>
</record>
<record model="ir.action.keyword" id="report_family_equivalent_keyword">
<field name="keyword">form_print</field>
<field name="model">lims.family.equivalent,-1</field>
<field name="action" ref="report_family_equivalent"/>
</record>
<!-- Access Rights on Models -->
<record model="ir.model.access" id="access_brand">
@ -220,12 +355,12 @@ this repository contains the full copyright notices and license terms. -->
<!-- Configuration / Material -->
<record model="ir.ui.menu-res.group" id="menu_lims_group_conf_material_readonly">
<field name="menu" ref="lims.lims_menu"/>
<record model="ir.ui.menu-res.group" id="menu_lims_laboratory_group_conf_material_readonly">
<field name="menu" ref="lims.lims_laboratory"/>
<field name="group" ref="group_lims_conf_material_readonly"/>
</record>
<record model="ir.ui.menu-res.group" id="menu_lims_group_conf_material_admin">
<field name="menu" ref="lims.lims_menu"/>
<record model="ir.ui.menu-res.group" id="menu_lims_laboratory_group_conf_material_admin">
<field name="menu" ref="lims.lims_laboratory"/>
<field name="group" ref="group_lims_conf_material_admin"/>
</record>

View File

@ -1,59 +0,0 @@
<?xml version="1.0"?>
<tryton>
<data>
<!-- Production -->
<record model="ir.ui.view" id="lims_production_view_form">
<field name="model">production</field>
<field name="inherit" ref="production.production_view_form"/>
<field name="name">production_production_form</field>
</record>
<record model="ir.ui.view" id="lims_production_view_list">
<field name="model">production</field>
<field name="inherit" ref="production.production_view_list"/>
<field name="name">production_production_list</field>
</record>
<!-- BOM -->
<record model="ir.ui.view" id="lims_bom_view_form">
<field name="model">production.bom</field>
<field name="inherit" ref="production.bom_view_form"/>
<field name="name">production_bom_form</field>
</record>
<record model="ir.ui.view" id="lims_bom_view_list">
<field name="model">production.bom</field>
<field name="inherit" ref="production.bom_view_list"/>
<field name="name">production_bom_list</field>
</record>
<!-- Move actions related -->
<record model="ir.action.act_window" id="act_production_related">
<field name="name">Related Productions</field>
<field name="res_model">production</field>
</record>
<record model="ir.action.act_window.view" id="act_production_related_list_view">
<field name="sequence" eval="10"/>
<field name="view" ref="production.production_view_list"/>
<field name="act_window" ref="act_production_related"/>
</record>
<record model="ir.action.act_window.view" id="act_production_related_form_view">
<field name="sequence" eval="20"/>
<field name="view" ref="production.production_view_form"/>
<field name="act_window" ref="act_production_related"/>
</record>
<record model="ir.action.wizard" id="wizard_move_production_related">
<field name="name">Related Productions</field>
<field name="wiz_name">lims.move.production_related</field>
</record>
<record model="ir.action.keyword" id="act_open_production_keyword">
<field name="keyword">form_relate</field>
<field name="model">stock.move,-1</field>
<field name="action" ref="wizard_move_production_related"/>
</record>
</data>
</tryton>

View File

@ -1,5 +0,0 @@
# This file is part of lims_production module for Tryton.
# The COPYRIGHT file at the top level of this repository contains
# the full copyright notices and license terms.
from .lims_report import *

View File

@ -1,52 +0,0 @@
# -*- coding: utf-8 -*-
# This file is part of lims_production module for Tryton.
# The COPYRIGHT file at the top level of this repository contains
# the full copyright notices and license terms.
from trytond.report import Report
from trytond.transaction import Transaction
from trytond.pool import Pool
__all__ = ['FamilyEquivalentReport']
class FamilyEquivalentReport(Report):
'Family/Equivalent'
__name__ = 'lims.family.equivalent.report'
@classmethod
def get_context(cls, records, data):
pool = Pool()
Company = pool.get('company.company')
report_context = super(FamilyEquivalentReport, cls).get_context(
records, data)
report_context['company'] = Company(Transaction().context['company'])
report_context['records'] = cls._get_family_records(records)
report_context['compute_qty'] = cls.compute_qty
return report_context
@classmethod
def _get_family_records(cls, records):
pool = Pool()
Location = pool.get('stock.location')
Date_ = pool.get('ir.date')
FamilyEquivalent = pool.get('lims.family.equivalent')
locations = Location.search([
('type', '=', 'storage'),
])
context = {}
context['locations'] = [l.id for l in locations]
context['stock_date_end'] = Date_.today()
with Transaction().set_context(context):
res = FamilyEquivalent.browse(records)
return res
@classmethod
def compute_qty(cls, from_uom, qty, to_uom):
pool = Pool()
Uom = pool.get('product.uom')
return Uom.compute_qty(from_uom, qty, to_uom)

View File

@ -1,45 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<!-- Groups -->
<record model="res.group" id="group_lims_conf_material_readonly">
<field name="name">Lims Configuration Material Read Only</field>
</record>
<record model="res.group" id="group_lims_conf_material_admin">
<field name="name">Lims Configuration Material Admin</field>
</record>
<record model="res.user-res.group" id="user_admin_group_lims_conf_material_admin">
<field name="user" ref="res.user_admin"/>
<field name="group" ref="group_lims_conf_material_admin"/>
</record>
<record model="res.group" id="group_lims_lot_input_prod">
<field name="name">Lims Lot Input for production</field>
</record>
<record model="res.user-res.group" id="user_admin_group_lot_input_prod">
<field name="user" ref="res.user_admin"/>
<field name="group" ref="group_lims_lot_input_prod"/>
</record>
<record model="res.group" id="group_lims_lot_prod_sale">
<field name="name">Lims Lot Production for sale</field>
</record>
<record model="res.user-res.group" id="user_admin_group_lot_prod_sale">
<field name="user" ref="res.user_admin"/>
<field name="group" ref="group_lims_lot_prod_sale"/>
</record>
<record model="res.group" id="group_lims_lot_domestic_use">
<field name="name">Lims Lot Production for domestic use</field>
</record>
<record model="res.user-res.group" id="user_admin_group_lot_domestic_use">
<field name="user" ref="res.user_admin"/>
<field name="group" ref="group_lims_lot_domestic_use"/>
</record>
</data>
</tryton>

View File

@ -10,12 +10,11 @@ from trytond.model import ModelView, ModelSQL, fields
from trytond.pyson import PYSONEncoder, Eval, Equal, Bool, Not
from trytond.transaction import Transaction
from trytond.pool import PoolMeta, Pool
from trytond.wizard import Wizard, StateTransition, StateAction
from trytond.wizard import Wizard, StateAction
from trytond.modules.product import price_digits
__all__ = ['PurityDegree', 'Brand', 'FamilyEquivalent', 'Template', 'Product',
'UpdateCostPrice', 'LotCategory', 'Lot', 'Move', 'ShipmentIn',
'LimsMoveProductionRelated']
'LotCategory', 'Lot', 'Move', 'ShipmentIn', 'MoveProductionRelated']
class PurityDegree(ModelSQL, ModelView):
@ -220,8 +219,8 @@ class Product:
qty = Decimal(str(qty))
if move.from_location.type == 'storage':
qty *= -1
if (move.from_location.type in ['supplier', 'production']
or move.to_location.type == 'supplier'):
if (move.from_location.type in ['supplier', 'production'] or
move.to_location.type == 'supplier'):
with Transaction().set_context(date=move.effective_date):
unit_price = Currency.compute(
move.currency, move.unit_price,
@ -255,34 +254,6 @@ class Product:
return [('template.' + name,) + tuple(clause[1:])]
class UpdateCostPrice:
__name__ = 'product.update_cost_price'
__metaclass__ = PoolMeta
start_state = 'check'
check = StateTransition()
def transition_check(self):
context = Transaction().context
if context['active_model'] == 'product.template':
return 'ask_price'
return 'end'
def default_ask_price(self, fields):
Template = Pool().get('product.template')
default = super(UpdateCostPrice, self).default_ask_price(fields)
if 'template' in default:
template = Template(default['template'])
default['cost_price'] = template.cost_price
return default
def transition_update_price(self):
Template = Pool().get('product.template')
write = partial(Template.write, [self.ask_price.template])
write({'cost_price': self.ask_price.cost_price})
return 'end'
class LotCategory(ModelSQL, ModelView):
"Lot Category"
__name__ = "stock.lot.category"
@ -302,7 +273,7 @@ class Lot:
category = fields.Many2One('stock.lot.category', 'Category')
special_category = fields.Function(fields.Char('Category'),
'on_change_with_special_category', searcher='search_special_category')
'on_change_with_special_category')
stability = fields.Char('Stability', depends=['special_category'],
states={
@ -446,24 +417,24 @@ class Lot:
def create(cls, vlist):
pool = Pool()
Product = pool.get('product.product')
LimsConfig = pool.get('lims.configuration')
Config = pool.get('lims.configuration')
lims_config = LimsConfig(1)
config = Config(1)
vlist = [x.copy() for x in vlist]
for values in vlist:
if not values.get('category'):
product = Product(values['product'])
lot_category_id = None
if (product.purchasable and not product.salable):
lot_category_id = (lims_config.lot_category_input_prod.id
if lims_config.lot_category_input_prod else None)
lot_category_id = (config.lot_category_input_prod.id
if config.lot_category_input_prod else None)
elif (not product.purchasable and product.salable):
lot_category_id = (lims_config.lot_category_prod_sale.id
if lims_config.lot_category_prod_sale else None)
lot_category_id = (config.lot_category_prod_sale.id
if config.lot_category_prod_sale else None)
elif (not product.purchasable and not product.salable):
lot_category_id = (
lims_config.lot_category_prod_domestic_use.id if
lims_config.lot_category_prod_domestic_use else None)
config.lot_category_prod_domestic_use.id if
config.lot_category_prod_domestic_use else None)
if lot_category_id:
values['category'] = lot_category_id
return super(Lot, cls).create(vlist)
@ -569,7 +540,7 @@ class ShipmentIn:
return move
class LimsMoveProductionRelated(Wizard):
class MoveProductionRelated(Wizard):
'Related Productions'
__name__ = 'lims.move.production_related'

View File

@ -1,5 +1,5 @@
[tryton]
version=4.4.0
version=4.8.0
depends:
lims
production
@ -7,11 +7,6 @@ depends:
sale
purchase
xml:
security/users.xml
production_view.xml
stock_view.xml
configuration_view.xml
lims_report.xml
data/production_sequences.xml
lims_menu.xml
security/access_rights.xml
stock.xml
production.xml
configuration.xml

View File

@ -3,7 +3,7 @@
<xpath
expr="/form/notebook/page[@id='general']"
position="after">
<page string="Lims" id="lims">
<page string="LIMS" id="lims">
<label name="common_name"/>
<field name="common_name"/>
<label name="chemical_name"/>

View File

@ -1,7 +1,7 @@
<?xml version="1.0"?>
<data>
<xpath
expr="/tree/field[@name='active']"
expr="/tree/field[@name='name']"
position="after">
<field name="divide_lots"/>
</xpath>

View File

@ -1,7 +1,7 @@
<?xml version="1.0"?>
<data>
<xpath
expr="/form/notebook/page[@id='inventory_moves']/field[@name='inventory_moves']"
expr="/form/notebook/page[@name='inventory_moves']/field[@name='inventory_moves']"
position="replace_attributes">
<field name="inventory_moves" colspan="4" mode="tree,form"
view_ids="lims_production.stock_move_in_shipment_view_list,stock.move_view_form"/>

View File

@ -1 +0,0 @@
Version 4.4.0 - 2017-10-08

View File

@ -1,5 +1,5 @@
Copyright (C) 2017 Ignacio Parszyk <iparszyk@kalenislims.com>
Copyright (C) 2013-2017 Sebastián Marró <smarro@kalenislims.com>
Copyright (C) 2017-2018 Ignacio Parszyk <iparszyk@kalenislims.com>
Copyright (C) 2013-2018 Sebastián Marró <smarro@kalenislims.com>
Copyright (C) 2013-2016 Luis Falcon <falcon@gnu.org>
This program is free software: you can redistribute it and/or modify

View File

@ -3,16 +3,16 @@
# the full copyright notices and license terms.
from trytond.pool import Pool
from .project import *
from . import project
def register():
Pool.register(
LimsProject,
LimsEntry,
LimsSample,
LimsCreateSampleStart,
project.Project,
project.Entry,
project.Sample,
project.CreateSampleStart,
module='lims_project', type_='model')
Pool.register(
LimsCreateSample,
project.CreateSample,
module='lims_project', type_='wizard')

View File

@ -1,24 +0,0 @@
<?xml version="1.0"?>
<tryton>
<data>
<!-- Icons -->
<record model="ir.ui.icon" id="project_icon">
<field name="name">lims-project</field>
<field name="path">icons/project.svg</field>
</record>
<!-- Menu -->
<!-- Configuration / Projects -->
<menuitem name="Projects" parent="lims.lims_config"
id="lims_config_projects" sequence="60"/>
<!-- Projects -->
<menuitem parent="lims.lims_menu" action="act_lims_project_list"
id="lims_project_menu" sequence="20"
icon="lims-project"/>
</data>
</tryton>

View File

@ -8,11 +8,10 @@ from trytond.pool import PoolMeta, Pool
from trytond.pyson import Eval
from trytond.transaction import Transaction
__all__ = ['LimsProject', 'LimsEntry', 'LimsSample', 'LimsCreateSampleStart',
'LimsCreateSample']
__all__ = ['Project', 'Entry', 'Sample', 'CreateSampleStart', 'CreateSample']
class LimsProject(ModelSQL, ModelView):
class Project(ModelSQL, ModelView):
'Project'
__name__ = 'lims.project'
_rec_name = 'description'
@ -29,7 +28,7 @@ class LimsProject(ModelSQL, ModelView):
@classmethod
def __setup__(cls):
super(LimsProject, cls).__setup__()
super(Project, cls).__setup__()
t = cls.__table__()
cls._sql_constraints += [
('code_uniq', Unique(t, t.code),
@ -62,7 +61,7 @@ class LimsProject(ModelSQL, ModelView):
return [(cls._rec_name,) + tuple(clause[1:])]
class LimsEntry:
class Entry:
__name__ = 'lims.entry'
__metaclass__ = PoolMeta
@ -74,7 +73,7 @@ class LimsEntry:
@classmethod
def __setup__(cls):
super(LimsEntry, cls).__setup__()
super(Entry, cls).__setup__()
cls.samples.context.update({
'project': Eval('project', None),
})
@ -89,7 +88,7 @@ class LimsEntry:
return res
class LimsSample:
class Sample:
__name__ = 'lims.sample'
__metaclass__ = PoolMeta
@ -98,9 +97,9 @@ class LimsSample:
@staticmethod
def default_project_type():
LimsProject = Pool().get('lims.project')
Project = Pool().get('lims.project')
if Transaction().context.get('project'):
return LimsProject(Transaction().context.get('project')).type
return Project(Transaction().context.get('project')).type
return ''
@fields.depends('entry')
@ -111,24 +110,24 @@ class LimsSample:
return res
class LimsCreateSampleStart:
class CreateSampleStart:
__name__ = 'lims.create_sample.start'
__metaclass__ = PoolMeta
project_type = fields.Char('Type')
class LimsCreateSample:
class CreateSample:
__name__ = 'lims.create_sample'
__metaclass__ = PoolMeta
def default_start(self, fields):
LimsEntry = Pool().get('lims.entry')
Entry = Pool().get('lims.entry')
defaults = super(LimsCreateSample, self).default_start(fields)
defaults = super(CreateSample, self).default_start(fields)
defaults['project_type'] = ''
entry = LimsEntry(Transaction().context['active_id'])
entry = Entry(Transaction().context['active_id'])
if entry.project:
defaults['project_type'] = entry.project.type
return defaults

158
lims_project/project.xml Normal file
View File

@ -0,0 +1,158 @@
<?xml version="1.0"?>
<tryton>
<data>
<!-- Groups -->
<record model="res.group" id="group_lims_conf_project_readonly">
<field name="name">Lims Configuration Projects Read Only</field>
</record>
<record model="res.group" id="group_lims_conf_project_admin">
<field name="name">Lims Configuration Projects Admin</field>
</record>
<record model="res.user-res.group" id="user_admin_group_lims_conf_project_admin">
<field name="user" ref="res.user_admin"/>
<field name="group" ref="group_lims_conf_project_admin"/>
</record>
<record model="res.group" id="group_lims_project_readonly">
<field name="name">Lims Projects Read Only</field>
</record>
<record model="res.group" id="group_lims_project">
<field name="name">Lims Projects</field>
</record>
<record model="res.user-res.group" id="user_admin_group_lims_project">
<field name="user" ref="res.user_admin"/>
<field name="group" ref="group_lims_project"/>
</record>
<!-- Project -->
<record model="ir.ui.view" id="lims_project_view_form">
<field name="model">lims.project</field>
<field name="type">form</field>
<field name="name">project_form</field>
</record>
<record model="ir.ui.view" id="lims_project_view_list">
<field name="model">lims.project</field>
<field name="type">tree</field>
<field name="name">project_list</field>
</record>
<record model="ir.action.act_window" id="act_lims_project_list">
<field name="name">Projects</field>
<field name="res_model">lims.project</field>
</record>
<record model="ir.action.act_window.view" id="act_lims_project_view_list">
<field name="sequence" eval="10"/>
<field name="view" ref="lims_project_view_list"/>
<field name="act_window" ref="act_lims_project_list"/>
</record>
<record model="ir.action.act_window.view" id="act_lims_project_view_form">
<field name="sequence" eval="20"/>
<field name="view" ref="lims_project_view_form"/>
<field name="act_window" ref="act_lims_project_list"/>
</record>
<!-- Entry -->
<record model="ir.ui.view" id="lims_entry_view_form">
<field name="model">lims.entry</field>
<field name="inherit" ref="lims.lims_entry_view_form"/>
<field name="name">entry_form</field>
</record>
<record model="ir.ui.view" id="lims_entry_view_list">
<field name="model">lims.entry</field>
<field name="inherit" ref="lims.lims_entry_view_list"/>
<field name="name">entry_list</field>
</record>
<!-- Icons -->
<record model="ir.ui.icon" id="project_icon">
<field name="name">lims-project</field>
<field name="path">icons/project.svg</field>
</record>
<!-- Menu -->
<!-- Configuration / Projects -->
<menuitem name="Projects" parent="lims.lims_config"
id="lims_config_projects" sequence="60"/>
<!-- Projects -->
<menuitem parent="lims.lims_laboratory" action="act_lims_project_list"
id="lims_project_menu" sequence="20"
icon="lims-project"/>
<!-- Access Rights on Models -->
<record model="ir.model.access" id="access_project">
<field name="model" search="[('model', '=', 'lims.project')]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_project_group_project">
<field name="model" search="[('model', '=', 'lims.project')]"/>
<field name="group" ref="group_lims_project"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<!-- Access Rights on Menu -->
<!-- Configuration / Projects -->
<record model="ir.ui.menu-res.group" id="menu_lims_laboratory_group_conf_project_readonly">
<field name="menu" ref="lims.lims_laboratory"/>
<field name="group" ref="group_lims_conf_project_readonly"/>
</record>
<record model="ir.ui.menu-res.group" id="menu_lims_laboratory_group_conf_project_admin">
<field name="menu" ref="lims.lims_laboratory"/>
<field name="group" ref="group_lims_conf_project_admin"/>
</record>
<record model="ir.ui.menu-res.group" id="menu_config_group_conf_project_readonly">
<field name="menu" ref="lims.lims_config"/>
<field name="group" ref="group_lims_conf_project_readonly"/>
</record>
<record model="ir.ui.menu-res.group" id="menu_config_group_conf_project_admin">
<field name="menu" ref="lims.lims_config"/>
<field name="group" ref="group_lims_conf_project_admin"/>
</record>
<record model="ir.ui.menu-res.group" id="menu_config_projects_group_conf_project_readonly">
<field name="menu" ref="lims_config_projects"/>
<field name="group" ref="group_lims_conf_project_readonly"/>
</record>
<record model="ir.ui.menu-res.group" id="menu_config_projects_group_conf_project_admin">
<field name="menu" ref="lims_config_projects"/>
<field name="group" ref="group_lims_conf_project_admin"/>
</record>
<!-- Projects -->
<record model="ir.ui.menu-res.group" id="menu_lims_laboratory_group_project_readonly">
<field name="menu" ref="lims.lims_laboratory"/>
<field name="group" ref="group_lims_project_readonly"/>
</record>
<record model="ir.ui.menu-res.group" id="menu_lims_laboratory_group_project">
<field name="menu" ref="lims.lims_laboratory"/>
<field name="group" ref="group_lims_project"/>
</record>
<record model="ir.ui.menu-res.group" id="menu_project_group_project_readonly">
<field name="menu" ref="lims_project_menu"/>
<field name="group" ref="group_lims_project_readonly"/>
</record>
<record model="ir.ui.menu-res.group" id="menu_project_group_project">
<field name="menu" ref="lims_project_menu"/>
<field name="group" ref="group_lims_project"/>
</record>
</data>
</tryton>

View File

@ -1,49 +0,0 @@
<?xml version="1.0"?>
<tryton>
<data>
<!-- Menu items in lims_menu.xml -->
<!-- Project -->
<record model="ir.ui.view" id="lims_project_view_form">
<field name="model">lims.project</field>
<field name="type">form</field>
<field name="name">project_form</field>
</record>
<record model="ir.ui.view" id="lims_project_view_list">
<field name="model">lims.project</field>
<field name="type">tree</field>
<field name="name">project_list</field>
</record>
<record model="ir.action.act_window" id="act_lims_project_list">
<field name="name">Projects</field>
<field name="res_model">lims.project</field>
</record>
<record model="ir.action.act_window.view" id="act_lims_project_view_list">
<field name="sequence" eval="10"/>
<field name="view" ref="lims_project_view_list"/>
<field name="act_window" ref="act_lims_project_list"/>
</record>
<record model="ir.action.act_window.view" id="act_lims_project_view_form">
<field name="sequence" eval="20"/>
<field name="view" ref="lims_project_view_form"/>
<field name="act_window" ref="act_lims_project_list"/>
</record>
<!-- Entry -->
<record model="ir.ui.view" id="lims_entry_view_form">
<field name="model">lims.entry</field>
<field name="inherit" ref="lims.lims_entry_view_form"/>
<field name="name">entry_form</field>
</record>
<record model="ir.ui.view" id="lims_entry_view_list">
<field name="model">lims.entry</field>
<field name="inherit" ref="lims.lims_entry_view_list"/>
<field name="name">entry_list</field>
</record>
</data>
</tryton>

View File

@ -1,77 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<!-- Access Rights on Models -->
<record model="ir.model.access" id="access_project">
<field name="model" search="[('model', '=', 'lims.project')]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_project_group_project">
<field name="model" search="[('model', '=', 'lims.project')]"/>
<field name="group" ref="group_lims_project"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<!-- Access Rights on Menu -->
<!-- Configuration / Projects -->
<record model="ir.ui.menu-res.group" id="menu_lims_group_conf_project_readonly">
<field name="menu" ref="lims.lims_menu"/>
<field name="group" ref="group_lims_conf_project_readonly"/>
</record>
<record model="ir.ui.menu-res.group" id="menu_lims_group_conf_project_admin">
<field name="menu" ref="lims.lims_menu"/>
<field name="group" ref="group_lims_conf_project_admin"/>
</record>
<record model="ir.ui.menu-res.group" id="menu_config_group_conf_project_readonly">
<field name="menu" ref="lims.lims_config"/>
<field name="group" ref="group_lims_conf_project_readonly"/>
</record>
<record model="ir.ui.menu-res.group" id="menu_config_group_conf_project_admin">
<field name="menu" ref="lims.lims_config"/>
<field name="group" ref="group_lims_conf_project_admin"/>
</record>
<record model="ir.ui.menu-res.group" id="menu_config_projects_group_conf_project_readonly">
<field name="menu" ref="lims_config_projects"/>
<field name="group" ref="group_lims_conf_project_readonly"/>
</record>
<record model="ir.ui.menu-res.group" id="menu_config_projects_group_conf_project_admin">
<field name="menu" ref="lims_config_projects"/>
<field name="group" ref="group_lims_conf_project_admin"/>
</record>
<!-- Projects -->
<record model="ir.ui.menu-res.group" id="menu_lims_group_project_readonly">
<field name="menu" ref="lims.lims_menu"/>
<field name="group" ref="group_lims_project_readonly"/>
</record>
<record model="ir.ui.menu-res.group" id="menu_lims_group_project">
<field name="menu" ref="lims.lims_menu"/>
<field name="group" ref="group_lims_project"/>
</record>
<record model="ir.ui.menu-res.group" id="menu_project_group_project_readonly">
<field name="menu" ref="lims_project_menu"/>
<field name="group" ref="group_lims_project_readonly"/>
</record>
<record model="ir.ui.menu-res.group" id="menu_project_group_project">
<field name="menu" ref="lims_project_menu"/>
<field name="group" ref="group_lims_project"/>
</record>
</data>
</tryton>

View File

@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<!-- Groups -->
<record model="res.group" id="group_lims_conf_project_readonly">
<field name="name">Lims Configuration Projects Read Only</field>
</record>
<record model="res.group" id="group_lims_conf_project_admin">
<field name="name">Lims Configuration Projects Admin</field>
</record>
<record model="res.user-res.group" id="user_admin_group_lims_conf_project_admin">
<field name="user" ref="res.user_admin"/>
<field name="group" ref="group_lims_conf_project_admin"/>
</record>
<record model="res.group" id="group_lims_project_readonly">
<field name="name">Lims Projects Read Only</field>
</record>
<record model="res.group" id="group_lims_project">
<field name="name">Lims Projects</field>
</record>
<record model="res.user-res.group" id="user_admin_group_lims_project">
<field name="user" ref="res.user_admin"/>
<field name="group" ref="group_lims_project"/>
</record>
</data>
</tryton>

View File

@ -1,9 +1,6 @@
[tryton]
version=4.4.0
version=4.8.0
depends:
lims
xml:
security/users.xml
project_view.xml
lims_menu.xml
security/access_rights.xml
project.xml

View File

@ -1 +0,0 @@
Version 4.4.0 - 2017-10-08

View File

@ -1,5 +1,5 @@
Copyright (C) 2017 Ignacio Parszyk <iparszyk@kalenislims.com>
Copyright (C) 2013-2017 Sebastián Marró <smarro@kalenislims.com>
Copyright (C) 2017-2018 Ignacio Parszyk <iparszyk@kalenislims.com>
Copyright (C) 2013-2018 Sebastián Marró <smarro@kalenislims.com>
Copyright (C) 2013-2016 Luis Falcon <falcon@gnu.org>
This program is free software: you can redistribute it and/or modify

View File

@ -3,11 +3,11 @@
# the full copyright notices and license terms.
from trytond.pool import Pool
from .project import *
from . import project
def register():
Pool.register(
LimsProject,
LimsEntry,
project.Project,
project.Entry,
module='lims_project_interlaboratory', type_='model')

View File

@ -7,7 +7,7 @@ from trytond.model import fields
from trytond.pool import PoolMeta
from trytond.pyson import Eval, Equal, Bool, Not, And
__all__ = ['LimsProject', 'LimsEntry']
__all__ = ['Project', 'Entry']
STATES = {
'required': Bool(Equal(Eval('type'), 'itl')),
@ -16,7 +16,7 @@ DEPENDS = ['type']
PROJECT_TYPE = ('itl', 'Interlaboratory')
class LimsProject:
class Project:
__name__ = 'lims.project'
__metaclass__ = PoolMeta
@ -32,26 +32,26 @@ class LimsProject:
@classmethod
def __setup__(cls):
super(LimsProject, cls).__setup__()
super(Project, cls).__setup__()
project_type = PROJECT_TYPE
if project_type not in cls.type.selection:
cls.type.selection.append(project_type)
@classmethod
def view_attributes(cls):
return super(LimsProject, cls).view_attributes() + [
return super(Project, cls).view_attributes() + [
('//group[@id="itl"]', 'states', {
'invisible': Not(Bool(Equal(Eval('type'), 'itl'))),
})]
class LimsEntry:
class Entry:
__name__ = 'lims.entry'
__metaclass__ = PoolMeta
@classmethod
def __setup__(cls):
super(LimsEntry, cls).__setup__()
super(Entry, cls).__setup__()
project_type = PROJECT_TYPE
if project_type not in cls.project_type.selection:
cls.project_type.selection.append(project_type)

View File

@ -1,5 +1,5 @@
[tryton]
version=4.4.0
version=4.8.0
depends:
lims_project
xml:

View File

@ -1 +0,0 @@
Version 4.4.0 - 2017-10-08

View File

@ -1,5 +1,5 @@
Copyright (C) 2017 Ignacio Parszyk <iparszyk@kalenislims.com>
Copyright (C) 2013-2017 Sebastián Marró <smarro@kalenislims.com>
Copyright (C) 2017-2018 Ignacio Parszyk <iparszyk@kalenislims.com>
Copyright (C) 2013-2018 Sebastián Marró <smarro@kalenislims.com>
Copyright (C) 2013-2016 Luis Falcon <falcon@gnu.org>
This program is free software: you can redistribute it and/or modify

View File

@ -3,61 +3,59 @@
# the full copyright notices and license terms.
from trytond.pool import Pool
from .configuration import *
from .project import *
from report import *
from wizard import *
from . import configuration
from . import project
def register():
Pool.register(
LimsConfiguration,
LimsConfigurationSequence,
LimsLabWorkYear,
LimsLabWorkYearSequence,
LimsProject,
LimsEntry,
LimsProjectReferenceElement,
LimsProjectSolventAndReagent,
LimsProjectSampleInCustody,
LimsProjectDeviationAndAmendment,
LimsProjectDeviationAndAmendmentProfessional,
LimsProjectChangeLog,
LimsSample,
LimsCreateSampleStart,
LimsProjectProfessionalPosition,
LimsProjectLaboratoryProfessional,
Lot,
LimsProjectReOpenStart,
LimsProjectGLPReport03PrintStart,
LimsProjectGLPReport05PrintStart,
LimsProjectGLPReport10PrintStart,
LimsProjectGLPReport12PrintStart,
configuration.Configuration,
configuration.ConfigurationSequence,
configuration.LabWorkYear,
configuration.LabWorkYearSequence,
project.Project,
project.Entry,
project.ProjectReferenceElement,
project.ProjectSolventAndReagent,
project.ProjectSampleInCustody,
project.ProjectDeviationAndAmendment,
project.ProjectDeviationAndAmendmentProfessional,
project.ProjectChangeLog,
project.Sample,
project.CreateSampleStart,
project.ProjectProfessionalPosition,
project.ProjectLaboratoryProfessional,
project.Lot,
project.ProjectReOpenStart,
project.ProjectGLPReport03PrintStart,
project.ProjectGLPReport05PrintStart,
project.ProjectGLPReport10PrintStart,
project.ProjectGLPReport12PrintStart,
module='lims_project_study_plan', type_='model')
Pool.register(
LimsCreateSample,
LimsProjectReOpen,
LimsProjectGLPReport03Print,
LimsProjectGLPReport05Print,
LimsProjectGLPReport10Print,
LimsProjectGLPReport12Print,
project.CreateSample,
project.ProjectReOpen,
project.ProjectGLPReport03Print,
project.ProjectGLPReport05Print,
project.ProjectGLPReport10Print,
project.ProjectGLPReport12Print,
module='lims_project_study_plan', type_='wizard')
Pool.register(
LimsProjectGLPReport01,
LimsProjectGLPReport02,
LimsProjectGLPReport03,
LimsProjectGLPReport04,
LimsProjectGLPReport05,
LimsProjectGLPReport06,
LimsProjectGLPReport07,
LimsProjectGLPReport08,
LimsProjectGLPReport09,
LimsProjectGLPReport10,
LimsProjectGLPReport11,
LimsProjectGLPReport12,
LimsProjectGLPReport13,
LimsProjectGLPReportStudyPlan,
LimsProjectGLPReportFinalRP,
LimsProjectGLPReportFinalFOR,
LimsProjectGLPReportAnalyticalPhase,
project.ProjectGLPReport01,
project.ProjectGLPReport02,
project.ProjectGLPReport03,
project.ProjectGLPReport04,
project.ProjectGLPReport05,
project.ProjectGLPReport06,
project.ProjectGLPReport07,
project.ProjectGLPReport08,
project.ProjectGLPReport09,
project.ProjectGLPReport10,
project.ProjectGLPReport11,
project.ProjectGLPReport12,
project.ProjectGLPReport13,
project.ProjectGLPReportStudyPlan,
project.ProjectGLPReportFinalRP,
project.ProjectGLPReportFinalFOR,
project.ProjectGLPReportAnalyticalPhase,
module='lims_project_study_plan', type_='report')

View File

@ -3,16 +3,15 @@
# The COPYRIGHT file at the top level of this repository contains
# the full copyright notices and license terms.
from trytond import backend
from trytond.model import fields
from trytond.pool import PoolMeta, Pool
from trytond.pyson import Eval
__all__ = ['LimsConfiguration', 'LimsConfigurationSequence',
'LimsLabWorkYear', 'LimsLabWorkYearSequence']
__all__ = ['Configuration', 'ConfigurationSequence', 'LabWorkYear',
'LabWorkYearSequence']
class LimsConfiguration:
class Configuration:
__name__ = 'lims.configuration'
__metaclass__ = PoolMeta
@ -29,7 +28,7 @@ class LimsConfiguration:
pool = Pool()
if field == 'sample_in_custody_sequence':
return pool.get('lims.configuration.sequence')
return super(LimsConfiguration, cls).multivalue_model(field)
return super(Configuration, cls).multivalue_model(field)
@classmethod
def default_sample_in_custody_sequence(cls, **pattern):
@ -37,7 +36,7 @@ class LimsConfiguration:
'sample_in_custody_sequence').default_sample_in_custody_sequence()
class LimsConfigurationSequence:
class ConfigurationSequence:
__name__ = 'lims.configuration.sequence'
__metaclass__ = PoolMeta
@ -58,7 +57,7 @@ class LimsConfigurationSequence:
return None
class LimsLabWorkYear:
class LabWorkYear:
__name__ = 'lims.lab.workyear'
__metaclass__ = PoolMeta
@ -75,10 +74,10 @@ class LimsLabWorkYear:
pool = Pool()
if field == 'project_study_plan_sequence':
return pool.get('lims.lab.workyear.sequence')
return super(LimsLabWorkYear, cls).multivalue_model(field)
return super(LabWorkYear, cls).multivalue_model(field)
class LimsLabWorkYearSequence:
class LabWorkYearSequence:
__name__ = 'lims.lab.workyear.sequence'
__metaclass__ = PoolMeta

View File

@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<tryton>
<data noupdate="1">
<!-- Sequences for Sample in Custody -->
<record model="ir.sequence.type" id="seq_type_sample_in_custody">
<field name="name">Sample in Custody</field>
<field name="code">lims.project.sample_in_custody</field>
</record>
<record model="ir.sequence.type-res.group"
id="seq_type_sample_in_custody_group_admin">
<field name="sequence_type" ref="seq_type_sample_in_custody"/>
<field name="group" ref="res.group_admin"/>
</record>
<record model="ir.sequence" id="seq_sample_in_custody">
<field name="name">Sample in Custody</field>
<field name="code">lims.project.sample_in_custody</field>
</record>
<!-- Sequences for Project -->
<record model="ir.sequence.type" id="seq_type_stp_project">
<field name="name">Project</field>
<field name="code">lims.project</field>
</record>
<record model="ir.sequence.type-res.group"
id="seq_type_stp_project_group_admin">
<field name="sequence_type" ref="seq_type_stp_project"/>
<field name="group" ref="res.group_admin"/>
</record>
</data>
</tryton>

View File

@ -1,13 +0,0 @@
<?xml version="1.0"?>
<tryton>
<data>
<!-- Menu -->
<!-- Configuration / Projects / Professional Positions -->
<menuitem parent="lims_project.lims_config_projects"
action="act_lims_project_stp_professional_position_list"
id="lims_project_stp_professional_position_menu" sequence="20"/>
</data>
</tryton>

Some files were not shown because too many files have changed in this diff Show More