<commit_before># -*- coding: utf-8 -*-
##############################################################################
#
#    OpenERP, Open Source Management Solution
#    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

from datetime import datetime
from tools.translate import _
from osv import fields, osv
from openerp.addons.resource.faces import task as Task

class project_phase(osv.osv):
    _name = "project.phase"
    _description = "Project Phase"

    def _check_recursion(self, cr, uid, ids, context=None):
         if context is None:
            context = {}

         data_phase = self.browse(cr, uid, ids[0], context=context)
         prev_ids = data_phase.previous_phase_ids
         next_ids = data_phase.next_phase_ids
         # it should neither be in prev_ids nor in next_ids
         if (data_phase in prev_ids) or (data_phase in next_ids):
             return False
         ids = [id for id in prev_ids if id in next_ids]
         # both prev_ids and next_ids must be unique
         if ids:
             return False
         # unrelated project
         prev_ids = [rec.id for rec in prev_ids]
         next_ids = [rec.id for rec in next_ids]
         # iter prev_ids
         while prev_ids:
             cr.execute('SELECT distinct prv_phase_id FROM project_phase_rel WHERE next_phase_id IN %s', (tuple(prev_ids),))
             prv_phase_ids = filter(None, map(lambda x: x[0], cr.fetchall()))
             if data_phase.id in prv_phase_ids:
                 return False
             ids = [id for id in prv_phase_ids if id in next_ids]
             if ids:
                 return False
             prev_ids = prv_phase_ids
         # iter next_ids
         while next_ids:
             cr.execute('SELECT distinct next_phase_id FROM project_phase_rel WHERE prv_phase_id IN %s', (tuple(next_ids),))
             next_phase_ids = filter(None, map(lambda x: x[0], cr.fetchall()))
             if data_phase.id in next_phase_ids:
                 return False
             ids = [id for id in next_phase_ids if id in prev_ids]
             if ids:
                 return False
             next_ids = next_phase_ids
         return True

    def _check_dates(self, cr, uid, ids, context=None):
         for phase in self.read(cr, uid, ids, ['date_start', 'date_end'], context=context):
             if phase['date_start'] and phase['date_end'] and phase['date_start'] > phase['date_end']:
                 return False
         return True

    def _compute_progress(self, cr, uid, ids, field_name, arg, context=None):
        res = {}
        if not ids:
            return res
        for phase in self.browse(cr, uid, ids, context=context):
            if phase.state=='done':
                res[phase.id] = 100.0
                continue
            elif phase.state=="cancelled":
                res[phase.id] = 0.0
                continue
            elif not phase.task_ids:
                res[phase.id] = 0.0
                continue

            tot = done = 0.0
            for task in phase.task_ids:
                tot += task.total_hours
                done += min(task.effective_hours, task.total_hours)

            if not tot:
                res[phase.id] = 0.0
            else:
                res[phase.id] = round(100.0 * done / tot, 2)
        return res

    _columns = {
        'name': fields.char("Name", size=64, required=True),
        'date_start': fields.datetime('Start Date', select=True, help="It's computed by the scheduler according the project date or the end date of the previous phase.", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
        'date_end': fields.datetime('End Date', help=" It's computed by the scheduler according to the start date and the duration.", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
        'constraint_date_start': fields.datetime('Minimum Start Date', help='force the phase to start after this date', states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
        'constraint_date_end': fields.datetime('Deadline', help='force the phase to finish before this date', states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
        'project_id': fields.many2one('project.project', 'Project', required=True, select=True),
        'next_phase_ids': fields.many2many('project.phase', 'project_phase_rel', 'prv_phase_id', 'next_phase_id', 'Next Phases', states={'cancelled':[('readonly',True)]}),
        'previous_phase_ids': fields.many2many('project.phase', 'project_phase_rel', 'next_phase_id', 'prv_phase_id', 'Previous Phases', states={'cancelled':[('readonly',True)]}),
        'sequence': fields.integer('Sequence', select=True, help="Gives the sequence order when displaying a list of phases."),
        'duration': fields.float('Duration', required=True, help="By default in days", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
        'product_uom': fields.many2one('product.uom', 'Duration Unit of Measure', required=True, help="Unit of Measure (Unit of Measure) is the unit of measurement for Duration", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
        'task_ids': fields.one2many('project.task', 'phase_id', "Project Tasks", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
        'user_force_ids': fields.many2many('res.users', string='Force Assigned Users'),
        'user_ids': fields.one2many('project.user.allocation', 'phase_id', "Assigned Users",states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]},
            help="The resources on the project can be computed automatically by the scheduler."),
        'state': fields.selection([('draft', 'New'), ('cancelled', 'Cancelled'),('open', 'In Progress'), ('pending', 'Pending'), ('done', 'Done')], 'Status', readonly=True, required=True,
                                  help='If the phase is created the state \'Draft\'.\n If the phase is started, the state becomes \'In Progress\'.\n If review is needed the phase is in \'Pending\' state.\
                                  \n If the phase is over, the states is set to \'Done\'.'),
        'progress': fields.function(_compute_progress, string='Progress', help="Computed based on related tasks"),
     }
    _defaults = {
        'state': 'draft',
        'sequence': 10,
        'product_uom': lambda self,cr,uid,c: self.pool.get('product.uom').search(cr, uid, [('name', '=', _('Day'))], context=c)[0]
    }
    _order = "project_id, date_start, sequence"
    _constraints = [
        (_check_recursion,'Loops in phases not allowed',['next_phase_ids', 'previous_phase_ids']),
        (_check_dates, 'Phase start-date must be lower than phase end-date.', ['date_start', 'date_end']),
    ]

    def onchange_project(self, cr, uid, ids, project, context=None):
        return {}

    def copy(self, cr, uid, id, default=None, context=None):
        if default is None:
            default = {}
        if not default.get('name', False):
            default['name'] = self.browse(cr, uid, id, context=context).name + _(' (copy)')
        return super(project_phase, self).copy(cr, uid, id, default, context)

    def set_draft(self, cr, uid, ids, *args):
        self.write(cr, uid, ids, {'state': 'draft'})
        return True

    def set_open(self, cr, uid, ids, *args):
        self.write(cr, uid, ids, {'state': 'open'})
        return True

    def set_pending(self, cr, uid, ids, *args):
        self.write(cr, uid, ids, {'state': 'pending'})
        return True

    def set_cancel(self, cr, uid, ids, *args):
        self.write(cr, uid, ids, {'state': 'cancelled'})
        return True

    def set_done(self, cr, uid, ids, *args):
        self.write(cr, uid, ids, {'state': 'done'})
        return True

    def generate_phase(self, cr, uid, phases, context=None):
        context = context or {}
        result = ""

        task_pool = self.pool.get('project.task')
        for phase in phases:
            if phase.state in ('done','cancelled'):
                continue
            duration_uom = {
                'days': 'd', 'day': 'd', 'd':'d',
                'months': 'm', 'month':'month', 'm':'m',
                'weeks': 'w', 'week': 'w', 'w':'w',
                'hours': 'H', 'hour': 'H', 'h':'H',
            }.get(phase.product_uom.name.lower(), "H")
            duration = str(phase.duration) + duration_uom
            result += '''
    def Phase_%s():
        effort = \"%s\"''' % (phase.id, duration)
            start = []
            if phase.constraint_date_start:
                start.append('datetime.datetime.strptime("'+str(phase.constraint_date_start)+'", "%Y-%m-%d %H:%M:%S")')
            for previous_phase in phase.previous_phase_ids:
                start.append("up.Phase_%s.end" % (previous_phase.id,))
            if start:
                result += '''
        start = max(%s)
''' % (','.join(start))

            if phase.user_force_ids:
                result += '''
        resource = %s
''' % '|'.join(map(lambda x: 'User_'+str(x.id), phase.user_force_ids))

            result += task_pool._generate_task(cr, uid, phase.task_ids, ident=8, context=context)
            result += "\n"

        return result
project_phase()

class project_user_allocation(osv.osv):
    _name = 'project.user.allocation'
    _description = 'Phase User Allocation'
    _rec_name = 'user_id'
    _columns = {
        'user_id': fields.many2one('res.users', 'User', required=True),
        'phase_id': fields.many2one('project.phase', 'Project Phase', ondelete='cascade', required=True),
        'project_id': fields.related('phase_id', 'project_id', type='many2one', relation="project.project", string='Project', store=True),
        'date_start': fields.datetime('Start Date', help="Starting Date"),
        'date_end': fields.datetime('End Date', help="Ending Date"),
    }
project_user_allocation()

class project(osv.osv):
    _inherit = "project.project"

    def _phase_count(self, cr, uid, ids, field_name, arg, context=None):
        res = dict.fromkeys(ids, 0)
        phase_ids = self.pool.get('project.phase').search(cr, uid, [('project_id', 'in', ids)])
        for phase in self.pool.get('project.phase').browse(cr, uid, phase_ids, context):
            res[phase.project_id.id] += 1
        return res

    _columns = {
        'phase_ids': fields.one2many('project.phase', 'project_id', "Project Phases"),
        'phase_count': fields.function(_phase_count, type='integer', string="Open Phases"),
    }

    def schedule_phases(self, cr, uid, ids, context=None):
        context = context or {}
        if type(ids) in (long, int,):
            ids = [ids]
        projects = self.browse(cr, uid, ids, context=context)
        result = self._schedule_header(cr, uid, ids, context=context)
        for project in projects:
            result += self._schedule_project(cr, uid, project, context=context)
            result += self.pool.get('project.phase').generate_phase(cr, uid, project.phase_ids, context=context)

        local_dict = {}
        exec result in local_dict
        projects_gantt = Task.BalancedProject(local_dict['Project'])

        for project in projects:
            project_gantt = getattr(projects_gantt, 'Project_%d' % (project.id,))
            for phase in project.phase_ids:
                if phase.state in ('done','cancelled'):
                    continue
                # Maybe it's better to update than unlink/create if it already exists ?
                p = getattr(project_gantt, 'Phase_%d' % (phase.id,))

                self.pool.get('project.user.allocation').unlink(cr, uid,
                    [x.id for x in phase.user_ids],
                    context=context
                )

                for r in p.booked_resource:
                    self.pool.get('project.user.allocation').create(cr, uid, {
                        'user_id': int(r.name[5:]),
                        'phase_id': phase.id,
                        'date_start': p.start.strftime('%Y-%m-%d %H:%M:%S'),
                        'date_end': p.end.strftime('%Y-%m-%d %H:%M:%S')
                    }, context=context)
                self.pool.get('project.phase').write(cr, uid, [phase.id], {
                    'date_start': p.start.strftime('%Y-%m-%d %H:%M:%S'),
                    'date_end': p.end.strftime('%Y-%m-%d %H:%M:%S')
                }, context=context)
        return True
project()

class account_analytic_account(osv.osv):
    _inherit = 'account.analytic.account'
    _description = 'Analytic Account'
    _columns = {
        'use_phases': fields.boolean('Phases', help="Check this field if you plan to use phase-based scheduling"),
    }

    def on_change_template(self, cr, uid, ids, template_id, context=None):
        res = super(account_analytic_account, self).on_change_template(cr, uid, ids, template_id, context=context)
        if template_id and 'value' in res:
            template = self.browse(cr, uid, template_id, context=context)
            res['value']['use_phases'] = template.use_phases
        return res

    def _trigger_project_creation(self, cr, uid, vals, context=None):
        res= super(account_analytic_account, self)._trigger_project_creation(cr, uid, vals, context=context)
        return res or vals.get('use_phases')

account_analytic_account()

class project_task(osv.osv):
    _inherit = "project.task"
    _columns = {
        'phase_id': fields.many2one('project.phase', 'Project Phase'),
    }
project_task()

# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
<commit_msg>[FIX] project_long_term: filtered phases with project_id on task form<commit_after># -*- coding: utf-8 -*-
##############################################################################
#
#    OpenERP, Open Source Management Solution
#    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

from datetime import datetime
from tools.translate import _
from osv import fields, osv
from openerp.addons.resource.faces import task as Task

class project_phase(osv.osv):
    _name = "project.phase"
    _description = "Project Phase"

    def _check_recursion(self, cr, uid, ids, context=None):
         if context is None:
            context = {}

         data_phase = self.browse(cr, uid, ids[0], context=context)
         prev_ids = data_phase.previous_phase_ids
         next_ids = data_phase.next_phase_ids
         # it should neither be in prev_ids nor in next_ids
         if (data_phase in prev_ids) or (data_phase in next_ids):
             return False
         ids = [id for id in prev_ids if id in next_ids]
         # both prev_ids and next_ids must be unique
         if ids:
             return False
         # unrelated project
         prev_ids = [rec.id for rec in prev_ids]
         next_ids = [rec.id for rec in next_ids]
         # iter prev_ids
         while prev_ids:
             cr.execute('SELECT distinct prv_phase_id FROM project_phase_rel WHERE next_phase_id IN %s', (tuple(prev_ids),))
             prv_phase_ids = filter(None, map(lambda x: x[0], cr.fetchall()))
             if data_phase.id in prv_phase_ids:
                 return False
             ids = [id for id in prv_phase_ids if id in next_ids]
             if ids:
                 return False
             prev_ids = prv_phase_ids
         # iter next_ids
         while next_ids:
             cr.execute('SELECT distinct next_phase_id FROM project_phase_rel WHERE prv_phase_id IN %s', (tuple(next_ids),))
             next_phase_ids = filter(None, map(lambda x: x[0], cr.fetchall()))
             if data_phase.id in next_phase_ids:
                 return False
             ids = [id for id in next_phase_ids if id in prev_ids]
             if ids:
                 return False
             next_ids = next_phase_ids
         return True

    def _check_dates(self, cr, uid, ids, context=None):
         for phase in self.read(cr, uid, ids, ['date_start', 'date_end'], context=context):
             if phase['date_start'] and phase['date_end'] and phase['date_start'] > phase['date_end']:
                 return False
         return True

    def _compute_progress(self, cr, uid, ids, field_name, arg, context=None):
        res = {}
        if not ids:
            return res
        for phase in self.browse(cr, uid, ids, context=context):
            if phase.state=='done':
                res[phase.id] = 100.0
                continue
            elif phase.state=="cancelled":
                res[phase.id] = 0.0
                continue
            elif not phase.task_ids:
                res[phase.id] = 0.0
                continue

            tot = done = 0.0
            for task in phase.task_ids:
                tot += task.total_hours
                done += min(task.effective_hours, task.total_hours)

            if not tot:
                res[phase.id] = 0.0
            else:
                res[phase.id] = round(100.0 * done / tot, 2)
        return res

    _columns = {
        'name': fields.char("Name", size=64, required=True),
        'date_start': fields.datetime('Start Date', select=True, help="It's computed by the scheduler according the project date or the end date of the previous phase.", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
        'date_end': fields.datetime('End Date', help=" It's computed by the scheduler according to the start date and the duration.", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
        'constraint_date_start': fields.datetime('Minimum Start Date', help='force the phase to start after this date', states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
        'constraint_date_end': fields.datetime('Deadline', help='force the phase to finish before this date', states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
        'project_id': fields.many2one('project.project', 'Project', required=True, select=True),
        'next_phase_ids': fields.many2many('project.phase', 'project_phase_rel', 'prv_phase_id', 'next_phase_id', 'Next Phases', states={'cancelled':[('readonly',True)]}),
        'previous_phase_ids': fields.many2many('project.phase', 'project_phase_rel', 'next_phase_id', 'prv_phase_id', 'Previous Phases', states={'cancelled':[('readonly',True)]}),
        'sequence': fields.integer('Sequence', select=True, help="Gives the sequence order when displaying a list of phases."),
        'duration': fields.float('Duration', required=True, help="By default in days", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
        'product_uom': fields.many2one('product.uom', 'Duration Unit of Measure', required=True, help="Unit of Measure (Unit of Measure) is the unit of measurement for Duration", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
        'task_ids': fields.one2many('project.task', 'phase_id', "Project Tasks", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
        'user_force_ids': fields.many2many('res.users', string='Force Assigned Users'),
        'user_ids': fields.one2many('project.user.allocation', 'phase_id', "Assigned Users",states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]},
            help="The resources on the project can be computed automatically by the scheduler."),
        'state': fields.selection([('draft', 'New'), ('cancelled', 'Cancelled'),('open', 'In Progress'), ('pending', 'Pending'), ('done', 'Done')], 'Status', readonly=True, required=True,
                                  help='If the phase is created the state \'Draft\'.\n If the phase is started, the state becomes \'In Progress\'.\n If review is needed the phase is in \'Pending\' state.\
                                  \n If the phase is over, the states is set to \'Done\'.'),
        'progress': fields.function(_compute_progress, string='Progress', help="Computed based on related tasks"),
     }
    _defaults = {
        'state': 'draft',
        'sequence': 10,
        'product_uom': lambda self,cr,uid,c: self.pool.get('product.uom').search(cr, uid, [('name', '=', _('Day'))], context=c)[0]
    }
    _order = "project_id, date_start, sequence"
    _constraints = [
        (_check_recursion,'Loops in phases not allowed',['next_phase_ids', 'previous_phase_ids']),
        (_check_dates, 'Phase start-date must be lower than phase end-date.', ['date_start', 'date_end']),
    ]

    def onchange_project(self, cr, uid, ids, project, context=None):
        return {}

    def copy(self, cr, uid, id, default=None, context=None):
        if default is None:
            default = {}
        if not default.get('name', False):
            default['name'] = self.browse(cr, uid, id, context=context).name + _(' (copy)')
        return super(project_phase, self).copy(cr, uid, id, default, context)

    def set_draft(self, cr, uid, ids, *args):
        self.write(cr, uid, ids, {'state': 'draft'})
        return True

    def set_open(self, cr, uid, ids, *args):
        self.write(cr, uid, ids, {'state': 'open'})
        return True

    def set_pending(self, cr, uid, ids, *args):
        self.write(cr, uid, ids, {'state': 'pending'})
        return True

    def set_cancel(self, cr, uid, ids, *args):
        self.write(cr, uid, ids, {'state': 'cancelled'})
        return True

    def set_done(self, cr, uid, ids, *args):
        self.write(cr, uid, ids, {'state': 'done'})
        return True

    def generate_phase(self, cr, uid, phases, context=None):
        context = context or {}
        result = ""

        task_pool = self.pool.get('project.task')
        for phase in phases:
            if phase.state in ('done','cancelled'):
                continue
            duration_uom = {
                'days': 'd', 'day': 'd', 'd':'d',
                'months': 'm', 'month':'month', 'm':'m',
                'weeks': 'w', 'week': 'w', 'w':'w',
                'hours': 'H', 'hour': 'H', 'h':'H',
            }.get(phase.product_uom.name.lower(), "H")
            duration = str(phase.duration) + duration_uom
            result += '''
    def Phase_%s():
        effort = \"%s\"''' % (phase.id, duration)
            start = []
            if phase.constraint_date_start:
                start.append('datetime.datetime.strptime("'+str(phase.constraint_date_start)+'", "%Y-%m-%d %H:%M:%S")')
            for previous_phase in phase.previous_phase_ids:
                start.append("up.Phase_%s.end" % (previous_phase.id,))
            if start:
                result += '''
        start = max(%s)
''' % (','.join(start))

            if phase.user_force_ids:
                result += '''
        resource = %s
''' % '|'.join(map(lambda x: 'User_'+str(x.id), phase.user_force_ids))

            result += task_pool._generate_task(cr, uid, phase.task_ids, ident=8, context=context)
            result += "\n"

        return result
project_phase()

class project_user_allocation(osv.osv):
    _name = 'project.user.allocation'
    _description = 'Phase User Allocation'
    _rec_name = 'user_id'
    _columns = {
        'user_id': fields.many2one('res.users', 'User', required=True),
        'phase_id': fields.many2one('project.phase', 'Project Phase', ondelete='cascade', required=True),
        'project_id': fields.related('phase_id', 'project_id', type='many2one', relation="project.project", string='Project', store=True),
        'date_start': fields.datetime('Start Date', help="Starting Date"),
        'date_end': fields.datetime('End Date', help="Ending Date"),
    }
project_user_allocation()

class project(osv.osv):
    _inherit = "project.project"

    def _phase_count(self, cr, uid, ids, field_name, arg, context=None):
        res = dict.fromkeys(ids, 0)
        phase_ids = self.pool.get('project.phase').search(cr, uid, [('project_id', 'in', ids)])
        for phase in self.pool.get('project.phase').browse(cr, uid, phase_ids, context):
            res[phase.project_id.id] += 1
        return res

    _columns = {
        'phase_ids': fields.one2many('project.phase', 'project_id', "Project Phases"),
        'phase_count': fields.function(_phase_count, type='integer', string="Open Phases"),
    }

    def schedule_phases(self, cr, uid, ids, context=None):
        context = context or {}
        if type(ids) in (long, int,):
            ids = [ids]
        projects = self.browse(cr, uid, ids, context=context)
        result = self._schedule_header(cr, uid, ids, context=context)
        for project in projects:
            result += self._schedule_project(cr, uid, project, context=context)
            result += self.pool.get('project.phase').generate_phase(cr, uid, project.phase_ids, context=context)

        local_dict = {}
        exec result in local_dict
        projects_gantt = Task.BalancedProject(local_dict['Project'])

        for project in projects:
            project_gantt = getattr(projects_gantt, 'Project_%d' % (project.id,))
            for phase in project.phase_ids:
                if phase.state in ('done','cancelled'):
                    continue
                # Maybe it's better to update than unlink/create if it already exists ?
                p = getattr(project_gantt, 'Phase_%d' % (phase.id,))

                self.pool.get('project.user.allocation').unlink(cr, uid,
                    [x.id for x in phase.user_ids],
                    context=context
                )

                for r in p.booked_resource:
                    self.pool.get('project.user.allocation').create(cr, uid, {
                        'user_id': int(r.name[5:]),
                        'phase_id': phase.id,
                        'date_start': p.start.strftime('%Y-%m-%d %H:%M:%S'),
                        'date_end': p.end.strftime('%Y-%m-%d %H:%M:%S')
                    }, context=context)
                self.pool.get('project.phase').write(cr, uid, [phase.id], {
                    'date_start': p.start.strftime('%Y-%m-%d %H:%M:%S'),
                    'date_end': p.end.strftime('%Y-%m-%d %H:%M:%S')
                }, context=context)
        return True
project()

class account_analytic_account(osv.osv):
    _inherit = 'account.analytic.account'
    _description = 'Analytic Account'
    _columns = {
        'use_phases': fields.boolean('Phases', help="Check this field if you plan to use phase-based scheduling"),
    }

    def on_change_template(self, cr, uid, ids, template_id, context=None):
        res = super(account_analytic_account, self).on_change_template(cr, uid, ids, template_id, context=context)
        if template_id and 'value' in res:
            template = self.browse(cr, uid, template_id, context=context)
            res['value']['use_phases'] = template.use_phases
        return res

    def _trigger_project_creation(self, cr, uid, vals, context=None):
        res= super(account_analytic_account, self)._trigger_project_creation(cr, uid, vals, context=context)
        return res or vals.get('use_phases')

account_analytic_account()

class project_task(osv.osv):
    _inherit = "project.task"
    _columns = {
        'phase_id': fields.many2one('project.phase', 'Project Phase', domain="[('project_id', '=', project_id)]"),
    }
project_task()

# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
<|endoftext|>


<commit_before>#!/usr/bin/env python2.7

# to use gimport: use wget or curl to download the gimport.py file locally
# ex. os.system('wget https://github.com/scottidler/gimport/raw/master/gimport.py')

import os
import re
import imp
import sys
import argparse
import contextlib
import subprocess

from subprocess import Popen, PIPE

sys.dont_write_bytecode = True

@contextlib.contextmanager
def cd(*args, **kwargs):
    mkdir = kwargs.pop('mkdir', True)
    verbose = kwargs.pop('verbose', False)
    path = os.path.sep.join(args)
    path = os.path.normpath(path)
    path = os.path.expanduser(path)
    prev = os.getcwd()
    if path != prev:
        if mkdir:
            run('mkdir -p %(path)s' % locals(), verbose=verbose)
        os.chdir(path)
        curr = os.getcwd()
        sys.path.append(curr)
        if verbose:
            print 'cd %s' % curr
    try:
        yield
    finally:
        if path != prev:
            sys.path.remove(curr)
            os.chdir(prev)
            if verbose:
                print 'cd %s' % prev

def run(*args, **kwargs):
    nerf = kwargs.pop('nerf', False)
    shell = kwargs.pop('shell', True)
    verbose = kwargs.pop('verbose', False)
    if (verbose or nerf) and args[0]:
        print args[0]
    if nerf:
        return (None, 'nerfed', 'nerfed')
    process = Popen(shell=shell, *args, **kwargs)
    stdout, stderr = process.communicate()
    exitcode = process.poll()
    if verbose and stdout:
        print stdout
    return exitcode, stdout, stderr

def decompose(giturl):
    pattern = '(((ssh|https)://)?([a-zA-Z0-9_.\-]+@)?)([a-zA-Z0-9_.\-]+)([:/]{1,2})([a-zA-Z0-9_.\-\/]+)'
    match = re.search(pattern, giturl)
    return match.groups()[-1]

def divine(giturl, revision):
    r2c = {}
    c2r = {}
    result = run('git ls-remote %(giturl)s' % locals(), stdout=PIPE)[1].strip()
    for line in result.split('\n'):
        commit, refname = line.split('\t')
        r2c[refname] = commit
        c2r[commit] = refname

    refnames = [
        'refs/heads/' + revision,
        'refs/tags/' + revision,
        revision
    ]

    commit = None
    for refname in refnames:
        commit = r2c.get(refname, None)
        if commit:
            break

    if not commit:
        commit = revision

    return c2r.get(commit, None), commit

def clone(giturl, reponame, refname, commit, gimport_cache, repo_cache):
    path = os.path.join(gimport_cache, reponame)
    with cd(path, mkdir=True):
        if not os.path.isdir(commit):
            run('git clone %(giturl)s %(commit)s' % locals(), stdout=PIPE, stderr=PIPE)
        with cd(commit):
            run('git clean -x -f -d', stdout=PIPE, stderr=PIPE)
            run('git checkout %(commit)s' % locals(), stdout=PIPE, stderr=PIPE)
    return os.path.join(path, commit)

def gimport(giturl, revision, filepath, imports=None, gimport_cache='.gimport', repo_cache=None):
    reponame = decompose(giturl)
    refname, commit = divine(giturl, revision)
    path = clone(giturl, reponame, refname, commit, gimport_cache, repo_cache)
    with cd(path):
        modname = os.path.splitext(os.path.basename(filepath))[0]
        module = imp.load_source(modname, filepath)

        if imports:
            return [ module[import_] for import_ in imports ]
        return module

def main(args):

    parser = argparse.ArgumentParser()
    parser.add_argument(
        '--gimport-cache',
        metavar='GIMPORT-CACHE',
        default='.gimport',
        help='root path to store all gimport cached files')
    parser.add_argument(
        '--repo-cache',
        metavar='REPO-CACHE',
        help='path to cached repos to support fast cloning')
    parser.add_argument(
        '--imports',
        nargs='+',
        help='list of imports')
    parser.add_argument(
        'giturl',
        help='the giturl to be used with git clone')
    parser.add_argument(
        'revision',
        help='revision to checkout')
    parser.add_argument(
        'filepath',
        help='the filepath inside the git repo')
    ns = parser.parse_args()
    print gimport(**ns.__dict__)

    return 0

if __name__ == '__main__':
    sys.exit(main(sys.argv[1:]) )
<commit_msg>better example of what needs to be placed in calling code... -sai<commit_after>#!/usr/bin/env python2.7

# to use gimport: use wget or curl to download the gimport.py file locally
#ie.  os.system('wget -q https://github.com/scottidler/gimport/raw/master/gimport.py -O gimport.py')

import os
import re
import imp
import sys
import argparse
import contextlib
import subprocess

from subprocess import Popen, PIPE

sys.dont_write_bytecode = True

@contextlib.contextmanager
def cd(*args, **kwargs):
    mkdir = kwargs.pop('mkdir', True)
    verbose = kwargs.pop('verbose', False)
    path = os.path.sep.join(args)
    path = os.path.normpath(path)
    path = os.path.expanduser(path)
    prev = os.getcwd()
    if path != prev:
        if mkdir:
            run('mkdir -p %(path)s' % locals(), verbose=verbose)
        os.chdir(path)
        curr = os.getcwd()
        sys.path.append(curr)
        if verbose:
            print 'cd %s' % curr
    try:
        yield
    finally:
        if path != prev:
            sys.path.remove(curr)
            os.chdir(prev)
            if verbose:
                print 'cd %s' % prev

def run(*args, **kwargs):
    nerf = kwargs.pop('nerf', False)
    shell = kwargs.pop('shell', True)
    verbose = kwargs.pop('verbose', False)
    if (verbose or nerf) and args[0]:
        print args[0]
    if nerf:
        return (None, 'nerfed', 'nerfed')
    process = Popen(shell=shell, *args, **kwargs)
    stdout, stderr = process.communicate()
    exitcode = process.poll()
    if verbose and stdout:
        print stdout
    return exitcode, stdout, stderr

def decompose(giturl):
    pattern = '(((ssh|https)://)?([a-zA-Z0-9_.\-]+@)?)([a-zA-Z0-9_.\-]+)([:/]{1,2})([a-zA-Z0-9_.\-\/]+)'
    match = re.search(pattern, giturl)
    return match.groups()[-1]

def divine(giturl, revision):
    r2c = {}
    c2r = {}
    result = run('git ls-remote %(giturl)s' % locals(), stdout=PIPE)[1].strip()
    for line in result.split('\n'):
        commit, refname = line.split('\t')
        r2c[refname] = commit
        c2r[commit] = refname

    refnames = [
        'refs/heads/' + revision,
        'refs/tags/' + revision,
        revision
    ]

    commit = None
    for refname in refnames:
        commit = r2c.get(refname, None)
        if commit:
            break

    if not commit:
        commit = revision

    return c2r.get(commit, None), commit

def clone(giturl, reponame, refname, commit, gimport_cache, repo_cache):
    path = os.path.join(gimport_cache, reponame)
    with cd(path, mkdir=True):
        if not os.path.isdir(commit):
            run('git clone %(giturl)s %(commit)s' % locals(), stdout=PIPE, stderr=PIPE)
        with cd(commit):
            run('git clean -x -f -d', stdout=PIPE, stderr=PIPE)
            run('git checkout %(commit)s' % locals(), stdout=PIPE, stderr=PIPE)
    return os.path.join(path, commit)

def gimport(giturl, revision, filepath, imports=None, gimport_cache='.gimport', repo_cache=None):
    reponame = decompose(giturl)
    refname, commit = divine(giturl, revision)
    path = clone(giturl, reponame, refname, commit, gimport_cache, repo_cache)
    with cd(path):
        modname = os.path.splitext(os.path.basename(filepath))[0]
        module = imp.load_source(modname, filepath)

        if imports:
            return [ module[import_] for import_ in imports ]
        return module

def main(args):

    parser = argparse.ArgumentParser()
    parser.add_argument(
        '--gimport-cache',
        metavar='GIMPORT-CACHE',
        default='.gimport',
        help='root path to store all gimport cached files')
    parser.add_argument(
        '--repo-cache',
        metavar='REPO-CACHE',
        help='path to cached repos to support fast cloning')
    parser.add_argument(
        '--imports',
        nargs='+',
        help='list of imports')
    parser.add_argument(
        'giturl',
        help='the giturl to be used with git clone')
    parser.add_argument(
        'revision',
        help='revision to checkout')
    parser.add_argument(
        'filepath',
        help='the filepath inside the git repo')
    ns = parser.parse_args()
    print gimport(**ns.__dict__)

    return 0

if __name__ == '__main__':
    sys.exit(main(sys.argv[1:]) )
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
##############################################################################
#
#    OpenERP, Open Source Management Solution
#    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

from datetime import datetime
from tools.translate import _
from osv import fields, osv
from openerp.addons.resource.faces import task as Task

class project_phase(osv.osv):
    _name = "project.phase"
    _description = "Project Phase"

    def _check_recursion(self, cr, uid, ids, context=None):
         if context is None:
            context = {}

         data_phase = self.browse(cr, uid, ids[0], context=context)
         prev_ids = data_phase.previous_phase_ids
         next_ids = data_phase.next_phase_ids
         # it should neither be in prev_ids nor in next_ids
         if (data_phase in prev_ids) or (data_phase in next_ids):
             return False
         ids = [id for id in prev_ids if id in next_ids]
         # both prev_ids and next_ids must be unique
         if ids:
             return False
         # unrelated project
         prev_ids = [rec.id for rec in prev_ids]
         next_ids = [rec.id for rec in next_ids]
         # iter prev_ids
         while prev_ids:
             cr.execute('SELECT distinct prv_phase_id FROM project_phase_rel WHERE next_phase_id IN %s', (tuple(prev_ids),))
             prv_phase_ids = filter(None, map(lambda x: x[0], cr.fetchall()))
             if data_phase.id in prv_phase_ids:
                 return False
             ids = [id for id in prv_phase_ids if id in next_ids]
             if ids:
                 return False
             prev_ids = prv_phase_ids
         # iter next_ids
         while next_ids:
             cr.execute('SELECT distinct next_phase_id FROM project_phase_rel WHERE prv_phase_id IN %s', (tuple(next_ids),))
             next_phase_ids = filter(None, map(lambda x: x[0], cr.fetchall()))
             if data_phase.id in next_phase_ids:
                 return False
             ids = [id for id in next_phase_ids if id in prev_ids]
             if ids:
                 return False
             next_ids = next_phase_ids
         return True

    def _check_dates(self, cr, uid, ids, context=None):
         for phase in self.read(cr, uid, ids, ['date_start', 'date_end'], context=context):
             if phase['date_start'] and phase['date_end'] and phase['date_start'] > phase['date_end']:
                 return False
         return True

    def _compute_progress(self, cr, uid, ids, field_name, arg, context=None):
        res = {}
        if not ids:
            return res
        for phase in self.browse(cr, uid, ids, context=context):
            if phase.state=='done':
                res[phase.id] = 100.0
                continue
            elif phase.state=="cancelled":
                res[phase.id] = 0.0
                continue
            elif not phase.task_ids:
                res[phase.id] = 0.0
                continue

            tot = done = 0.0
            for task in phase.task_ids:
                tot += task.total_hours
                done += min(task.effective_hours, task.total_hours)

            if not tot:
                res[phase.id] = 0.0
            else:
                res[phase.id] = round(100.0 * done / tot, 2)
        return res

    _columns = {
        'name': fields.char("Name", size=64, required=True),
        'date_start': fields.datetime('Start Date', select=True, help="It's computed by the scheduler according the project date or the end date of the previous phase.", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
        'date_end': fields.datetime('End Date', help=" It's computed by the scheduler according to the start date and the duration.", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
        'constraint_date_start': fields.datetime('Minimum Start Date', help='force the phase to start after this date', states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
        'constraint_date_end': fields.datetime('Deadline', help='force the phase to finish before this date', states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
        'project_id': fields.many2one('project.project', 'Project', required=True, select=True),
        'next_phase_ids': fields.many2many('project.phase', 'project_phase_rel', 'prv_phase_id', 'next_phase_id', 'Next Phases', states={'cancelled':[('readonly',True)]}),
        'previous_phase_ids': fields.many2many('project.phase', 'project_phase_rel', 'next_phase_id', 'prv_phase_id', 'Previous Phases', states={'cancelled':[('readonly',True)]}),
        'sequence': fields.integer('Sequence', select=True, help="Gives the sequence order when displaying a list of phases."),
        'duration': fields.float('Duration', required=True, help="By default in days", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
        'product_uom': fields.many2one('product.uom', 'Duration Unit of Measure', required=True, help="Unit of Measure (Unit of Measure) is the unit of measurement for Duration", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
        'task_ids': fields.one2many('project.task', 'phase_id', "Project Tasks", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
        'user_force_ids': fields.many2many('res.users', string='Force Assigned Users'),
        'user_ids': fields.one2many('project.user.allocation', 'phase_id', "Assigned Users",states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]},
            help="The ressources on the project can be computed automatically by the scheduler"),
        'state': fields.selection([('draft', 'New'), ('cancelled', 'Cancelled'),('open', 'In Progress'), ('pending', 'Pending'), ('done', 'Done')], 'Status', readonly=True, required=True,
                                  help='If the phase is created the state \'Draft\'.\n If the phase is started, the state becomes \'In Progress\'.\n If review is needed the phase is in \'Pending\' state.\
                                  \n If the phase is over, the states is set to \'Done\'.'),
        'progress': fields.function(_compute_progress, string='Progress', help="Computed based on related tasks"),
     }
    _defaults = {
        'state': 'draft',
        'sequence': 10,
        'product_uom': lambda self,cr,uid,c: self.pool.get('product.uom').search(cr, uid, [('name', '=', _('Day'))], context=c)[0]
    }
    _order = "project_id, date_start, sequence"
    _constraints = [
        (_check_recursion,'Loops in phases not allowed',['next_phase_ids', 'previous_phase_ids']),
        (_check_dates, 'Phase start-date must be lower than phase end-date.', ['date_start', 'date_end']),
    ]

    def onchange_project(self, cr, uid, ids, project, context=None):
        return {}

    def copy(self, cr, uid, id, default=None, context=None):
        if default is None:
            default = {}
        if not default.get('name', False):
            default['name'] = self.browse(cr, uid, id, context=context).name + _(' (copy)')
        return super(project_phase, self).copy(cr, uid, id, default, context)

    def set_draft(self, cr, uid, ids, *args):
        self.write(cr, uid, ids, {'state': 'draft'})
        return True

    def set_open(self, cr, uid, ids, *args):
        self.write(cr, uid, ids, {'state': 'open'})
        return True

    def set_pending(self, cr, uid, ids, *args):
        self.write(cr, uid, ids, {'state': 'pending'})
        return True

    def set_cancel(self, cr, uid, ids, *args):
        self.write(cr, uid, ids, {'state': 'cancelled'})
        return True

    def set_done(self, cr, uid, ids, *args):
        self.write(cr, uid, ids, {'state': 'done'})
        return True

    def generate_phase(self, cr, uid, phases, context=None):
        context = context or {}
        result = ""

        task_pool = self.pool.get('project.task')
        for phase in phases:
            if phase.state in ('done','cancelled'):
                continue
            duration_uom = {
                'days': 'd', 'day': 'd', 'd':'d',
                'months': 'm', 'month':'month', 'm':'m',
                'weeks': 'w', 'week': 'w', 'w':'w',
                'hours': 'H', 'hour': 'H', 'h':'H',
            }.get(phase.product_uom.name.lower(), "H")
            duration = str(phase.duration) + duration_uom
            result += '''
    def Phase_%s():
        effort = \"%s\"''' % (phase.id, duration)
            start = []
            if phase.constraint_date_start:
                start.append('datetime.datetime.strptime("'+str(phase.constraint_date_start)+'", "%Y-%m-%d %H:%M:%S")')
            for previous_phase in phase.previous_phase_ids:
                start.append("up.Phase_%s.end" % (previous_phase.id,))
            if start:
                result += '''
        start = max(%s)
''' % (','.join(start))

            if phase.user_force_ids:
                result += '''
        resource = %s
''' % '|'.join(map(lambda x: 'User_'+str(x.id), phase.user_force_ids))

            result += task_pool._generate_task(cr, uid, phase.task_ids, ident=8, context=context)
            result += "\n"

        return result
project_phase()

class project_user_allocation(osv.osv):
    _name = 'project.user.allocation'
    _description = 'Phase User Allocation'
    _rec_name = 'user_id'
    _columns = {
        'user_id': fields.many2one('res.users', 'User', required=True),
        'phase_id': fields.many2one('project.phase', 'Project Phase', ondelete='cascade', required=True),
        'project_id': fields.related('phase_id', 'project_id', type='many2one', relation="project.project", string='Project', store=True),
        'date_start': fields.datetime('Start Date', help="Starting Date"),
        'date_end': fields.datetime('End Date', help="Ending Date"),
    }
project_user_allocation()

class project(osv.osv):
    _inherit = "project.project"

    def _phase_count(self, cr, uid, ids, field_name, arg, context=None):
        res = dict.fromkeys(ids, 0)
        phase_ids = self.pool.get('project.phase').search(cr, uid, [('project_id', 'in', ids)])
        for phase in self.pool.get('project.phase').browse(cr, uid, phase_ids, context):
            res[phase.project_id.id] += 1
        return res

    _columns = {
        'phase_ids': fields.one2many('project.phase', 'project_id', "Project Phases"),
        'phase_count': fields.function(_phase_count, type='integer', string="Open Phases"),
    }

    def schedule_phases(self, cr, uid, ids, context=None):
        context = context or {}
        if type(ids) in (long, int,):
            ids = [ids]
        projects = self.browse(cr, uid, ids, context=context)
        result = self._schedule_header(cr, uid, ids, context=context)
        for project in projects:
            result += self._schedule_project(cr, uid, project, context=context)
            result += self.pool.get('project.phase').generate_phase(cr, uid, project.phase_ids, context=context)

        local_dict = {}
        exec result in local_dict
        projects_gantt = Task.BalancedProject(local_dict['Project'])

        for project in projects:
            project_gantt = getattr(projects_gantt, 'Project_%d' % (project.id,))
            for phase in project.phase_ids:
                if phase.state in ('done','cancelled'):
                    continue
                # Maybe it's better to update than unlink/create if it already exists ?
                p = getattr(project_gantt, 'Phase_%d' % (phase.id,))

                self.pool.get('project.user.allocation').unlink(cr, uid,
                    [x.id for x in phase.user_ids],
                    context=context
                )

                for r in p.booked_resource:
                    self.pool.get('project.user.allocation').create(cr, uid, {
                        'user_id': int(r.name[5:]),
                        'phase_id': phase.id,
                        'date_start': p.start.strftime('%Y-%m-%d %H:%M:%S'),
                        'date_end': p.end.strftime('%Y-%m-%d %H:%M:%S')
                    }, context=context)
                self.pool.get('project.phase').write(cr, uid, [phase.id], {
                    'date_start': p.start.strftime('%Y-%m-%d %H:%M:%S'),
                    'date_end': p.end.strftime('%Y-%m-%d %H:%M:%S')
                }, context=context)
        return True
project()

class account_analytic_account(osv.osv):
    _inherit = 'account.analytic.account'
    _description = 'Analytic Account'
    _columns = {
        'use_phases': fields.boolean('Phases Planing', help="Check this field if project manages phases"),
    }

    def _trigger_project_creation(self, cr, uid, vals, context=None):
        res= super(account_analytic_account, self)._trigger_project_creation(cr, uid, vals, context=context)
        return res or vals.get('use_phases')

account_analytic_account()

class project_task(osv.osv):
    _inherit = "project.task"
    _columns = {
        'phase_id': fields.many2one('project.phase', 'Project Phase'),
    }
project_task()

# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
<commit_msg>[IMP] project_long_term : Override the 'on_change_template' method.<commit_after># -*- coding: utf-8 -*-
##############################################################################
#
#    OpenERP, Open Source Management Solution
#    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

from datetime import datetime
from tools.translate import _
from osv import fields, osv
from openerp.addons.resource.faces import task as Task

class project_phase(osv.osv):
    _name = "project.phase"
    _description = "Project Phase"

    def _check_recursion(self, cr, uid, ids, context=None):
         if context is None:
            context = {}

         data_phase = self.browse(cr, uid, ids[0], context=context)
         prev_ids = data_phase.previous_phase_ids
         next_ids = data_phase.next_phase_ids
         # it should neither be in prev_ids nor in next_ids
         if (data_phase in prev_ids) or (data_phase in next_ids):
             return False
         ids = [id for id in prev_ids if id in next_ids]
         # both prev_ids and next_ids must be unique
         if ids:
             return False
         # unrelated project
         prev_ids = [rec.id for rec in prev_ids]
         next_ids = [rec.id for rec in next_ids]
         # iter prev_ids
         while prev_ids:
             cr.execute('SELECT distinct prv_phase_id FROM project_phase_rel WHERE next_phase_id IN %s', (tuple(prev_ids),))
             prv_phase_ids = filter(None, map(lambda x: x[0], cr.fetchall()))
             if data_phase.id in prv_phase_ids:
                 return False
             ids = [id for id in prv_phase_ids if id in next_ids]
             if ids:
                 return False
             prev_ids = prv_phase_ids
         # iter next_ids
         while next_ids:
             cr.execute('SELECT distinct next_phase_id FROM project_phase_rel WHERE prv_phase_id IN %s', (tuple(next_ids),))
             next_phase_ids = filter(None, map(lambda x: x[0], cr.fetchall()))
             if data_phase.id in next_phase_ids:
                 return False
             ids = [id for id in next_phase_ids if id in prev_ids]
             if ids:
                 return False
             next_ids = next_phase_ids
         return True

    def _check_dates(self, cr, uid, ids, context=None):
         for phase in self.read(cr, uid, ids, ['date_start', 'date_end'], context=context):
             if phase['date_start'] and phase['date_end'] and phase['date_start'] > phase['date_end']:
                 return False
         return True

    def _compute_progress(self, cr, uid, ids, field_name, arg, context=None):
        res = {}
        if not ids:
            return res
        for phase in self.browse(cr, uid, ids, context=context):
            if phase.state=='done':
                res[phase.id] = 100.0
                continue
            elif phase.state=="cancelled":
                res[phase.id] = 0.0
                continue
            elif not phase.task_ids:
                res[phase.id] = 0.0
                continue

            tot = done = 0.0
            for task in phase.task_ids:
                tot += task.total_hours
                done += min(task.effective_hours, task.total_hours)

            if not tot:
                res[phase.id] = 0.0
            else:
                res[phase.id] = round(100.0 * done / tot, 2)
        return res

    _columns = {
        'name': fields.char("Name", size=64, required=True),
        'date_start': fields.datetime('Start Date', select=True, help="It's computed by the scheduler according the project date or the end date of the previous phase.", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
        'date_end': fields.datetime('End Date', help=" It's computed by the scheduler according to the start date and the duration.", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
        'constraint_date_start': fields.datetime('Minimum Start Date', help='force the phase to start after this date', states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
        'constraint_date_end': fields.datetime('Deadline', help='force the phase to finish before this date', states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
        'project_id': fields.many2one('project.project', 'Project', required=True, select=True),
        'next_phase_ids': fields.many2many('project.phase', 'project_phase_rel', 'prv_phase_id', 'next_phase_id', 'Next Phases', states={'cancelled':[('readonly',True)]}),
        'previous_phase_ids': fields.many2many('project.phase', 'project_phase_rel', 'next_phase_id', 'prv_phase_id', 'Previous Phases', states={'cancelled':[('readonly',True)]}),
        'sequence': fields.integer('Sequence', select=True, help="Gives the sequence order when displaying a list of phases."),
        'duration': fields.float('Duration', required=True, help="By default in days", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
        'product_uom': fields.many2one('product.uom', 'Duration Unit of Measure', required=True, help="Unit of Measure (Unit of Measure) is the unit of measurement for Duration", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
        'task_ids': fields.one2many('project.task', 'phase_id', "Project Tasks", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}),
        'user_force_ids': fields.many2many('res.users', string='Force Assigned Users'),
        'user_ids': fields.one2many('project.user.allocation', 'phase_id', "Assigned Users",states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]},
            help="The ressources on the project can be computed automatically by the scheduler"),
        'state': fields.selection([('draft', 'New'), ('cancelled', 'Cancelled'),('open', 'In Progress'), ('pending', 'Pending'), ('done', 'Done')], 'Status', readonly=True, required=True,
                                  help='If the phase is created the state \'Draft\'.\n If the phase is started, the state becomes \'In Progress\'.\n If review is needed the phase is in \'Pending\' state.\
                                  \n If the phase is over, the states is set to \'Done\'.'),
        'progress': fields.function(_compute_progress, string='Progress', help="Computed based on related tasks"),
     }
    _defaults = {
        'state': 'draft',
        'sequence': 10,
        'product_uom': lambda self,cr,uid,c: self.pool.get('product.uom').search(cr, uid, [('name', '=', _('Day'))], context=c)[0]
    }
    _order = "project_id, date_start, sequence"
    _constraints = [
        (_check_recursion,'Loops in phases not allowed',['next_phase_ids', 'previous_phase_ids']),
        (_check_dates, 'Phase start-date must be lower than phase end-date.', ['date_start', 'date_end']),
    ]

    def onchange_project(self, cr, uid, ids, project, context=None):
        return {}

    def copy(self, cr, uid, id, default=None, context=None):
        if default is None:
            default = {}
        if not default.get('name', False):
            default['name'] = self.browse(cr, uid, id, context=context).name + _(' (copy)')
        return super(project_phase, self).copy(cr, uid, id, default, context)

    def set_draft(self, cr, uid, ids, *args):
        self.write(cr, uid, ids, {'state': 'draft'})
        return True

    def set_open(self, cr, uid, ids, *args):
        self.write(cr, uid, ids, {'state': 'open'})
        return True

    def set_pending(self, cr, uid, ids, *args):
        self.write(cr, uid, ids, {'state': 'pending'})
        return True

    def set_cancel(self, cr, uid, ids, *args):
        self.write(cr, uid, ids, {'state': 'cancelled'})
        return True

    def set_done(self, cr, uid, ids, *args):
        self.write(cr, uid, ids, {'state': 'done'})
        return True

    def generate_phase(self, cr, uid, phases, context=None):
        context = context or {}
        result = ""

        task_pool = self.pool.get('project.task')
        for phase in phases:
            if phase.state in ('done','cancelled'):
                continue
            duration_uom = {
                'days': 'd', 'day': 'd', 'd':'d',
                'months': 'm', 'month':'month', 'm':'m',
                'weeks': 'w', 'week': 'w', 'w':'w',
                'hours': 'H', 'hour': 'H', 'h':'H',
            }.get(phase.product_uom.name.lower(), "H")
            duration = str(phase.duration) + duration_uom
            result += '''
    def Phase_%s():
        effort = \"%s\"''' % (phase.id, duration)
            start = []
            if phase.constraint_date_start:
                start.append('datetime.datetime.strptime("'+str(phase.constraint_date_start)+'", "%Y-%m-%d %H:%M:%S")')
            for previous_phase in phase.previous_phase_ids:
                start.append("up.Phase_%s.end" % (previous_phase.id,))
            if start:
                result += '''
        start = max(%s)
''' % (','.join(start))

            if phase.user_force_ids:
                result += '''
        resource = %s
''' % '|'.join(map(lambda x: 'User_'+str(x.id), phase.user_force_ids))

            result += task_pool._generate_task(cr, uid, phase.task_ids, ident=8, context=context)
            result += "\n"

        return result
project_phase()

class project_user_allocation(osv.osv):
    _name = 'project.user.allocation'
    _description = 'Phase User Allocation'
    _rec_name = 'user_id'
    _columns = {
        'user_id': fields.many2one('res.users', 'User', required=True),
        'phase_id': fields.many2one('project.phase', 'Project Phase', ondelete='cascade', required=True),
        'project_id': fields.related('phase_id', 'project_id', type='many2one', relation="project.project", string='Project', store=True),
        'date_start': fields.datetime('Start Date', help="Starting Date"),
        'date_end': fields.datetime('End Date', help="Ending Date"),
    }
project_user_allocation()

class project(osv.osv):
    _inherit = "project.project"

    def _phase_count(self, cr, uid, ids, field_name, arg, context=None):
        res = dict.fromkeys(ids, 0)
        phase_ids = self.pool.get('project.phase').search(cr, uid, [('project_id', 'in', ids)])
        for phase in self.pool.get('project.phase').browse(cr, uid, phase_ids, context):
            res[phase.project_id.id] += 1
        return res

    _columns = {
        'phase_ids': fields.one2many('project.phase', 'project_id', "Project Phases"),
        'phase_count': fields.function(_phase_count, type='integer', string="Open Phases"),
    }

    def schedule_phases(self, cr, uid, ids, context=None):
        context = context or {}
        if type(ids) in (long, int,):
            ids = [ids]
        projects = self.browse(cr, uid, ids, context=context)
        result = self._schedule_header(cr, uid, ids, context=context)
        for project in projects:
            result += self._schedule_project(cr, uid, project, context=context)
            result += self.pool.get('project.phase').generate_phase(cr, uid, project.phase_ids, context=context)

        local_dict = {}
        exec result in local_dict
        projects_gantt = Task.BalancedProject(local_dict['Project'])

        for project in projects:
            project_gantt = getattr(projects_gantt, 'Project_%d' % (project.id,))
            for phase in project.phase_ids:
                if phase.state in ('done','cancelled'):
                    continue
                # Maybe it's better to update than unlink/create if it already exists ?
                p = getattr(project_gantt, 'Phase_%d' % (phase.id,))

                self.pool.get('project.user.allocation').unlink(cr, uid,
                    [x.id for x in phase.user_ids],
                    context=context
                )

                for r in p.booked_resource:
                    self.pool.get('project.user.allocation').create(cr, uid, {
                        'user_id': int(r.name[5:]),
                        'phase_id': phase.id,
                        'date_start': p.start.strftime('%Y-%m-%d %H:%M:%S'),
                        'date_end': p.end.strftime('%Y-%m-%d %H:%M:%S')
                    }, context=context)
                self.pool.get('project.phase').write(cr, uid, [phase.id], {
                    'date_start': p.start.strftime('%Y-%m-%d %H:%M:%S'),
                    'date_end': p.end.strftime('%Y-%m-%d %H:%M:%S')
                }, context=context)
        return True
project()

class account_analytic_account(osv.osv):
    _inherit = 'account.analytic.account'
    _description = 'Analytic Account'
    _columns = {
        'use_phases': fields.boolean('Phases Planing', help="Check this field if project manages phases"),
    }

    def on_change_template(self, cr, uid, ids, template_id, context=None):
        res = super(account_analytic_account, self).on_change_template(cr, uid, ids, template_id, context=context)
        if template_id and 'value' in res:
            template = self.browse(cr, uid, template_id, context=context)
            res['value']['use_phases'] = template.use_phases
        return res

    def _trigger_project_creation(self, cr, uid, vals, context=None):
        res= super(account_analytic_account, self)._trigger_project_creation(cr, uid, vals, context=context)
        return res or vals.get('use_phases')

account_analytic_account()

class project_task(osv.osv):
    _inherit = "project.task"
    _columns = {
        'phase_id': fields.many2one('project.phase', 'Project Phase'),
    }
project_task()

# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
<|endoftext|>


<commit_before>def execute():
	import webnotes

	webnotes.reload_doc("production", "doctype", "production_order")
	webnotes.reload_doc("stock", "doctype", "stock_entry")

	webnotes.conn.sql("""update `tabProduction Order`
		set use_multi_level_bom = if(consider_sa_items='Yes', 0, 1)""")

	webnotes.conn.sql("""update `tabStock Entry`
		set use_multi_level_bom = if(consider_sa_items_as_raw_materials='Yes', 0, 1)""")
	<commit_msg>changed production to manufacturing in reload doc<commit_after>def execute():
	import webnotes

	webnotes.reload_doc("manufacturing", "doctype", "production_order")
	webnotes.reload_doc("stock", "doctype", "stock_entry")

	webnotes.conn.sql("""update `tabProduction Order`
		set use_multi_level_bom = if(consider_sa_items='Yes', 0, 1)""")

	webnotes.conn.sql("""update `tabStock Entry`
		set use_multi_level_bom = if(consider_sa_items_as_raw_materials='Yes', 0, 1)""")
	<|endoftext|>


<commit_before>#!/usr/bin/env python3
import subprocess
import os
from PyPDF2 import PdfFileReader, PdfFileWriter

def is_page_colour(pdf_filename, page_number, dpi=10):
    p = subprocess.Popen([
        "/usr/bin/pdftoppm",
        "-r", str(dpi),
        "-f", str(page_number),
        "-l", str(page_number),
        pdf_filename
    ], stdout=subprocess.PIPE)

    ppm, stderr = p.communicate()

    ppm = ppm.split(b"\n")
    magic_number = ppm[0].decode("UTF-8").split()[0]
    # TODO: Enforce that magic number is P6
    # width, height = ppm[1].decode("UTF-8").split()
    maxval = int(ppm[2].decode("UTF-8").split()[0])
    pixels = b''.join(ppm[3:])

    num_bytes = 1 if maxval < 256 else 2
    slice_size = num_bytes*3

    for i in range(0, len(pixels), slice_size):
        slice_pixels = pixels[i:i+slice_size]
        if num_bytes == 1:
            colour_values = slice_pixels
        else:
            colour_values = [slice_pixels[x:x+num_bytes] for x in range(0,
                slice_size, num_bytes)]

        r,g,b = colour_values
        if not(r==g and g==b):
            return True # We encountered a colour pixel so page is colour

    return False # No colour pixels were encoutered

def get_page_count(pdf_filename):
    lines = subprocess.check_output(["/usr/bin/pdfinfo", pdf_filename]).decode(
        "UTF-8").split("\n")
    for line in lines:
        if line.startswith("Pages:"):
            return int(line.split()[1].strip())

def detect_pages(pdf_filename, num_pages):
    colour_pages = []
    mono_pages = []

    for i in range(1, num_pages+1):
        if is_page_colour(pdf_filename, i):
            colour_pages.append(i)
        else:
            mono_pages.append(i)

    return (colour_pages, mono_pages)

def get_file_structure(num_pages, colour_pages, mono_pages, duplex, stackable):
    if duplex:
        duplex_colour_pages = []
        duplex_mono_pages = []
        # If either side of a page is colour, put both sides into the colour
        # list
        for i in range(1, num_pages+1, 2):
            if i in colour_pages or i+1 in colour_pages:
                duplex_colour_pages += [i, i+1]
            else:
                duplex_mono_pages += [i, i+1]
    else:
        duplex_colour_pages = colour_pages
        duplex_mono_pages = mono_pages

    colour_files = []
    mono_files = []

    if stackable:
        temp_pages = []
        previous_classification = None
        for i in range(1, num_pages+1):
            classification = "colour" if i in duplex_colour_pages else "mono"
            if classification != previous_classification and \
                previous_classification != None:
                if previous_classification == "colour":
                    colour_files.append(temp_pages)
                else:
                    mono_files.append(temp_pages)
                temp_pages = []
            previous_classification = classification
            temp_pages.append(i)
        if previous_classification == "colour":
            colour_files.append(temp_pages)
        else:
            mono_files.append(temp_pages)
    else:
        colour_files = duplex_colour_pages
        mono_files = duplex_mono_pages

    return (colour_files, mono_files)

def write_output_files(pdf_filename, colour_files, mono_files):
    classification = "mono"
    if len(colour_files) and 1 in colour_files[0]:
        classification = "colour"

    num_output_files = len(colour_files) + len(mono_files)
    number_pad_amount = len(str(num_output_files)) # How much to zfill filename

    base_filename = os.path.basename(pdf_filename)
    if base_filename.lower().endswith(".pdf"):
        base_filename = base_filename[:-4]

    for i in range(1, num_output_files+1):
        if classification == "colour":
            pages = colour_files.pop(0)
        else:
            pages = mono_files.pop(0)

        output_filename = "{}_{}_{}.pdf".format(base_filename, classification,
            str(i).zfill(number_pad_amount))

        subprocess.call("/usr/bin/pdftk {} cat {} output {}".format(
            pdf_filename, " ".join(str(p) for p in pages), output_filename),
            shell=True)

        classification = "mono" if classification == "colour" else "colour"

def main():
    pdf_filename = "/tmp/pdf/final_report.pdf"
    num_pages = get_page_count(pdf_filename)

    colour, mono = detect_pages(pdf_filename, num_pages)
    colour_f, mono_f = get_file_structure(num_pages, colour, mono, True, True)
    write_output_files(pdf_filename, colour_f, mono_f)

if __name__ == "__main__":
    main()
<commit_msg>Added arguments and stuff, now usable from CLI<commit_after>#!/usr/bin/env python3
import subprocess
import os
import argparse
from PyPDF2 import PdfFileReader, PdfFileWriter

def is_page_colour(pdf_filename, page_number, dpi=10):
    p = subprocess.Popen([
        "/usr/bin/pdftoppm",
        "-r", str(dpi),
        "-f", str(page_number),
        "-l", str(page_number),
        pdf_filename
    ], stdout=subprocess.PIPE)

    ppm, stderr = p.communicate()

    ppm = ppm.split(b"\n")
    magic_number = ppm[0].decode("UTF-8").split()[0]
    # TODO: Enforce that magic number is P6
    # width, height = ppm[1].decode("UTF-8").split()
    maxval = int(ppm[2].decode("UTF-8").split()[0])
    pixels = b''.join(ppm[3:])

    num_bytes = 1 if maxval < 256 else 2
    slice_size = num_bytes*3

    for i in range(0, len(pixels), slice_size):
        slice_pixels = pixels[i:i+slice_size]
        if num_bytes == 1:
            colour_values = slice_pixels
        else:
            colour_values = [slice_pixels[x:x+num_bytes] for x in range(0,
                slice_size, num_bytes)]

        r,g,b = colour_values
        if not(r==g and g==b):
            return True # We encountered a colour pixel so page is colour

    return False # No colour pixels were encoutered

def get_page_count(pdf_filename):
    lines = subprocess.check_output(["/usr/bin/pdfinfo", pdf_filename]).decode(
        "UTF-8").split("\n")
    for line in lines:
        if line.startswith("Pages:"):
            return int(line.split()[1].strip())

def detect_pages(pdf_filename, num_pages):
    colour_pages = []
    mono_pages = []

    for i in range(1, num_pages+1):
        if is_page_colour(pdf_filename, i):
            colour_pages.append(i)
        else:
            mono_pages.append(i)

    return (colour_pages, mono_pages)

def get_file_structure(num_pages, colour_pages, mono_pages, duplex, stackable):
    if duplex:
        duplex_colour_pages = []
        duplex_mono_pages = []
        # If either side of a page is colour, put both sides into the colour
        # list
        for i in range(1, num_pages+1, 2):
            if i in colour_pages or i+1 in colour_pages:
                duplex_colour_pages += [i, i+1]
            else:
                duplex_mono_pages += [i, i+1]
    else:
        duplex_colour_pages = colour_pages
        duplex_mono_pages = mono_pages

    colour_files = []
    mono_files = []

    if stackable:
        temp_pages = []
        previous_classification = None
        for i in range(1, num_pages+1):
            classification = "colour" if i in duplex_colour_pages else "mono"
            if classification != previous_classification and \
                previous_classification != None:
                if previous_classification == "colour":
                    colour_files.append(temp_pages)
                else:
                    mono_files.append(temp_pages)
                temp_pages = []
            previous_classification = classification
            temp_pages.append(i)
        if previous_classification == "colour":
            colour_files.append(temp_pages)
        else:
            mono_files.append(temp_pages)
    else:
        colour_files = [duplex_colour_pages]
        mono_files = [duplex_mono_pages]

    return (colour_files, mono_files)

def write_output_files(pdf_filename, colour_files, mono_files):
    classification = "mono"
    if len(colour_files) and 1 in colour_files[0]:
        classification = "colour"

    num_output_files = len(colour_files) + len(mono_files)
    number_pad_amount = len(str(num_output_files)) # How much to zfill filename

    base_filename = os.path.basename(pdf_filename)
    if base_filename.lower().endswith(".pdf"):
        base_filename = base_filename[:-4]

    for i in range(1, num_output_files+1):
        if classification == "colour":
            pages = colour_files.pop(0)
        else:
            pages = mono_files.pop(0)

        output_filename = "{}_{}_{}.pdf".format(base_filename, classification,
            str(i).zfill(number_pad_amount))

        subprocess.call("/usr/bin/pdftk {} cat {} output {}".format(
            pdf_filename, " ".join(str(p) for p in pages), output_filename),
            shell=True)

        classification = "mono" if classification == "colour" else "colour"

def split_pdf(pdf_filename, duplex, stackable):
    num_pages = get_page_count(pdf_filename)

    colour, mono = detect_pages(pdf_filename, num_pages)
    c_file, m_file = get_file_structure(num_pages, colour, mono, duplex,
        stackable)
    write_output_files(pdf_filename, c_file, m_file)

def main():
    parser = argparse.ArgumentParser(description="Splits a PDF into colour and "
        "monochrome pages")
    parser.add_argument("--noduplex", action="store_false", dest="duplex",
        help="Do not arrange pages to handle duplex (double sided) printing")
    parser.add_argument("--nostack", action="store_false", dest="stackable",
        help="Do not split output into several PDF files to aid stacking of "
            "printed output")
    parser.add_argument("input_file", help="PDF file to split", default=None)
    args = parser.parse_args()

    split_pdf(args.input_file, args.duplex, args.stackable)

if __name__ == "__main__":
    main()
<|endoftext|>


<commit_before># cbfdump.py
#
# A little jiffy to read a cbf file and tell us what it finds...
#
# Based on pycbf from the CBFlib distribution.
#

import sys
import pycbf
import math
from scitbx import matrix
from scitbx.math import r3_rotation_axis_and_angle_from_matrix

def find_detector_id(cbf_handle):

    detector_id = ''

    cbf_handle.rewind_datablock()
    nblocks = cbf_handle.count_datablocks()

    for j in range(nblocks):
        cbf_handle.select_datablock(0)

    ncat = cbf_handle.count_categories()

    for j in range(ncat):
        cbf_handle.select_category(j)

        if not cbf_handle.category_name() == 'diffrn_detector':
            continue

        nrows = cbf_handle.count_rows()
        ncols = cbf_handle.count_columns()

        cbf_handle.rewind_column()
        cbf_handle.rewind_row()

        while True:
            if cbf_handle.column_name() == 'id':
                detector_id = cbf_handle.get_value()
                break
            try:
                cbf_handle.next_column()
            except:
                break

    return detector_id

def print_axes(cbf_handle):

    # help(cbf_handle)

    cbf_handle.rewind_datablock()
    nblocks = cbf_handle.count_datablocks()

    cbf_handle.find_category('axis')

    nrows = cbf_handle.count_rows()
    ncols = cbf_handle.count_columns()

    cbf_handle.rewind_column()

    axes = []
    types = { }

    for j in range(ncols):
        print cbf_handle.column_name(),
        try:
            cbf_handle.next_column()
        except:
            break

    print ''

    while True:
        cbf_handle.rewind_column()
        for j in range(ncols):

            try:
                print cbf_handle.get_value(),

                if cbf_handle.column_name() == 'id':
                    axes.append(cbf_handle.get_value())
                if cbf_handle.column_name() == 'type':
                    types[axes[-1]] = cbf_handle.get_value()

                cbf_handle.next_column()
            except:
                break
        print ''

        try:
            cbf_handle.next_row()
        except:
            break

    for axis in axes:
        if not types[axis] in ['rotation', 'translation']:
            continue
        try:
            setting = tuple(cbf_handle.get_axis_setting(axis))
            print '%s %f %f (%s)' % (axis, setting[0], setting[1], types[axis])
        except:
            print '%s not found' % axis

    return

def test_example_adsc_cif_header():
    '''A unit test to ensure that the reading of CIF headers using pycbf
    is working correcty.'''

    cbf_image = 'example-adsc-cif-header.cif'

    cbf_handle = pycbf.cbf_handle_struct()
    cbf_handle.read_file(cbf_image, pycbf.MSG_DIGEST)

    cbf_handle.rewind_datablock()

    # find the direct beam vector - takes a few steps
    cbf_handle.find_category('axis')

    # find record with equipment = source
    cbf_handle.find_column('equipment')
    cbf_handle.find_row('source')

    # then get the vector and offset from this

    beam_direction = []

    for j in range(3):
        cbf_handle.find_column('vector[%d]' % (j + 1))
        beam_direction.append(cbf_handle.get_doublevalue())

    detector = cbf_handle.construct_detector(0)

    beam = detector.get_beam_center()

    beam_pixel = tuple(beam[:2])
    beam_mm = tuple(beam[2:])
    detector_normal = tuple(detector.get_detector_normal())
    distance = detector.get_detector_distance()
    pixel = (detector.get_inferred_pixel_size(1),
             detector.get_inferred_pixel_size(2))

    gonio = cbf_handle.construct_goniometer()

    axis = tuple(gonio.get_rotation_axis())
    angles = tuple(gonio.get_rotation_range())

    date = cbf_handle.get_datestamp()

    time = cbf_handle.get_timestamp()
    size = tuple(cbf_handle.get_image_size(0))
    exposure = cbf_handle.get_integration_time()
    overload = cbf_handle.get_overload(0)
    wavelength = cbf_handle.get_wavelength()

    assert(size == (3072, 3072))
    assert(math.fabs(pixel[0] - 0.103) < 0.01)
    assert(math.fabs(pixel[1] - 0.103) < 0.01)
    assert(math.fabs(distance - 116) < 0.1)

    assert(math.fabs(detector_normal[0] - 0.0) < 0.01)
    assert(math.fabs(detector_normal[1] - 0.0) < 0.01)
    assert(math.fabs(detector_normal[2] - 1.0) < 0.01)

    assert(math.fabs(exposure - 0.5) < 0.01)
    assert(int(overload) == 65535)

    if do_print: print 'Beam:       %.2f %.2f' % beam_mm
    if do_print: print 'Beam:       %.2f %.2f %.2f' % tuple(beam_direction)

    if do_print: print 'Goniometer:'
    if do_print: print 'Axis:       %.2f %.2f %.2f' % axis
    if do_print: print 'Angles:     %.2f %.2f' % angles

    if do_print: print 'Experiment:'
    if do_print: print 'Wavelength: %.5f' % wavelength

    # now need to dig out the detector axes
    # perhaps bodge this by looking at the displacements of pixels in the
    # fast and slow directions?

    origin = detector.get_pixel_coordinates(0, 0)
    fast = detector.get_pixel_coordinates(0, 1)
    slow = detector.get_pixel_coordinates(1, 0)

    if do_print: print 'Origin:     %.2f %.2f %.2f' % tuple(origin)

    dfast = [fast[j] - origin[j] for j in range(3)]
    dslow = [slow[j] - origin[j] for j in range(3)]

    lfast = math.sqrt(sum([dfast[j] * dfast[j] for j in range(3)]))
    lslow = math.sqrt(sum([dslow[j] * dslow[j] for j in range(3)]))

    fast = tuple([dfast[j] / lfast for j in range(3)])
    slow = tuple([dslow[j] / lslow for j in range(3)])

    if do_print: print 'Fast direction: %.2f %.2f %.2f' % fast
    if do_print: print 'Slow direction: %.2f %.2f %.2f' % slow

    if hasattr(detector, 'get_detector_axis_fast'):

        if do_print: print 'CBF fast: %.2f %.2f %.2f' % \
           tuple(detector.get_detector_axis_fast())
        if do_print: print 'CBF slow: %.2f %.2f %.2f' % \
           tuple(detector.get_detector_axis_slow())

    detector.__swig_destroy__(detector)
    del(detector)

    gonio.__swig_destroy__(gonio)
    del(gonio)

    return

def determine_effective_scan_axis(gonio):
    x = gonio.rotate_vector(0.0, 1, 0, 0)
    y = gonio.rotate_vector(0.0, 0, 1, 0)
    z = gonio.rotate_vector(0.0, 0, 0, 1)

    R = matrix.rec(x + y + z, (3, 3)).transpose()

    x1 = gonio.rotate_vector(1.0, 1, 0, 0)
    y1 = gonio.rotate_vector(1.0, 0, 1, 0)
    z1 = gonio.rotate_vector(1.0, 0, 0, 1)

    R1 = matrix.rec(x1 + y1 + z1, (3, 3)).transpose()

    RA = R1 * R.inverse()

    rot = r3_rotation_axis_and_angle_from_matrix(RA)

    return rot.axis, rot.angle(deg = True)

def cbfdump(cbf_image, do_print = False):

    cbf_handle = pycbf.cbf_handle_struct()
    cbf_handle.read_file(cbf_image, pycbf.MSG_DIGEST)

    # print_axes(cbf_handle)

    detector_id = find_detector_id(cbf_handle)

    cbf_handle.rewind_datablock()

    # find the direct beam vector - takes a few steps
    cbf_handle.find_category('axis')

    # find record with equipment = source
    cbf_handle.find_column('equipment')
    cbf_handle.find_row('source')

    # then get the vector and offset from this

    beam_direction = []

    for j in range(3):
        cbf_handle.find_column('vector[%d]' % (j + 1))
        beam_direction.append(cbf_handle.get_doublevalue())

    detector = cbf_handle.construct_detector(0)

    # this returns slow fast slow fast pixels pixels mm mm

    beam = detector.get_beam_center()

    beam_pixel = tuple(reversed(beam[:2]))
    beam_mm = tuple(reversed(beam[2:]))
    detector_normal = tuple(detector.get_detector_normal())
    distance = detector.get_detector_distance()
    pixel = (detector.get_inferred_pixel_size(1),
             detector.get_inferred_pixel_size(2))

    gonio = cbf_handle.construct_goniometer()

    real_axis, real_angle = determine_effective_scan_axis(gonio)

    axis = tuple(gonio.get_rotation_axis())
    angles = tuple(gonio.get_rotation_range())

    date = cbf_handle.get_datestamp()

    time = cbf_handle.get_timestamp()

    # this method returns slow then fast dimensions i.e. (y, x)

    size = tuple(reversed(cbf_handle.get_image_size(0)))
    exposure = cbf_handle.get_integration_time()
    overload = cbf_handle.get_overload(0)
    wavelength = cbf_handle.get_wavelength()

    if do_print: print 'Detector information:'
    if do_print: print 'Dimensions: %d %d' % size
    if do_print: print 'Pixel size: %.3f %.3f' % pixel
    if do_print: print 'Distance:   %.1f' % distance
    if do_print: print 'Normal:     %.2f %.2f %.2f' % detector_normal
    if do_print: print 'Exposure:   %.2f' % exposure
    if do_print: print 'Overload:   %d' % int(overload)
    if do_print: print 'Beam:       %.2f %.2f' % beam_mm
    if do_print: print 'Beam:       %.2f %.2f %.2f' % tuple(beam_direction)

    if do_print: print 'Goniometer:'
    if do_print: print 'Axis:       %.2f %.2f %.2f' % axis
    if do_print: print 'Real axis:  %.2f %.2f %.2f' % real_axis
    if do_print: print 'Angles:     %.2f %.2f' % angles
    if do_print: print 'Real angle: %.2f' % real_angle

    if do_print: print 'Experiment:'
    if do_print: print 'Wavelength: %.5f' % wavelength

    # now need to dig out the detector axes
    # perhaps bodge this by looking at the displacements of pixels in the
    # fast and slow directions?

    origin = detector.get_pixel_coordinates(0, 0)
    fast = detector.get_pixel_coordinates(0, 1)
    slow = detector.get_pixel_coordinates(1, 0)

    if do_print: print 'Origin:     %.2f %.2f %.2f' % tuple(origin)

    dfast = [fast[j] - origin[j] for j in range(3)]
    dslow = [slow[j] - origin[j] for j in range(3)]

    lfast = math.sqrt(sum([dfast[j] * dfast[j] for j in range(3)]))
    lslow = math.sqrt(sum([dslow[j] * dslow[j] for j in range(3)]))

    fast = tuple([dfast[j] / lfast for j in range(3)])
    slow = tuple([dslow[j] / lslow for j in range(3)])

    if do_print: print 'Fast direction: %.2f %.2f %.2f' % fast
    if do_print: print 'Slow direction: %.2f %.2f %.2f' % slow

    if hasattr(detector, 'get_detector_axis_fast'):

        if do_print: print 'CBF fast: %.2f %.2f %.2f' % \
           tuple(detector.get_detector_axis_fast())
        if do_print: print 'CBF slow: %.2f %.2f %.2f' % \
           tuple(detector.get_detector_axis_slow())

    # now also compute the position on the detector where the beam will
    # actually strike the detector - this will be the intersection of the
    # source vector with the plane defined by fast and slow passing through
    # the detector origin. Then return this position on the detector in mm.
    #
    # unit vectors -
    # _b - beam
    # _n - detector normal
    # _f, _s - fast and slow directions on the detector
    #
    # full vectors -
    # _O - displacement of origin
    # _D - displacement to intersection of beam with detector plane
    # _B - displacement from origin to said intersection

    _b = matrix.col(beam_direction) / math.sqrt(
        matrix.col(beam_direction).dot())
    _n = matrix.col(detector_normal) / math.sqrt(
        matrix.col(detector_normal).dot())
    _f = matrix.col(fast) / math.sqrt(matrix.col(fast).dot())
    _s = matrix.col(slow) / math.sqrt(matrix.col(slow).dot())

    _O = matrix.col(origin)
    _D = _b * (_O.dot(_n) / _b.dot(_n))

    _B = _D - _O

    x = _B.dot(_f)
    y = _B.dot(_s)

    if do_print: print 'Computed:   %.2f %.2f' % (x, y)
    if do_print: print 'Size:       %.2f %.2f' % (size[0] * pixel[0],
                                                  size[1] * pixel[1])
    detector.__swig_destroy__(detector)
    del(detector)

    gonio.__swig_destroy__(gonio)
    del(gonio)

    return

if __name__ == '__main__':
    import time

    start = time.time()

    j = 0
    for image in sys.argv[1:]:
         print image
         cbfdump(image, do_print = True)
         j += 1

    end = time.time()

    print 'Reading %d headers took %.1fs' % (j, end - start)

<commit_msg>Removed cruft now I have found that my problems were to do with a cbflib version<commit_after># cbfdump.py
#
# A little jiffy to read a cbf file and tell us what it finds...
#
# Based on pycbf from the CBFlib distribution.
#

import sys
import pycbf
import math
from scitbx import matrix
from scitbx.math import r3_rotation_axis_and_angle_from_matrix

def find_detector_id(cbf_handle):

    detector_id = ''

    cbf_handle.rewind_datablock()
    nblocks = cbf_handle.count_datablocks()

    for j in range(nblocks):
        cbf_handle.select_datablock(0)

    ncat = cbf_handle.count_categories()

    for j in range(ncat):
        cbf_handle.select_category(j)

        if not cbf_handle.category_name() == 'diffrn_detector':
            continue

        nrows = cbf_handle.count_rows()
        ncols = cbf_handle.count_columns()

        cbf_handle.rewind_column()
        cbf_handle.rewind_row()

        while True:
            if cbf_handle.column_name() == 'id':
                detector_id = cbf_handle.get_value()
                break
            try:
                cbf_handle.next_column()
            except:
                break

    return detector_id

def determine_effective_scan_axis(gonio):
    x = gonio.rotate_vector(0.0, 1, 0, 0)
    y = gonio.rotate_vector(0.0, 0, 1, 0)
    z = gonio.rotate_vector(0.0, 0, 0, 1)

    R = matrix.rec(x + y + z, (3, 3)).transpose()

    x1 = gonio.rotate_vector(1.0, 1, 0, 0)
    y1 = gonio.rotate_vector(1.0, 0, 1, 0)
    z1 = gonio.rotate_vector(1.0, 0, 0, 1)

    R1 = matrix.rec(x1 + y1 + z1, (3, 3)).transpose()

    RA = R1 * R.inverse()

    rot = r3_rotation_axis_and_angle_from_matrix(RA)

    return rot.axis, rot.angle(deg = True)

def cbfdump(cbf_image, do_print = False):

    cbf_handle = pycbf.cbf_handle_struct()
    cbf_handle.read_file(cbf_image, pycbf.MSG_DIGEST)

    detector_id = find_detector_id(cbf_handle)

    cbf_handle.rewind_datablock()

    # find the direct beam vector - takes a few steps
    cbf_handle.find_category('axis')

    # find record with equipment = source
    cbf_handle.find_column('equipment')
    cbf_handle.find_row('source')

    # then get the vector and offset from this

    beam_direction = []

    for j in range(3):
        cbf_handle.find_column('vector[%d]' % (j + 1))
        beam_direction.append(cbf_handle.get_doublevalue())

    detector = cbf_handle.construct_detector(0)

    # this returns slow fast slow fast pixels pixels mm mm

    beam = detector.get_beam_center()

    beam_pixel = tuple(reversed(beam[:2]))
    beam_mm = tuple(reversed(beam[2:]))
    detector_normal = tuple(detector.get_detector_normal())
    distance = detector.get_detector_distance()
    pixel = (detector.get_inferred_pixel_size(1),
             detector.get_inferred_pixel_size(2))

    gonio = cbf_handle.construct_goniometer()

    real_axis, real_angle = determine_effective_scan_axis(gonio)

    axis = tuple(gonio.get_rotation_axis())
    angles = tuple(gonio.get_rotation_range())

    date = cbf_handle.get_datestamp()

    time = cbf_handle.get_timestamp()

    # this method returns slow then fast dimensions i.e. (y, x)

    size = tuple(reversed(cbf_handle.get_image_size(0)))
    exposure = cbf_handle.get_integration_time()
    overload = cbf_handle.get_overload(0)
    wavelength = cbf_handle.get_wavelength()

    if do_print: print 'Detector information:'
    if do_print: print 'Dimensions: %d %d' % size
    if do_print: print 'Pixel size: %.3f %.3f' % pixel
    if do_print: print 'Distance:   %.1f' % distance
    if do_print: print 'Normal:     %.2f %.2f %.2f' % detector_normal
    if do_print: print 'Exposure:   %.2f' % exposure
    if do_print: print 'Overload:   %d' % int(overload)
    if do_print: print 'Beam:       %.2f %.2f' % beam_mm
    if do_print: print 'Beam:       %.2f %.2f %.2f' % tuple(beam_direction)

    if do_print: print 'Goniometer:'
    if do_print: print 'Axis:       %.2f %.2f %.2f' % axis
    if do_print: print 'Real axis:  %.2f %.2f %.2f' % real_axis
    if do_print: print 'Angles:     %.2f %.2f' % angles
    if do_print: print 'Real angle: %.2f' % real_angle

    if do_print: print 'Experiment:'
    if do_print: print 'Wavelength: %.5f' % wavelength

    # now need to dig out the detector axes
    # perhaps bodge this by looking at the displacements of pixels in the
    # fast and slow directions?

    origin = detector.get_pixel_coordinates(0, 0)
    fast = detector.get_pixel_coordinates(0, 1)
    slow = detector.get_pixel_coordinates(1, 0)

    if do_print: print 'Origin:     %.2f %.2f %.2f' % tuple(origin)

    dfast = [fast[j] - origin[j] for j in range(3)]
    dslow = [slow[j] - origin[j] for j in range(3)]

    lfast = math.sqrt(sum([dfast[j] * dfast[j] for j in range(3)]))
    lslow = math.sqrt(sum([dslow[j] * dslow[j] for j in range(3)]))

    fast = tuple([dfast[j] / lfast for j in range(3)])
    slow = tuple([dslow[j] / lslow for j in range(3)])

    if do_print: print 'Fast direction: %.2f %.2f %.2f' % fast
    if do_print: print 'Slow direction: %.2f %.2f %.2f' % slow

    if hasattr(detector, 'get_detector_axis_fast'):

        if do_print: print 'CBF fast: %.2f %.2f %.2f' % \
           tuple(detector.get_detector_axis_fast())
        if do_print: print 'CBF slow: %.2f %.2f %.2f' % \
           tuple(detector.get_detector_axis_slow())

    # now also compute the position on the detector where the beam will
    # actually strike the detector - this will be the intersection of the
    # source vector with the plane defined by fast and slow passing through
    # the detector origin. Then return this position on the detector in mm.
    #
    # unit vectors -
    # _b - beam
    # _n - detector normal
    # _f, _s - fast and slow directions on the detector
    #
    # full vectors -
    # _O - displacement of origin
    # _D - displacement to intersection of beam with detector plane
    # _B - displacement from origin to said intersection

    _b = matrix.col(beam_direction) / math.sqrt(
        matrix.col(beam_direction).dot())
    _n = matrix.col(detector_normal) / math.sqrt(
        matrix.col(detector_normal).dot())
    _f = matrix.col(fast) / math.sqrt(matrix.col(fast).dot())
    _s = matrix.col(slow) / math.sqrt(matrix.col(slow).dot())

    _O = matrix.col(origin)
    _D = _b * (_O.dot(_n) / _b.dot(_n))

    _B = _D - _O

    x = _B.dot(_f)
    y = _B.dot(_s)

    if do_print: print 'Computed:   %.2f %.2f' % (x, y)
    if do_print: print 'Size:       %.2f %.2f' % (size[0] * pixel[0],
                                                  size[1] * pixel[1])
    detector.__swig_destroy__(detector)
    del(detector)

    gonio.__swig_destroy__(gonio)
    del(gonio)

    return

if __name__ == '__main__':
    import time

    start = time.time()

    j = 0
    for image in sys.argv[1:]:
         print image
         cbfdump(image, do_print = True)
         j += 1

    end = time.time()

    print 'Reading %d headers took %.1fs' % (j, end - start)

<|endoftext|>