コード例 #1
0
class ProductPackaging(models.Model):
    _inherit = 'product.packaging'

    height = fields.Integer('Height')
    width = fields.Integer('Width')
    length = fields.Integer('Length')
    max_weight = fields.Float(
        'Max Weight', help='Maximum weight shippable in this packaging')
    shipper_package_code = fields.Char('Package Code')
    package_carrier_type = fields.Selection(
        [('none', 'No carrier integration')], string='Carrier', default='none')

    _sql_constraints = [
        ('positive_height', 'CHECK(height>=0)', 'Height must be positive'),
        ('positive_width', 'CHECK(width>=0)', 'Width must be positive'),
        ('positive_length', 'CHECK(length>=0)', 'Length must be positive'),
        ('positive_max_weight', 'CHECK(max_weight>=0.0)',
         'Max Weight must be positive'),
    ]
コード例 #2
0
class FleetVehicleModel(models.Model):
    _inherit = 'fleet.vehicle.model'

    default_recurring_cost_amount_depreciated = fields.Float(
        string="Cost (Depreciated)",
        help=
        "Default recurring cost amount that should be applied to a new car from this model"
    )
    default_co2 = fields.Float(string="CO2 emissions")
    default_fuel_type = fields.Selection([('gasoline', 'Gasoline'),
                                          ('diesel', 'Diesel'),
                                          ('electric', 'Electric'),
                                          ('hybrid', 'Hybrid')],
                                         'Fuel Type',
                                         help='Fuel Used by the vehicle')
    default_car_value = fields.Float(string="Catalog Value (VAT Incl.)")
    can_be_requested = fields.Boolean(
        string="Can be requested",
        help="Can be requested on a contract as a new car")
    default_atn = fields.Float(compute='_compute_atn', string="ATN")
    default_total_depreciated_cost = fields.Float(
        compute='_compute_default_total_depreciated_cost',
        string="Total Cost (Depreciated)")
    co2_fee = fields.Float(compute='_compute_co2_fee', string="CO2 fee")

    @api.depends('default_car_value', 'default_co2', 'default_fuel_type')
    def _compute_atn(self):
        now = Datetime.now()
        for model in self:
            model.default_atn = self.env['fleet.vehicle']._get_car_atn(
                now, model.default_car_value, model.default_fuel_type,
                model.default_co2)

    @api.depends('co2_fee', 'default_recurring_cost_amount_depreciated')
    def _compute_default_total_depreciated_cost(self):
        for model in self:
            model.default_total_depreciated_cost = model.co2_fee + model.default_recurring_cost_amount_depreciated

    @api.depends('default_co2')
    def _compute_co2_fee(self):
        for model in self:
            model.co2_fee = self.env['fleet.vehicle']._get_co2_fee(
                model.default_co2)
コード例 #3
0
class MailActivityType(models.Model):
    """ Activity Types are used to categorize activities. Each type is a different
    kind of activity e.g. call, mail, meeting. An activity can be generic i.e.
    available for all models using activities; or specific to a model in which
    case res_model_id field should be used. """
    _name = 'mail.activity.type'
    _description = 'Activity Type'
    _rec_name = 'name'
    _order = 'sequence, id'

    name = fields.Char('Name', required=True, translate=True)
    summary = fields.Char('Summary', translate=True)
    sequence = fields.Integer('Sequence', default=10)
    days = fields.Integer(
        '# Days',
        default=0,
        help=
        'Number of days before executing the action. It allows to plan the action deadline.'
    )
    icon = fields.Char('Icon', help="Font awesome icon e.g. fa-tasks")
    res_model_id = fields.Many2one(
        'ir.model',
        'Model',
        index=True,
        help='Specify a model if the activity should be specific to a model'
        ' and not available when managing activities for other models.')
    next_type_ids = fields.Many2many('mail.activity.type',
                                     'mail_activity_rel',
                                     'activity_id',
                                     'recommended_id',
                                     string='Recommended Next Activities')
    previous_type_ids = fields.Many2many('mail.activity.type',
                                         'mail_activity_rel',
                                         'recommended_id',
                                         'activity_id',
                                         string='Preceding Activities')
    category = fields.Selection(
        [('default', 'Other')],
        default='default',
        string='Category',
        help=
        'Categories may trigger specific behavior like opening calendar view')
コード例 #4
0
class ChannelPartner(models.Model):
    _name = 'mail.channel.partner'
    _description = 'Listeners of a Channel'
    _table = 'mail_channel_partner'
    _rec_name = 'partner_id'

    partner_id = fields.Many2one('res.partner',
                                 string='Recipient',
                                 ondelete='cascade')
    partner_email = fields.Char('Email', related='partner_id.email')
    channel_id = fields.Many2one('mail.channel',
                                 string='Channel',
                                 ondelete='cascade')
    seen_message_id = fields.Many2one('mail.message', string='Last Seen')
    fold_state = fields.Selection([('open', 'Open'), ('folded', 'Folded'),
                                   ('closed', 'Closed')],
                                  string='Conversation Fold State',
                                  default='open')
    is_minimized = fields.Boolean("Conversation is minimized")
    is_pinned = fields.Boolean("Is pinned on the interface", default=True)
コード例 #5
0
class mother(models.Model):
    _inherit = 'test.inherit.mother'

    field_in_mother = fields.Char()
    partner_id = fields.Many2one('res.partner')

    # extend the name field: make it required and change its default value
    name = fields.Char(required=True, default='Bar')

    # extend the selection of the state field, and discard its default value
    state = fields.Selection(selection_add=[('c', 'C')], default=None)

    # override the computed field, and extend its dependencies
    @api.one
    @api.depends('field_in_mother')
    def _compute_surname(self):
        if self.field_in_mother:
            self.surname = self.field_in_mother
        else:
            super(mother, self)._compute_surname()
コード例 #6
0
class IrModelField(models.Model):
    _inherit = 'ir.model.fields'

    track_visibility = fields.Selection(
        [('onchange', "On Change"), ('always', "Always")],
        string="Tracking",
        help=
        "When set, every modification to this field will be tracked in the chatter.",
    )

    def _reflect_field_params(self, field):
        vals = super(IrModelField, self)._reflect_field_params(field)
        vals['track_visibility'] = getattr(field, 'track_visibility', None)
        return vals

    def _instanciate_attrs(self, field_data):
        attrs = super(IrModelField, self)._instanciate_attrs(field_data)
        if attrs and field_data.get('track_visibility'):
            attrs['track_visibility'] = field_data['track_visibility']
        return attrs
コード例 #7
0
class TemplatePreview(models.TransientModel):
    _inherit = "mail.template"
    _name = "email_template.preview"
    _description = "Email Template Preview"

    @api.model
    def _get_records(self):
        """ Return Records of particular Email Template's Model """
        template_id = self._context.get('template_id')
        default_res_id = self._context.get('default_res_id')
        if not template_id:
            return []
        template = self.env['mail.template'].browse(int(template_id))
        records = self.env[template.model_id.model].search([], limit=10)
        records |= records.browse(default_res_id)
        return records.name_get()

    @api.model
    def default_get(self, fields):
        result = super(TemplatePreview, self).default_get(fields)

        if 'res_id' in fields and not result.get('res_id'):
            records = self._get_records()
            result['res_id'] = records and records[0][0] or False  # select first record as a Default
        if self._context.get('template_id') and 'model_id' in fields and not result.get('model_id'):
            result['model_id'] = self.env['mail.template'].browse(self._context['template_id']).model_id.id
        return result

    res_id = fields.Selection(_get_records, 'Sample Document')
    partner_ids = fields.Many2many('res.partner', string='Recipients')

    @api.onchange('res_id')
    @api.multi
    def on_change_res_id(self):
        mail_values = {}
        if self.res_id and self._context.get('template_id'):
            template = self.env['mail.template'].browse(self._context['template_id'])
            self.name = template.name
            mail_values = template.generate_email(self.res_id)
        for field in ['email_from', 'email_to', 'email_cc', 'reply_to', 'subject', 'body_html', 'partner_to', 'partner_ids', 'attachment_ids']:
            setattr(self, field, mail_values.get(field, False))
コード例 #8
0
ファイル: mrp_workcenter.py プロジェクト: gecoerp/gecoerp
class MrpWorkcenterProductivity(models.Model):
    _name = "mrp.workcenter.productivity"
    _description = "Workcenter Productivity Log"
    _order = "id desc"
    _rec_name = "loss_id"

    workcenter_id = fields.Many2one('mrp.workcenter', "Work Center", required=True)
    workorder_id = fields.Many2one('mrp.workorder', 'Work Order')
    user_id = fields.Many2one(
        'res.users', "User",
        default=lambda self: self.env.uid)
    loss_id = fields.Many2one(
        'mrp.workcenter.productivity.loss', "Loss Reason",
        ondelete='restrict', required=True)
    loss_type = fields.Selection(
        "Effectiveness", related='loss_id.loss_type', store=True)
    description = fields.Text('Description')
    date_start = fields.Datetime('Start Date', default=fields.Datetime.now, required=True)
    date_end = fields.Datetime('End Date')
    duration = fields.Float('Duration', compute='_compute_duration', store=True)

    @api.depends('date_end', 'date_start')
    def _compute_duration(self):
        for blocktime in self:
            if blocktime.date_end:
                d1 = fields.Datetime.from_string(blocktime.date_start)
                d2 = fields.Datetime.from_string(blocktime.date_end)
                diff = d2 - d1
                if (blocktime.loss_type not in ('productive', 'performance')) and blocktime.workcenter_id.resource_calendar_id:
                    r = blocktime.workcenter_id.resource_calendar_id.get_work_hours_count(d1, d2, blocktime.workcenter_id.resource_id.id)
                    blocktime.duration = round(r * 60, 2)
                else:
                    blocktime.duration = round(diff.total_seconds() / 60.0, 2)
            else:
                blocktime.duration = 0.0

    @api.multi
    def button_block(self):
        self.ensure_one()
        self.workcenter_id.order_ids.end_all()
コード例 #9
0
class ResConfigSettings(models.TransientModel):
    _inherit = 'res.config.settings'

    security_lead = fields.Float(related='company_id.security_lead',
                                 string="Security Lead Time")
    group_route_so_lines = fields.Boolean(
        "Order-Specific Routes",
        implied_group='sale_stock.group_route_so_lines')
    module_sale_order_dates = fields.Boolean("Delivery Date")
    group_display_incoterm = fields.Boolean(
        "Incoterms", implied_group='sale_stock.group_display_incoterm')
    use_security_lead = fields.Boolean(
        string="Security Lead Time for Sales",
        oldname='default_new_security_lead',
        help=
        "Margin of error for dates promised to customers. Products will be scheduled for delivery that many days earlier than the actual promised date, to cope with unexpected delays in the supply chain."
    )
    default_picking_policy = fields.Selection(
        [('direct', 'Ship products as soon as available, with back orders'),
         ('one', 'Ship all products at once')],
        "Shipping Management",
        default='direct',
        default_model="sale.order",
        required=True)

    @api.onchange('use_security_lead')
    def _onchange_use_security_lead(self):
        if not self.use_security_lead:
            self.security_lead = 0.0

    def get_values(self):
        res = super(ResConfigSettings, self).get_values()
        res.update(use_security_lead=self.env['ir.config_parameter'].sudo().
                   get_param('sale_stock.use_security_lead'))
        return res

    def set_values(self):
        super(ResConfigSettings, self).set_values()
        self.env['ir.config_parameter'].sudo().set_param(
            'sale_stock.use_security_lead', self.use_security_lead)
コード例 #10
0
ファイル: resource.py プロジェクト: gecoerp/gecoerp
class ResourceCalendarAttendance(models.Model):
    _name = "resource.calendar.attendance"
    _description = "Work Detail"
    _order = 'dayofweek, hour_from'

    name = fields.Char(required=True)
    dayofweek = fields.Selection([
        ('0', 'Monday'),
        ('1', 'Tuesday'),
        ('2', 'Wednesday'),
        ('3', 'Thursday'),
        ('4', 'Friday'),
        ('5', 'Saturday'),
        ('6', 'Sunday')
        ], 'Day of Week', required=True, index=True, default='0')
    date_from = fields.Date(string='Starting Date')
    date_to = fields.Date(string='End Date')
    hour_from = fields.Float(string='Work from', required=True, index=True,
        help="Start and End time of working.\n"
             "A specific value of 24:00 is interpreted as 23:59:59.999999.")
    hour_to = fields.Float(string='Work to', required=True)
    calendar_id = fields.Many2one("resource.calendar", string="Resource's Calendar", required=True, ondelete='cascade')
コード例 #11
0
ファイル: challenge.py プロジェクト: gecoerp/gecoerp
class ChallengeLine(models.Model):
    """Gamification challenge line

    Predefined goal for 'gamification_challenge'
    These are generic list of goals with only the target goal defined
    Should only be created for the gamification.challenge object
    """
    _name = 'gamification.challenge.line'
    _description = 'Gamification generic goal for challenge'
    _order = "sequence, id"

    challenge_id = fields.Many2one('gamification.challenge',
                                   string='Challenge',
                                   required=True,
                                   ondelete="cascade")
    definition_id = fields.Many2one('gamification.goal.definition',
                                    string='Goal Definition',
                                    required=True,
                                    ondelete="cascade")

    sequence = fields.Integer('Sequence',
                              help='Sequence number for ordering',
                              default=1)
    target_goal = fields.Float('Target Value to Reach', required=True)

    name = fields.Char("Name", related='definition_id.name')
    condition = fields.Selection("Condition",
                                 related='definition_id.condition',
                                 readonly=True)
    definition_suffix = fields.Char("Unit",
                                    related='definition_id.suffix',
                                    readonly=True)
    definition_monetary = fields.Boolean("Monetary",
                                         related='definition_id.monetary',
                                         readonly=True)
    definition_full_suffix = fields.Char("Suffix",
                                         related='definition_id.full_suffix',
                                         readonly=True)
コード例 #12
0
ファイル: res_partner.py プロジェクト: gecoerp/gecoerp
class res_partner(models.Model):
    _name = 'res.partner'
    _inherit = 'res.partner'

    @api.multi
    def _purchase_invoice_count(self):
        PurchaseOrder = self.env['purchase.order']
        Invoice = self.env['account.invoice']
        for partner in self:
            partner.purchase_order_count = PurchaseOrder.search_count([('partner_id', 'child_of', partner.id)])
            partner.supplier_invoice_count = Invoice.search_count([('partner_id', 'child_of', partner.id), ('type', '=', 'in_invoice')])

    @api.model
    def _commercial_fields(self):
        return super(res_partner, self)._commercial_fields()

    property_purchase_currency_id = fields.Many2one(
        'res.currency', string="Supplier Currency", company_dependent=True,
        help="This currency will be used, instead of the default one, for purchases from the current partner")
    purchase_order_count = fields.Integer(compute='_purchase_invoice_count', string='# of Purchase Order')
    supplier_invoice_count = fields.Integer(compute='_purchase_invoice_count', string='# Vendor Bills')
    purchase_warn = fields.Selection(WARNING_MESSAGE, 'Purchase Order', help=WARNING_HELP, required=True, default="no-message")
    purchase_warn_msg = fields.Text('Message for Purchase Order')
コード例 #13
0
ファイル: ir_actions.py プロジェクト: gecoerp/gecoerp
class IrServerObjectLines(models.Model):
    _name = 'ir.server.object.lines'
    _description = 'Server Action value mapping'
    _sequence = 'ir_actions_id_seq'

    server_id = fields.Many2one('ir.actions.server',
                                string='Related Server Action',
                                ondelete='cascade')
    col1 = fields.Many2one('ir.model.fields', string='Field', required=True)
    value = fields.Text(
        required=True,
        help="Expression containing a value specification. \n"
        "When Formula type is selected, this field may be a Python expression "
        " that can use the same values as for the code field on the server action.\n"
        "If Value type is selected, the value will be used directly without evaluation."
    )
    type = fields.Selection([('value', 'Value'),
                             ('equation', 'Python expression')],
                            'Evaluation Type',
                            default='value',
                            required=True,
                            change_default=True)

    @api.multi
    def eval_value(self, eval_context=None):
        result = dict.fromkeys(self.ids, False)
        for line in self:
            expr = line.value
            if line.type == 'equation':
                expr = safe_eval(line.value, eval_context)
            elif line.col1.ttype in ['many2one', 'integer']:
                try:
                    expr = int(line.value)
                except Exception:
                    pass
            result[line.id] = expr
        return result
コード例 #14
0
class StockQuantityHistory(models.TransientModel):
    _name = 'stock.quantity.history'
    _description = 'Stock Quantity History'

    compute_at_date = fields.Selection(
        [(0, 'Current Inventory'), (1, 'At a Specific Date')],
        string="Compute",
        help=
        "Choose to analyze the current inventory or from a specific date in the past."
    )
    date = fields.Datetime(
        'Inventory at Date',
        help="Choose a date to get the inventory at that date",
        default=fields.Datetime.now)

    def open_table(self):
        self.ensure_one()

        if self.compute_at_date:
            tree_view_id = self.env.ref('stock.view_stock_product_tree').id
            form_view_id = self.env.ref(
                'stock.product_form_view_procurement_button').id
            # We pass `to_date` in the context so that `qty_available` will be computed across
            # moves until date.
            action = {
                'type': 'ir.actions.act_window',
                'views': [(tree_view_id, 'tree'), (form_view_id, 'form')],
                'view_mode': 'tree,form',
                'name': _('Products'),
                'res_model': 'product.product',
                'context': dict(self.env.context, to_date=self.date),
            }
            return action
        else:
            self.env['stock.quant']._merge_quants()
            return self.env.ref('stock.quantsact').read()[0]
コード例 #15
0
class AccountPrintJournal(models.TransientModel):
    _inherit = "account.common.journal.report"
    _name = "account.print.journal"
    _description = "Account Print Journal"

    sort_selection = fields.Selection([
        ('date', 'Date'),
        ('move_name', 'Journal Entry Number'),
    ],
                                      'Entries Sorted by',
                                      required=True,
                                      default='move_name')
    journal_ids = fields.Many2many(
        'account.journal',
        string='Journals',
        required=True,
        default=lambda self: self.env['account.journal'].search([(
            'type', 'in', ['sale', 'purchase'])]))

    def _print_report(self, data):
        data = self.pre_print_report(data)
        data['form'].update({'sort_selection': self.sort_selection})
        return self.env.ref('account.action_report_journal').with_context(
            landscape=True).report_action(self, data=data)
コード例 #16
0
class Partner(models.Model):
    _inherit = 'res.partner'

    property_stock_customer = fields.Many2one(
        'stock.location',
        string="Customer Location",
        company_dependent=True,
        help=
        "This stock location will be used, instead of the default one, as the destination location for goods you send to this partner"
    )
    property_stock_supplier = fields.Many2one(
        'stock.location',
        string="Vendor Location",
        company_dependent=True,
        help=
        "This stock location will be used, instead of the default one, as the source location for goods you receive from the current partner"
    )
    picking_warn = fields.Selection(WARNING_MESSAGE,
                                    'Stock Picking',
                                    help=WARNING_HELP,
                                    default='no-message',
                                    required=True)
    # TDE FIXME: expand this message / help
    picking_warn_msg = fields.Text('Message for Stock Picking')
コード例 #17
0
class Company(models.Model):
    _name = "res.company"
    _description = 'Companies'
    _order = 'sequence, name'

    @api.multi
    def copy(self, default=None):
        raise UserError(
            _('Duplicating a company is not allowed. Please create a new company instead.'
              ))

    def _get_logo(self):
        return base64.b64encode(
            open(
                os.path.join(tools.config['root_path'], 'addons', 'base',
                             'res', 'res_company_logo.png'), 'rb').read())

    @api.model
    def _get_euro(self):
        return self.env['res.currency.rate'].search([('rate', '=', 1)],
                                                    limit=1).currency_id

    @api.model
    def _get_user_currency(self):
        currency_id = self.env['res.users'].browse(
            self._uid).company_id.currency_id
        return currency_id or self._get_euro()

    name = fields.Char(related='partner_id.name',
                       string='Company Name',
                       required=True,
                       store=True)
    sequence = fields.Integer(
        help='Used to order Companies in the company switcher', default=10)
    parent_id = fields.Many2one('res.company',
                                string='Parent Company',
                                index=True)
    child_ids = fields.One2many('res.company',
                                'parent_id',
                                string='Child Companies')
    partner_id = fields.Many2one('res.partner',
                                 string='Partner',
                                 required=True)
    report_header = fields.Text(
        string='Company Tagline',
        help=
        "Appears by default on the top right corner of your printed documents (report header)."
    )
    report_footer = fields.Text(
        string='Report Footer',
        translate=True,
        help="Footer text displayed at the bottom of all reports.")
    logo = fields.Binary(related='partner_id.image',
                         default=_get_logo,
                         string="Company Logo")
    # logo_web: do not store in attachments, since the image is retrieved in SQL for
    # performance reasons (see addons/web/controllers/main.py, Binary.company_logo)
    logo_web = fields.Binary(compute='_compute_logo_web', store=True)
    currency_id = fields.Many2one(
        'res.currency',
        string='Currency',
        required=True,
        default=lambda self: self._get_user_currency())
    user_ids = fields.Many2many('res.users',
                                'res_company_users_rel',
                                'cid',
                                'user_id',
                                string='Accepted Users')
    account_no = fields.Char(string='Account No.')
    street = fields.Char(compute='_compute_address', inverse='_inverse_street')
    street2 = fields.Char(compute='_compute_address',
                          inverse='_inverse_street2')
    zip = fields.Char(compute='_compute_address', inverse='_inverse_zip')
    city = fields.Char(compute='_compute_address', inverse='_inverse_city')
    state_id = fields.Many2one('res.country.state',
                               compute='_compute_address',
                               inverse='_inverse_state',
                               string="Fed. State")
    bank_ids = fields.One2many('res.partner.bank',
                               'company_id',
                               string='Bank Accounts',
                               help='Bank accounts related to this company')
    country_id = fields.Many2one('res.country',
                                 compute='_compute_address',
                                 inverse='_inverse_country',
                                 string="Country")
    email = fields.Char(related='partner_id.email', store=True)
    phone = fields.Char(related='partner_id.phone', store=True)
    website = fields.Char(related='partner_id.website')
    vat = fields.Char(related='partner_id.vat', string="TIN")
    company_registry = fields.Char()
    paperformat_id = fields.Many2one(
        'report.paperformat',
        'Paper format',
        default=lambda self: self.env.ref('base.paperformat_euro',
                                          raise_if_not_found=False))
    external_report_layout = fields.Selection([
        ('background', 'Background'),
        ('boxed', 'Boxed'),
        ('clean', 'Clean'),
        ('standard', 'Standard'),
    ],
                                              string='Document Template')

    _sql_constraints = [('name_uniq', 'unique (name)',
                         'The company name must be unique !')]

    @api.model_cr
    def init(self):
        for company in self.search([('paperformat_id', '=', False)]):
            paperformat_euro = self.env.ref('base.paperformat_euro', False)
            if paperformat_euro:
                company.write({'paperformat_id': paperformat_euro.id})
        sup = super(Company, self)
        if hasattr(sup, 'init'):
            sup.init()

    def _get_company_address_fields(self, partner):
        return {
            'street': partner.street,
            'street2': partner.street2,
            'city': partner.city,
            'zip': partner.zip,
            'state_id': partner.state_id,
            'country_id': partner.country_id,
        }

    # TODO @api.depends(): currently now way to formulate the dependency on the
    # partner's contact address
    def _compute_address(self):
        for company in self.filtered(lambda company: company.partner_id):
            address_data = company.partner_id.sudo().address_get(
                adr_pref=['contact'])
            if address_data['contact']:
                partner = company.partner_id.browse(
                    address_data['contact']).sudo()
                company.update(company._get_company_address_fields(partner))

    def _inverse_street(self):
        for company in self:
            company.partner_id.street = company.street

    def _inverse_street2(self):
        for company in self:
            company.partner_id.street2 = company.street2

    def _inverse_zip(self):
        for company in self:
            company.partner_id.zip = company.zip

    def _inverse_city(self):
        for company in self:
            company.partner_id.city = company.city

    def _inverse_state(self):
        for company in self:
            company.partner_id.state_id = company.state_id

    def _inverse_country(self):
        for company in self:
            company.partner_id.country_id = company.country_id

    @api.depends('partner_id', 'partner_id.image')
    def _compute_logo_web(self):
        for company in self:
            company.logo_web = tools.image_resize_image(
                company.partner_id.image, (180, None))

    @api.onchange('state_id')
    def _onchange_state(self):
        self.country_id = self.state_id.country_id

    @api.multi
    def on_change_country(self, country_id):
        # This function is called from account/models/chart_template.py, hence decorated with `multi`.
        self.ensure_one()
        currency_id = self._get_user_currency()
        if country_id:
            currency_id = self.env['res.country'].browse(
                country_id).currency_id
        return {'value': {'currency_id': currency_id.id}}

    @api.onchange('country_id')
    def _onchange_country_id_wrapper(self):
        res = {'domain': {'state_id': []}}
        if self.country_id:
            res['domain']['state_id'] = [('country_id', '=',
                                          self.country_id.id)]
        values = self.on_change_country(self.country_id.id)['value']
        for fname, value in values.items():
            setattr(self, fname, value)
        return res

    @api.model
    def name_search(self, name='', args=None, operator='ilike', limit=100):
        context = dict(self.env.context)
        newself = self
        if context.pop('user_preference', None):
            # We browse as superuser. Otherwise, the user would be able to
            # select only the currently visible companies (according to rules,
            # which are probably to allow to see the child companies) even if
            # she belongs to some other companies.
            companies = self.env.user.company_id + self.env.user.company_ids
            args = (args or []) + [('id', 'in', companies.ids)]
            newself = newself.sudo()
        return super(Company, newself.with_context(context)).name_search(
            name=name, args=args, operator=operator, limit=limit)

    @api.model
    @api.returns('self', lambda value: value.id)
    def _company_default_get(self, object=False, field=False):
        """ Returns the default company (usually the user's company).
        The 'object' and 'field' arguments are ignored but left here for
        backward compatibility and potential override.
        """
        return self.env['res.users']._get_company()

    @api.model
    @tools.ormcache('self.env.uid', 'company')
    def _get_company_children(self, company=None):
        if not company:
            return []
        return self.search([('parent_id', 'child_of', [company])]).ids

    @api.multi
    def _get_partner_hierarchy(self):
        self.ensure_one()
        parent = self.parent_id
        if parent:
            return parent._get_partner_hierarchy()
        else:
            return self._get_partner_descendance([])

    @api.multi
    def _get_partner_descendance(self, descendance):
        self.ensure_one()
        descendance.append(self.partner_id.id)
        for child_id in self._get_company_children(self.id):
            if child_id != self.id:
                descendance = self.browse(child_id)._get_partner_descendance(
                    descendance)
        return descendance

    # deprecated, use clear_caches() instead
    def cache_restart(self):
        self.clear_caches()

    @api.model
    def create(self, vals):
        if not vals.get('name') or vals.get('partner_id'):
            self.clear_caches()
            return super(Company, self).create(vals)
        partner = self.env['res.partner'].create({
            'name': vals['name'],
            'is_company': True,
            'image': vals.get('logo'),
            'customer': False,
            'email': vals.get('email'),
            'phone': vals.get('phone'),
            'website': vals.get('website'),
            'vat': vals.get('vat'),
        })
        vals['partner_id'] = partner.id
        self.clear_caches()
        company = super(Company, self).create(vals)
        partner.write({'company_id': company.id})
        return company

    @api.multi
    def write(self, values):
        self.clear_caches()
        return super(Company, self).write(values)

    @api.constrains('parent_id')
    def _check_parent_id(self):
        if not self._check_recursion():
            raise ValidationError(
                _('Error ! You cannot create recursive companies.'))

    @api.multi
    def open_company_edit_report(self):
        self.ensure_one()
        return self.env['res.config.settings'].open_company()

    @api.multi
    def write_company_and_print_report(self, values):
        res = self.write(values)

        report_name = values.get('default_report_name')
        active_ids = values.get('active_ids')
        active_model = values.get('active_model')
        if report_name and active_ids and active_model:
            docids = self.env[active_model].browse(active_ids)
            return (self.env['ir.actions.report'].search(
                [('report_name', '=', report_name)],
                limit=1).with_context(values).report_action(docids))
        else:
            return res
コード例 #18
0
class mother(models.Model):
    _inherit = 'test.inherit.mother'

    # extend again the selection of the state field
    state = fields.Selection(selection_add=[('d', 'D')])
コード例 #19
0
class Lead2OpportunityPartner(models.TransientModel):

    _name = 'crm.lead2opportunity.partner'
    _description = 'Lead To Opportunity Partner'
    _inherit = 'crm.partner.binding'

    @api.model
    def default_get(self, fields):
        """ Default get for name, opportunity_ids.
            If there is an exisitng partner link to the lead, find all existing
            opportunities links with this partner to merge all information together
        """
        result = super(Lead2OpportunityPartner, self).default_get(fields)
        if self._context.get('active_id'):
            tomerge = {int(self._context['active_id'])}

            partner_id = result.get('partner_id')
            lead = self.env['crm.lead'].browse(self._context['active_id'])
            email = lead.partner_id.email if lead.partner_id else lead.email_from

            tomerge.update(self._get_duplicated_leads(partner_id, email, include_lost=True).ids)

            if 'action' in fields and not result.get('action'):
                result['action'] = 'exist' if partner_id else 'create'
            if 'partner_id' in fields:
                result['partner_id'] = partner_id
            if 'name' in fields:
                result['name'] = 'merge' if len(tomerge) >= 2 else 'convert'
            if 'opportunity_ids' in fields and len(tomerge) >= 2:
                result['opportunity_ids'] = list(tomerge)
            if lead.user_id:
                result['user_id'] = lead.user_id.id
            if lead.team_id:
                result['team_id'] = lead.team_id.id
            if not partner_id and not lead.contact_name:
                result['action'] = 'nothing'
        return result

    name = fields.Selection([
        ('convert', 'Convert to opportunity'),
        ('merge', 'Merge with existing opportunities')
    ], 'Conversion Action', required=True)
    opportunity_ids = fields.Many2many('crm.lead', string='Opportunities')
    user_id = fields.Many2one('res.users', 'Salesperson', index=True)
    team_id = fields.Many2one('crm.team', 'Sales Channel', oldname='section_id', index=True)

    @api.onchange('action')
    def onchange_action(self):
        if self.action == 'exist':
            self.partner_id = self._find_matching_partner()
        else:
            self.partner_id = False

    @api.onchange('user_id')
    def _onchange_user(self):
        """ When changing the user, also set a team_id or restrict team id
            to the ones user_id is member of.
        """
        if self.user_id:
            if self.team_id:
                user_in_team = self.env['crm.team'].search_count([('id', '=', self.team_id.id), '|', ('user_id', '=', self.user_id.id), ('member_ids', '=', self.user_id.id)])
            else:
                user_in_team = False
            if not user_in_team:
                values = self.env['crm.lead']._onchange_user_values(self.user_id.id if self.user_id else False)
                self.team_id = values.get('team_id', False)

    @api.model
    def _get_duplicated_leads(self, partner_id, email, include_lost=False):
        """ Search for opportunities that have the same partner and that arent done or cancelled """
        return self.env['crm.lead']._get_duplicated_leads_by_emails(partner_id, email, include_lost=include_lost)

    # NOTE JEM : is it the good place to test this ?
    @api.model
    def view_init(self, fields):
        """ Check some preconditions before the wizard executes. """
        for lead in self.env['crm.lead'].browse(self._context.get('active_ids', [])):
            if lead.probability == 100:
                raise UserError(_("Closed/Dead leads cannot be converted into opportunities."))
        return False

    @api.multi
    def _convert_opportunity(self, vals):
        self.ensure_one()

        res = False

        leads = self.env['crm.lead'].browse(vals.get('lead_ids'))
        for lead in leads:
            self_def_user = self.with_context(default_user_id=self.user_id.id)
            partner_id = self_def_user._create_partner(
                lead.id, self.action, vals.get('partner_id') or lead.partner_id.id)
            res = lead.convert_opportunity(partner_id, [], False)
        user_ids = vals.get('user_ids')

        leads_to_allocate = leads
        if self._context.get('no_force_assignation'):
            leads_to_allocate = leads_to_allocate.filtered(lambda lead: not lead.user_id)

        if user_ids:
            leads_to_allocate.allocate_salesman(user_ids, team_id=(vals.get('team_id')))

        return res

    @api.multi
    def action_apply(self):
        """ Convert lead to opportunity or merge lead and opportunity and open
            the freshly created opportunity view.
        """
        self.ensure_one()
        values = {
            'team_id': self.team_id.id,
        }

        if self.partner_id:
            values['partner_id'] = self.partner_id.id

        if self.name == 'merge':
            leads = self.with_context(active_test=False).opportunity_ids.merge_opportunity()
            if not leads.active:
                leads.write({'active': True, 'activity_type_id': False, 'lost_reason': False})
            if leads.type == "lead":
                values.update({'lead_ids': leads.ids, 'user_ids': [self.user_id.id]})
                self.with_context(active_ids=leads.ids)._convert_opportunity(values)
            elif not self._context.get('no_force_assignation') or not leads.user_id:
                values['user_id'] = self.user_id.id
                leads.write(values)
        else:
            leads = self.env['crm.lead'].browse(self._context.get('active_ids', []))
            values.update({'lead_ids': leads.ids, 'user_ids': [self.user_id.id]})
            self._convert_opportunity(values)

        return leads[0].redirect_opportunity_view()

    def _create_partner(self, lead_id, action, partner_id):
        """ Create partner based on action.
            :return dict: dictionary organized as followed: {lead_id: partner_assigned_id}
        """
        #TODO this method in only called by Lead2OpportunityPartner
        #wizard and would probably diserve to be refactored or at least
        #moved to a better place
        if action == 'each_exist_or_create':
            partner_id = self.with_context(active_id=lead_id)._find_matching_partner()
            action = 'create'
        result = self.env['crm.lead'].browse(lead_id).handle_partner_assignation(action, partner_id)
        return result.get(lead_id)
コード例 #20
0
class Lead2OpportunityMassConvert(models.TransientModel):

    _name = 'crm.lead2opportunity.partner.mass'
    _description = 'Mass Lead To Opportunity Partner'
    _inherit = 'crm.lead2opportunity.partner'

    @api.model
    def default_get(self, fields):
        res = super(Lead2OpportunityMassConvert, self).default_get(fields)
        if 'partner_id' in fields:  # avoid forcing the partner of the first lead as default
            res['partner_id'] = False
        if 'action' in fields:
            res['action'] = 'each_exist_or_create'
        if 'name' in fields:
            res['name'] = 'convert'
        if 'opportunity_ids' in fields:
            res['opportunity_ids'] = False
        return res

    user_ids = fields.Many2many('res.users', string='Salesmen')
    team_id = fields.Many2one('crm.team', 'Sales Channel', index=True, oldname='section_id')
    deduplicate = fields.Boolean('Apply deduplication', default=True, help='Merge with existing leads/opportunities of each partner')
    action = fields.Selection([
        ('each_exist_or_create', 'Use existing partner or create'),
        ('nothing', 'Do not link to a customer')
    ], 'Related Customer', required=True)
    force_assignation = fields.Boolean('Force assignation', help='If unchecked, this will leave the salesman of duplicated opportunities')

    @api.onchange('action')
    def _onchange_action(self):
        if self.action != 'exist':
            self.partner_id = False

    @api.onchange('deduplicate')
    def _onchange_deduplicate(self):
        active_leads = self.env['crm.lead'].browse(self._context['active_ids'])
        partner_ids = [(lead.partner_id.id, lead.partner_id and lead.partner_id.email or lead.email_from) for lead in active_leads]
        partners_duplicated_leads = {}
        for partner_id, email in partner_ids:
            duplicated_leads = self._get_duplicated_leads(partner_id, email)
            if len(duplicated_leads) > 1:
                partners_duplicated_leads.setdefault((partner_id, email), []).extend(duplicated_leads)

        leads_with_duplicates = []
        for lead in active_leads:
            lead_tuple = (lead.partner_id.id, lead.partner_id.email if lead.partner_id else lead.email_from)
            if len(partners_duplicated_leads.get(lead_tuple, [])) > 1:
                leads_with_duplicates.append(lead.id)

        self.opportunity_ids = self.env['crm.lead'].browse(leads_with_duplicates)

    @api.multi
    def _convert_opportunity(self, vals):
        """ When "massively" (more than one at a time) converting leads to
            opportunities, check the salesteam_id and salesmen_ids and update
            the values before calling super.
        """
        self.ensure_one()
        salesteam_id = self.team_id.id if self.team_id else False
        salesmen_ids = []
        if self.user_ids:
            salesmen_ids = self.user_ids.ids
        vals.update({'user_ids': salesmen_ids, 'team_id': salesteam_id})
        return super(Lead2OpportunityMassConvert, self)._convert_opportunity(vals)

    @api.multi
    def mass_convert(self):
        self.ensure_one()
        if self.name == 'convert' and self.deduplicate:
            merged_lead_ids = set()
            remaining_lead_ids = set()
            lead_selected = self._context.get('active_ids', [])
            for lead_id in lead_selected:
                if lead_id not in merged_lead_ids:
                    lead = self.env['crm.lead'].browse(lead_id)
                    duplicated_leads = self._get_duplicated_leads(lead.partner_id.id, lead.partner_id.email if lead.partner_id else lead.email_from)
                    if len(duplicated_leads) > 1:
                        lead = duplicated_leads.merge_opportunity()
                        merged_lead_ids.update(duplicated_leads.ids)
                        remaining_lead_ids.add(lead.id)
            active_ids = set(self._context.get('active_ids', {}))
            active_ids = (active_ids - merged_lead_ids) | remaining_lead_ids

            self = self.with_context(active_ids=list(active_ids))  # only update active_ids when there are set
        no_force_assignation = self._context.get('no_force_assignation', not self.force_assignation)
        return self.with_context(no_force_assignation=no_force_assignation).action_apply()
コード例 #21
0
ファイル: gamification.py プロジェクト: gecoerp/gecoerp
class Badge(models.Model):
    _inherit = 'gamification.badge'

    level = fields.Selection([('bronze', 'bronze'), ('silver', 'silver'), ('gold', 'gold')], string='Forum Badge Level')
コード例 #22
0
class account_financial_report(models.Model):
    _name = "account.financial.report"
    _description = "Account Report"

    @api.multi
    @api.depends('parent_id', 'parent_id.level')
    def _get_level(self):
        '''Returns a dictionary with key=the ID of a record and value = the level of this  
           record in the tree structure.'''
        for report in self:
            level = 0
            if report.parent_id:
                level = report.parent_id.level + 1
            report.level = level

    def _get_children_by_order(self):
        '''returns a recordset of all the children computed recursively, and sorted by sequence. Ready for the printing'''
        res = self
        children = self.search([('parent_id', 'in', self.ids)],
                               order='sequence ASC')
        if children:
            for child in children:
                res += child._get_children_by_order()
        return res

    name = fields.Char('Report Name', required=True, translate=True)
    parent_id = fields.Many2one('account.financial.report', 'Parent')
    children_ids = fields.One2many('account.financial.report', 'parent_id',
                                   'Account Report')
    sequence = fields.Integer('Sequence')
    level = fields.Integer(compute='_get_level', string='Level', store=True)
    type = fields.Selection([
        ('sum', 'View'),
        ('accounts', 'Accounts'),
        ('account_type', 'Account Type'),
        ('account_report', 'Report Value'),
    ],
                            'Type',
                            default='sum')
    account_ids = fields.Many2many('account.account',
                                   'account_account_financial_report',
                                   'report_line_id', 'account_id', 'Accounts')
    account_report_id = fields.Many2one('account.financial.report',
                                        'Report Value')
    account_type_ids = fields.Many2many(
        'account.account.type', 'account_account_financial_report_type',
        'report_id', 'account_type_id', 'Account Types')
    sign = fields.Selection(
        [(-1, 'Reverse balance sign'), (1, 'Preserve balance sign')],
        'Sign on Reports',
        required=True,
        default=1,
        help=
        'For accounts that are typically more debited than credited and that you would like to print as negative amounts in your reports, you should reverse the sign of the balance; e.g.: Expense account. The same applies for accounts that are typically more credited than debited and that you would like to print as positive amounts in your reports; e.g.: Income account.'
    )
    display_detail = fields.Selection(
        [('no_detail', 'No detail'), ('detail_flat', 'Display children flat'),
         ('detail_with_hierarchy', 'Display children with hierarchy')],
        'Display details',
        default='detail_flat')
    style_overwrite = fields.Selection(
        [
            (0, 'Automatic formatting'),
            (1, 'Main Title 1 (bold, underlined)'),
            (2, 'Title 2 (bold)'),
            (3, 'Title 3 (bold, smaller)'),
            (4, 'Normal Text'),
            (5, 'Italic Text (smaller)'),
            (6, 'Smallest Text'),
        ],
        'Financial Report Style',
        default=0,
        help=
        "You can set up here the format you want this record to be displayed. If you leave the automatic formatting, it will be computed based on the financial reports hierarchy (auto-computed field 'level')."
    )
コード例 #23
0
class IrTranslation(models.Model):
    _inherit = "ir.translation"

    gengo_comment = fields.Text("Comments & Activity Linked to Gengo")
    order_id = fields.Char('Gengo Order ID')
    gengo_translation = fields.Selection(
        [('machine', 'Translation By Machine'), ('standard', 'Standard'),
         ('pro', 'Pro'), ('ultra', 'Ultra')],
        "Gengo Translation Service Level",
        help=
        'You can select here the service level you want for an automatic translation using Gengo.'
    )

    @api.model
    def _get_all_supported_languages(self):
        flag, gengo = self.env['base.gengo.translations'].gengo_authentication(
        )
        if not flag:
            raise UserError(gengo)
        supported_langs = {}
        lang_pair = gengo.getServiceLanguagePairs(lc_src='en')
        if lang_pair['opstat'] == 'ok':
            for g_lang in lang_pair['response']:
                if g_lang['lc_tgt'] not in supported_langs:
                    supported_langs[g_lang['lc_tgt']] = []
                supported_langs[g_lang['lc_tgt']] += [g_lang['tier']]
        return supported_langs

    def _get_gengo_corresponding_language(self, lang):
        return lang in LANG_CODE_MAPPING and LANG_CODE_MAPPING[lang][0] or lang

    @api.model
    def _get_source_query(self, name, types, lang, source, res_id):
        query, params = super(IrTranslation,
                              self)._get_source_query(name, types, lang,
                                                      source, res_id)
        query += """
                    ORDER BY
                        CASE
                            WHEN gengo_translation=%s then 10
                            WHEN gengo_translation=%s then 20
                            WHEN gengo_translation=%s then 30
                            WHEN gengo_translation=%s then 40
                            ELSE 0
                        END DESC
                 """
        params += (
            'machine',
            'standard',
            'ultra',
            'pro',
        )
        return (query, params)

    @api.model
    def _get_terms_query(self, field, records):
        query, params = super(IrTranslation,
                              self)._get_terms_query(field, records)
        # order translations from worst to best
        query += """
                    ORDER BY
                        CASE
                            WHEN gengo_translation=%s then 10
                            WHEN gengo_translation=%s then 20
                            WHEN gengo_translation=%s then 30
                            WHEN gengo_translation=%s then 40
                            ELSE 0
                        END ASC
                 """
        params += ('machine', 'standard', 'ultra', 'pro')
        return query, params
コード例 #24
0
class PartnerBinding(models.TransientModel):
    """
        Handle the partner binding or generation in any CRM wizard that requires
        such feature, like the lead2opportunity wizard, or the
        phonecall2opportunity wizard.  Try to find a matching partner from the
        CRM model's information (name, email, phone number, etc) or create a new
        one on the fly.
        Use it like a mixin with the wizard of your choice.
    """

    _name = 'crm.partner.binding'
    _description = 'Handle partner binding or generation in CRM wizards.'

    @api.model
    def default_get(self, fields):
        res = super(PartnerBinding, self).default_get(fields)
        partner_id = self._find_matching_partner()

        if 'action' in fields and not res.get('action'):
            res['action'] = 'exist' if partner_id else 'create'
        if 'partner_id' in fields:
            res['partner_id'] = partner_id
        return res

    action = fields.Selection([
        ('exist', 'Link to an existing customer'),
        ('create', 'Create a new customer'),
        ('nothing', 'Do not link to a customer')
    ], 'Related Customer', required=True)
    partner_id = fields.Many2one('res.partner', 'Customer')

    @api.model
    def _find_matching_partner(self):
        """ Try to find a matching partner regarding the active model data, like
            the customer's name, email, phone number, etc.
            :return int partner_id if any, False otherwise
        """
        # active model has to be a lead
        if self._context.get('active_model') != 'crm.lead' or not self._context.get('active_id'):
            return False

        lead = self.env['crm.lead'].browse(self._context.get('active_id'))

        # find the best matching partner for the active model
        Partner = self.env['res.partner']
        if lead.partner_id:  # a partner is set already
            return lead.partner_id.id

        if lead.email_from:  # search through the existing partners based on the lead's email
            partner = Partner.search([('email', '=', lead.email_from)], limit=1)
            return partner.id

        if lead.partner_name:  # search through the existing partners based on the lead's partner or contact name
            partner = Partner.search([('name', 'ilike', '%' + lead.partner_name + '%')], limit=1)
            return partner.id

        if lead.contact_name:
            partner = Partner.search([('name', 'ilike', '%' + lead.contact_name+'%')], limit=1)
            return partner.id

        return False
コード例 #25
0
class ReportProjectTaskUser(models.Model):
    _name = "report.project.task.user"
    _description = "Tasks by user and project"
    _order = 'name desc, project_id'
    _auto = False

    name = fields.Char(string='Task Title', readonly=True)
    user_id = fields.Many2one('res.users', string='Assigned To', readonly=True)
    date_start = fields.Datetime(string='Assignation Date', readonly=True)
    date_end = fields.Datetime(string='Ending Date', readonly=True)
    date_deadline = fields.Date(string='Deadline', readonly=True)
    date_last_stage_update = fields.Datetime(string='Last Stage Update', readonly=True)
    project_id = fields.Many2one('project.project', string='Project', readonly=True)
    working_days_close = fields.Float(string='# Working Days to Close',
        digits=(16,2), readonly=True, group_operator="avg",
        help="Number of Working Days to close the task")
    working_days_open = fields.Float(string='# Working Days to Assign',
        digits=(16,2), readonly=True, group_operator="avg",
        help="Number of Working Days to Open the task")
    delay_endings_days = fields.Float(string='# Days to Deadline', digits=(16,2), readonly=True)
    nbr = fields.Integer('# of Tasks', readonly=True)  # TDE FIXME master: rename into nbr_tasks
    priority = fields.Selection([
        ('0', 'Low'),
        ('1', 'Normal'),
        ('2', 'High')
        ], size=1, readonly=True, string="Priority")
    state = fields.Selection([
            ('normal', 'In Progress'),
            ('blocked', 'Blocked'),
            ('done', 'Ready for next stage')
        ], string='Kanban State', readonly=True)
    company_id = fields.Many2one('res.company', string='Company', readonly=True)
    partner_id = fields.Many2one('res.partner', string='Contact', readonly=True)
    stage_id = fields.Many2one('project.task.type', string='Stage', readonly=True)

    def _select(self):
        select_str = """
             SELECT
                    (select 1 ) AS nbr,
                    t.id as id,
                    t.date_start as date_start,
                    t.date_end as date_end,
                    t.date_last_stage_update as date_last_stage_update,
                    t.date_deadline as date_deadline,
                    t.user_id,
                    t.project_id,
                    t.priority,
                    t.name as name,
                    t.company_id,
                    t.partner_id,
                    t.stage_id as stage_id,
                    t.kanban_state as state,
                    t.working_days_close as working_days_close,
                    t.working_days_open  as working_days_open,
                    (extract('epoch' from (t.date_deadline-(now() at time zone 'UTC'))))/(3600*24)  as delay_endings_days
        """
        return select_str

    def _group_by(self):
        group_by_str = """
                GROUP BY
                    t.id,
                    create_date,
                    write_date,
                    date_start,
                    date_end,
                    date_deadline,
                    date_last_stage_update,
                    t.user_id,
                    t.project_id,
                    t.priority,
                    name,
                    t.company_id,
                    t.partner_id,
                    stage_id
        """
        return group_by_str

    def init(self):
        tools.drop_view_if_exists(self._cr, self._table)
        self._cr.execute("""
            CREATE view %s as
              %s
              FROM project_task t
                WHERE t.active = 'true'
                %s
        """ % (self._table, self._select(), self._group_by()))
コード例 #26
0
class FleetVehicleLogContract(models.Model):
    _inherit = ['mail.thread']
    _inherits = {'fleet.vehicle.cost': 'cost_id'}
    _name = 'fleet.vehicle.log.contract'
    _description = 'Contract information on a vehicle'
    _order = 'state desc,expiration_date'

    def compute_next_year_date(self, strdate):
        oneyear = relativedelta(years=1)
        start_date = fields.Date.from_string(strdate)
        return fields.Date.to_string(start_date + oneyear)

    @api.model
    def default_get(self, default_fields):
        res = super(FleetVehicleLogContract, self).default_get(default_fields)
        contract = self.env.ref('fleet.type_contract_leasing',
                                raise_if_not_found=False)
        res.update({
            'date': fields.Date.context_today(self),
            'cost_subtype_id': contract and contract.id or False,
            'cost_type': 'contract'
        })
        return res

    name = fields.Text(compute='_compute_contract_name', store=True)
    active = fields.Boolean(default=True)
    start_date = fields.Date(
        'Contract Start Date',
        default=fields.Date.context_today,
        help='Date when the coverage of the contract begins')
    expiration_date = fields.Date(
        'Contract Expiration Date',
        default=lambda self: self.compute_next_year_date(
            fields.Date.context_today(self)),
        help=
        'Date when the coverage of the contract expirates (by default, one year after begin date)'
    )
    days_left = fields.Integer(compute='_compute_days_left',
                               string='Warning Date')
    insurer_id = fields.Many2one('res.partner', 'Vendor')
    purchaser_id = fields.Many2one(
        'res.partner',
        'Contractor',
        default=lambda self: self.env.user.partner_id.id,
        help='Person to which the contract is signed for')
    ins_ref = fields.Char('Contract Reference', size=64, copy=False)
    state = fields.Selection(
        [('futur', 'Incoming'), ('open', 'In Progress'),
         ('expired', 'Expired'), ('diesoon', 'Expiring Soon'),
         ('closed', 'Closed')],
        'Status',
        default='open',
        readonly=True,
        help='Choose whether the contract is still valid or not',
        track_visibility="onchange",
        copy=False)
    notes = fields.Text(
        'Terms and Conditions',
        help=
        'Write here all supplementary information relative to this contract',
        copy=False)
    cost_generated = fields.Float(
        'Recurring Cost Amount',
        help="Costs paid at regular intervals, depending on the cost frequency. "
        "If the cost frequency is set to unique, the cost will be logged at the start date"
    )
    cost_frequency = fields.Selection([('no', 'No'), ('daily', 'Daily'),
                                       ('weekly', 'Weekly'),
                                       ('monthly', 'Monthly'),
                                       ('yearly', 'Yearly')],
                                      'Recurring Cost Frequency',
                                      default='no',
                                      help='Frequency of the recuring cost',
                                      required=True)
    generated_cost_ids = fields.One2many('fleet.vehicle.cost', 'contract_id',
                                         'Generated Costs')
    sum_cost = fields.Float(compute='_compute_sum_cost',
                            string='Indicative Costs Total')
    cost_id = fields.Many2one('fleet.vehicle.cost',
                              'Cost',
                              required=True,
                              ondelete='cascade')
    # we need to keep this field as a related with store=True because the graph view doesn't support
    # (1) to address fields from inherited table
    # (2) fields that aren't stored in database
    cost_amount = fields.Float(related='cost_id.amount',
                               string='Amount',
                               store=True)
    odometer = fields.Float(
        string='Odometer at creation',
        help=
        'Odometer measure of the vehicle at the moment of the contract creation'
    )

    @api.depends('vehicle_id', 'cost_subtype_id', 'date')
    def _compute_contract_name(self):
        for record in self:
            name = record.vehicle_id.name
            if record.cost_subtype_id.name:
                name += ' / ' + record.cost_subtype_id.name
            if record.date:
                name += ' / ' + record.date
            record.name = name

    @api.depends('expiration_date', 'state')
    def _compute_days_left(self):
        """return a dict with as value for each contract an integer
        if contract is in an open state and is overdue, return 0
        if contract is in a closed state, return -1
        otherwise return the number of days before the contract expires
        """
        for record in self:
            if (record.expiration_date
                    and (record.state == 'open' or record.state == 'expired')):
                today = fields.Date.from_string(fields.Date.today())
                renew_date = fields.Date.from_string(record.expiration_date)
                diff_time = (renew_date - today).days
                record.days_left = diff_time > 0 and diff_time or 0
            else:
                record.days_left = -1

    @api.depends('cost_ids.amount')
    def _compute_sum_cost(self):
        for contract in self:
            contract.sum_cost = sum(contract.cost_ids.mapped('amount'))

    @api.onchange('vehicle_id')
    def _onchange_vehicle(self):
        if self.vehicle_id:
            self.odometer_unit = self.vehicle_id.odometer_unit

    @api.multi
    def contract_close(self):
        for record in self:
            record.state = 'closed'

    @api.multi
    def contract_open(self):
        for record in self:
            record.state = 'open'

    @api.multi
    def act_renew_contract(self):
        assert len(
            self.ids
        ) == 1, "This operation should only be done for 1 single contract at a time, as it it suppose to open a window as result"
        for element in self:
            # compute end date
            startdate = fields.Date.from_string(element.start_date)
            enddate = fields.Date.from_string(element.expiration_date)
            diffdate = (enddate - startdate)
            default = {
                'date':
                fields.Date.context_today(self),
                'start_date':
                fields.Date.to_string(
                    fields.Date.from_string(element.expiration_date) +
                    relativedelta(days=1)),
                'expiration_date':
                fields.Date.to_string(enddate + diffdate),
            }
            newid = element.copy(default).id
        return {
            'name': _("Renew Contract"),
            'view_mode': 'form',
            'view_id':
            self.env.ref('fleet.fleet_vehicle_log_contract_view_form').id,
            'view_type': 'tree,form',
            'res_model': 'fleet.vehicle.log.contract',
            'type': 'ir.actions.act_window',
            'domain': '[]',
            'res_id': newid,
            'context': {
                'active_id': newid
            },
        }

    @api.model
    def scheduler_manage_auto_costs(self):
        # This method is called by a cron task
        # It creates costs for contracts having the "recurring cost" field setted, depending on their frequency
        # For example, if a contract has a reccuring cost of 200 with a weekly frequency, this method creates a cost of 200 on the
        # first day of each week, from the date of the last recurring costs in the database to today
        # If the contract has not yet any recurring costs in the database, the method generates the recurring costs from the start_date to today
        # The created costs are associated to a contract thanks to the many2one field contract_id
        # If the contract has no start_date, no cost will be created, even if the contract has recurring costs
        VehicleCost = self.env['fleet.vehicle.cost']
        deltas = {
            'yearly': relativedelta(years=+1),
            'monthly': relativedelta(months=+1),
            'weekly': relativedelta(weeks=+1),
            'daily': relativedelta(days=+1)
        }
        contracts = self.env['fleet.vehicle.log.contract'].search(
            [('state', '!=', 'closed')], offset=0, limit=None, order=None)
        for contract in contracts:
            if not contract.start_date or contract.cost_frequency == 'no':
                continue
            found = False
            last_cost_date = contract.start_date
            if contract.generated_cost_ids:
                last_autogenerated_cost = VehicleCost.search(
                    [('contract_id', '=', contract.id),
                     ('auto_generated', '=', True)],
                    offset=0,
                    limit=1,
                    order='date desc')
                if last_autogenerated_cost:
                    found = True
                    last_cost_date = last_autogenerated_cost.date
            startdate = fields.Date.from_string(last_cost_date)
            if found:
                startdate += deltas.get(contract.cost_frequency)
            today = fields.Date.from_string(fields.Date.context_today(self))
            while (startdate <= today) & (startdate <= fields.Date.from_string(
                    contract.expiration_date)):
                data = {
                    'amount': contract.cost_generated,
                    'date': fields.Date.context_today(self),
                    'vehicle_id': contract.vehicle_id.id,
                    'cost_subtype_id': contract.cost_subtype_id.id,
                    'contract_id': contract.id,
                    'auto_generated': True
                }
                self.env['fleet.vehicle.cost'].create(data)
                startdate += deltas.get(contract.cost_frequency)
        return True

    @api.model
    def scheduler_manage_contract_expiration(self):
        # This method is called by a cron task
        # It manages the state of a contract, possibly by posting a message on the vehicle concerned and updating its status
        date_today = fields.Date.from_string(fields.Date.today())
        in_fifteen_days = fields.Date.to_string(date_today +
                                                relativedelta(days=+15))
        nearly_expired_contracts = self.search([('state', '=', 'open'),
                                                ('expiration_date', '<',
                                                 in_fifteen_days)])
        res = {}
        for contract in nearly_expired_contracts:
            if contract.vehicle_id.id in res:
                res[contract.vehicle_id.id] += 1
            else:
                res[contract.vehicle_id.id] = 1

        Vehicle = self.env['fleet.vehicle']
        for vehicle, value in res.items():
            Vehicle.browse(vehicle).message_post(body=_(
                '%s contract(s) will expire soon and should be renewed and/or closed!'
            ) % value)
        nearly_expired_contracts.write({'state': 'diesoon'})

        expired_contracts = self.search([('state', '!=', 'expired'),
                                         ('expiration_date', '<',
                                          fields.Date.today())])
        expired_contracts.write({'state': 'expired'})

        futur_contracts = self.search([
            ('state', 'not in', ['futur', 'closed']),
            ('start_date', '>', fields.Date.today())
        ])
        futur_contracts.write({'state': 'futur'})

        now_running_contracts = self.search([('state', '=', 'futur'),
                                             ('start_date', '<=',
                                              fields.Date.today())])
        now_running_contracts.write({'state': 'open'})

    @api.model
    def run_scheduler(self):
        self.scheduler_manage_auto_costs()
        self.scheduler_manage_contract_expiration()
コード例 #27
0
class MrpWorkorder(models.Model):
    _name = 'mrp.workorder'
    _description = 'Work Order'
    _inherit = ['mail.thread']

    name = fields.Char(
        'Work Order', required=True,
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]})

    workcenter_id = fields.Many2one(
        'mrp.workcenter', 'Work Center', required=True,
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]})
    working_state = fields.Selection(
        'Workcenter Status', related='workcenter_id.working_state',
        help='Technical: used in views only')

    production_id = fields.Many2one(
        'mrp.production', 'Manufacturing Order',
        index=True, ondelete='cascade', required=True, track_visibility='onchange',
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]})
    product_id = fields.Many2one(
        'product.product', 'Product',
        related='production_id.product_id', readonly=True,
        help='Technical: used in views only.', store=True)
    product_uom_id = fields.Many2one(
        'product.uom', 'Unit of Measure',
        related='production_id.product_uom_id', readonly=True,
        help='Technical: used in views only.')
    production_availability = fields.Selection(
        'Stock Availability', readonly=True,
        related='production_id.availability', store=True,
        help='Technical: used in views and domains only.')
    production_state = fields.Selection(
        'Production State', readonly=True,
        related='production_id.state',
        help='Technical: used in views only.')
    product_tracking = fields.Selection(
        'Product Tracking', related='production_id.product_id.tracking',
        help='Technical: used in views only.')
    qty_production = fields.Float('Original Production Quantity', readonly=True, related='production_id.product_qty')
    qty_remaining = fields.Float('Quantity To Be Produced', compute='_compute_qty_remaining', digits=dp.get_precision('Product Unit of Measure'))
    qty_produced = fields.Float(
        'Quantity', default=0.0,
        readonly=True,
        digits=dp.get_precision('Product Unit of Measure'),
        help="The number of products already handled by this work order")
    qty_producing = fields.Float(
        'Currently Produced Quantity', default=1.0,
        digits=dp.get_precision('Product Unit of Measure'),
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]})
    is_produced = fields.Boolean(string="Has Been Produced",
        compute='_compute_is_produced')

    state = fields.Selection([
        ('pending', 'Pending'),
        ('ready', 'Ready'),
        ('progress', 'In Progress'),
        ('done', 'Finished'),
        ('cancel', 'Cancelled')], string='Status',
        default='pending')
    date_planned_start = fields.Datetime(
        'Scheduled Date Start',
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]})
    date_planned_finished = fields.Datetime(
        'Scheduled Date Finished',
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]})
    date_start = fields.Datetime(
        'Effective Start Date',
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]})
    date_finished = fields.Datetime(
        'Effective End Date',
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]})

    duration_expected = fields.Float(
        'Expected Duration', digits=(16, 2),
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
        help="Expected duration (in minutes)")
    duration = fields.Float(
        'Real Duration', compute='_compute_duration',
        readonly=True, store=True)
    duration_unit = fields.Float(
        'Duration Per Unit', compute='_compute_duration',
        readonly=True, store=True)
    duration_percent = fields.Integer(
        'Duration Deviation (%)', compute='_compute_duration',
        group_operator="avg", readonly=True, store=True)

    operation_id = fields.Many2one(
        'mrp.routing.workcenter', 'Operation')  # Should be used differently as BoM can change in the meantime
    worksheet = fields.Binary(
        'Worksheet', related='operation_id.worksheet', readonly=True)
    move_raw_ids = fields.One2many(
        'stock.move', 'workorder_id', 'Moves')
    move_line_ids = fields.One2many(
        'stock.move.line', 'workorder_id', 'Moves to Track',
        domain=[('done_wo', '=', True)],
        help="Inventory moves for which you must scan a lot number at this work order")
    active_move_line_ids = fields.One2many(
        'stock.move.line', 'workorder_id',
        domain=[('done_wo', '=', False)])
    final_lot_id = fields.Many2one(
        'stock.production.lot', 'Lot/Serial Number', domain="[('product_id', '=', product_id)]",
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]})
    tracking = fields.Selection(related='production_id.product_id.tracking')
    time_ids = fields.One2many(
        'mrp.workcenter.productivity', 'workorder_id')
    is_user_working = fields.Boolean(
        'Is the Current User Working', compute='_compute_is_user_working',
        help="Technical field indicating whether the current user is working. ")
    production_messages = fields.Html('Workorder Message', compute='_compute_production_messages')

    next_work_order_id = fields.Many2one('mrp.workorder', "Next Work Order")
    scrap_ids = fields.One2many('stock.scrap', 'workorder_id')
    scrap_count = fields.Integer(compute='_compute_scrap_move_count', string='Scrap Move')
    production_date = fields.Datetime('Production Date', related='production_id.date_planned_start', store=True)
    color = fields.Integer('Color', compute='_compute_color')
    capacity = fields.Float(
        'Capacity', default=1.0,
        help="Number of pieces that can be produced in parallel.")

    @api.multi
    def name_get(self):
        return [(wo.id, "%s - %s - %s" % (wo.production_id.name, wo.product_id.name, wo.name)) for wo in self]

    @api.one
    @api.depends('production_id.product_qty', 'qty_produced')
    def _compute_is_produced(self):
        rounding = self.production_id.product_uom_id.rounding
        self.is_produced = float_compare(self.qty_produced, self.production_id.product_qty, precision_rounding=rounding) >= 0

    @api.one
    @api.depends('time_ids.duration', 'qty_produced')
    def _compute_duration(self):
        self.duration = sum(self.time_ids.mapped('duration'))
        self.duration_unit = round(self.duration / max(self.qty_produced, 1), 2)  # rounding 2 because it is a time
        if self.duration_expected:
            self.duration_percent = 100 * (self.duration_expected - self.duration) / self.duration_expected
        else:
            self.duration_percent = 0

    def _compute_is_user_working(self):
        """ Checks whether the current user is working """
        for order in self:
            if order.time_ids.filtered(lambda x: (x.user_id.id == self.env.user.id) and (not x.date_end) and (x.loss_type in ('productive', 'performance'))):
                order.is_user_working = True
            else:
                order.is_user_working = False

    @api.depends('production_id', 'workcenter_id', 'production_id.bom_id')
    def _compute_production_messages(self):
        ProductionMessage = self.env['mrp.message']
        for workorder in self:
            domain = [
                ('valid_until', '>=', fields.Date.today()),
                '|', ('workcenter_id', '=', False), ('workcenter_id', '=', workorder.workcenter_id.id),
                '|', '|', '|',
                ('product_id', '=', workorder.product_id.id),
                '&', ('product_id', '=', False), ('product_tmpl_id', '=', workorder.product_id.product_tmpl_id.id),
                ('bom_id', '=', workorder.production_id.bom_id.id),
                ('routing_id', '=', workorder.operation_id.routing_id.id)]
            messages = ProductionMessage.search(domain).mapped('message')
            workorder.production_messages = "<br/>".join(messages) or False

    @api.multi
    def _compute_scrap_move_count(self):
        data = self.env['stock.scrap'].read_group([('workorder_id', 'in', self.ids)], ['workorder_id'], ['workorder_id'])
        count_data = dict((item['workorder_id'][0], item['workorder_id_count']) for item in data)
        for workorder in self:
            workorder.scrap_count = count_data.get(workorder.id, 0)

    @api.multi
    @api.depends('date_planned_finished', 'production_id.date_planned_finished')
    def _compute_color(self):
        late_orders = self.filtered(lambda x: x.production_id.date_planned_finished and x.date_planned_finished > x.production_id.date_planned_finished)
        for order in late_orders:
            order.color = 4
        for order in (self - late_orders):
            order.color = 2

    @api.onchange('qty_producing')
    def _onchange_qty_producing(self):
        """ Update stock.move.lot records, according to the new qty currently
        produced. """
        moves = self.move_raw_ids.filtered(lambda move: move.state not in ('done', 'cancel') and move.product_id.tracking != 'none' and move.product_id.id != self.production_id.product_id.id)
        for move in moves:
            move_lots = self.active_move_line_ids.filtered(lambda move_lot: move_lot.move_id == move)
            if not move_lots:
                continue
            rounding = move.product_uom.rounding
            new_qty = float_round(move.unit_factor * self.qty_producing, precision_rounding=rounding)
            if move.product_id.tracking == 'lot':
                move_lots[0].product_qty = new_qty
                move_lots[0].qty_done = new_qty
            elif move.product_id.tracking == 'serial':
                # Create extra pseudo record
                qty_todo = float_round(new_qty - sum(move_lots.mapped('qty_done')), precision_rounding=rounding)
                if float_compare(qty_todo, 0.0, precision_rounding=rounding) > 0:
                    while float_compare(qty_todo, 0.0, precision_rounding=rounding) > 0:
                        self.active_move_line_ids += self.env['stock.move.line'].new({
                            'move_id': move.id,
                            'product_id': move.product_id.id,
                            'lot_id': False,
                            'product_uom_qty': 0.0,
                            'product_uom_id': move.product_uom.id,
                            'qty_done': min(1.0, qty_todo),
                            'workorder_id': self.id,
                            'done_wo': False,
                            'location_id': move.location_id.id,
                            'location_dest_id': move.location_dest_id.id,
                            'date': move.date,
                        })
                        qty_todo -= 1
                elif float_compare(qty_todo, 0.0, precision_rounding=rounding) < 0:
                    qty_todo = abs(qty_todo)
                    for move_lot in move_lots:
                        if float_compare(qty_todo, 0, precision_rounding=rounding) <= 0:
                            break
                        if not move_lot.lot_id and float_compare(qty_todo, move_lot.qty_done, precision_rounding=rounding) >= 0:
                            qty_todo = float_round(qty_todo - move_lot.qty_done, precision_rounding=rounding)
                            self.active_move_line_ids -= move_lot  # Difference operator
                        else:
                            #move_lot.product_qty = move_lot.product_qty - qty_todo
                            if float_compare(move_lot.qty_done - qty_todo, 0, precision_rounding=rounding) == 1:
                                move_lot.qty_done = move_lot.qty_done - qty_todo
                            else:
                                move_lot.qty_done = 0
                            qty_todo = 0

    @api.multi
    def write(self, values):
        if ('date_planned_start' in values or 'date_planned_finished' in values) and any(workorder.state == 'done' for workorder in self):
            raise UserError(_('You can not change the finished work order.'))
        return super(MrpWorkorder, self).write(values)

    def _generate_lot_ids(self):
        """ Generate stock move lines """
        self.ensure_one()
        MoveLine = self.env['stock.move.line']
        tracked_moves = self.move_raw_ids.filtered(
            lambda move: move.state not in ('done', 'cancel') and move.product_id.tracking != 'none' and move.product_id != self.production_id.product_id and move.bom_line_id)
        for move in tracked_moves:
            qty = move.unit_factor * self.qty_producing
            if move.product_id.tracking == 'serial':
                while float_compare(qty, 0.0, precision_rounding=move.product_uom.rounding) > 0:
                    MoveLine.create({
                        'move_id': move.id,
                        'product_uom_qty': 0,
                        'product_uom_id': move.product_uom.id,
                        'qty_done': min(1, qty),
                        'production_id': self.production_id.id,
                        'workorder_id': self.id,
                        'product_id': move.product_id.id,
                        'done_wo': False,
                        'location_id': move.location_id.id,
                        'location_dest_id': move.location_dest_id.id,
                    })
                    qty -= 1
            else:
                MoveLine.create({
                    'move_id': move.id,
                    'product_uom_qty': 0,
                    'product_uom_id': move.product_uom.id,
                    'qty_done': qty,
                    'product_id': move.product_id.id,
                    'production_id': self.production_id.id,
                    'workorder_id': self.id,
                    'done_wo': False,
                    'location_id': move.location_id.id,
                    'location_dest_id': move.location_dest_id.id,
                    })

    def _assign_default_final_lot_id(self):
        self.final_lot_id = self.env['stock.production.lot'].search([('use_next_on_work_order_id', '=', self.id)],
                                                                    order='create_date, id', limit=1)

    def _get_byproduct_move_line(self, by_product_move, quantity):
        return {
            'move_id': by_product_move.id,
            'product_id': by_product_move.product_id.id,
            'product_uom_qty': quantity,
            'product_uom_id': by_product_move.product_uom.id,
            'qty_done': quantity,
            'workorder_id': self.id,
            'location_id': by_product_move.location_id.id,
            'location_dest_id': by_product_move.location_dest_id.id,
        }

    @api.multi
    def record_production(self):
        self.ensure_one()
        if self.qty_producing <= 0:
            raise UserError(_('Please set the quantity you are currently producing. It should be different from zero.'))

        if (self.production_id.product_id.tracking != 'none') and not self.final_lot_id and self.move_raw_ids:
            raise UserError(_('You should provide a lot/serial number for the final product'))

        # Update quantities done on each raw material line
        # For each untracked component without any 'temporary' move lines,
        # (the new workorder tablet view allows registering consumed quantities for untracked components)
        # we assume that only the theoretical quantity was used
        for move in self.move_raw_ids:
            if move.has_tracking == 'none' and (move.state not in ('done', 'cancel')) and move.bom_line_id\
                        and move.unit_factor and not move.move_line_ids.filtered(lambda ml: not ml.done_wo):
                rounding = move.product_uom.rounding
                if self.product_id.tracking != 'none':
                    qty_to_add = float_round(self.qty_producing * move.unit_factor, precision_rounding=rounding)
                    move._generate_consumed_move_line(qty_to_add, self.final_lot_id)
                elif len(move._get_move_lines()) < 2:
                    move.quantity_done += float_round(self.qty_producing * move.unit_factor, precision_rounding=rounding)
                else:
                    move._set_quantity_done(move.quantity_done + float_round(self.qty_producing * move.unit_factor, precision_rounding=rounding))

        # Transfer quantities from temporary to final move lots or make them final
        for move_line in self.active_move_line_ids:
            # Check if move_line already exists
            if move_line.qty_done <= 0:  # rounding...
                move_line.sudo().unlink()
                continue
            if move_line.product_id.tracking != 'none' and not move_line.lot_id:
                raise UserError(_('You should provide a lot/serial number for a component'))
            # Search other move_line where it could be added:
            lots = self.move_line_ids.filtered(lambda x: (x.lot_id.id == move_line.lot_id.id) and (not x.lot_produced_id) and (not x.done_move) and (x.product_id == move_line.product_id))
            if lots:
                lots[0].qty_done += move_line.qty_done
                lots[0].lot_produced_id = self.final_lot_id.id
                move_line.sudo().unlink()
            else:
                move_line.lot_produced_id = self.final_lot_id.id
                move_line.done_wo = True

        # One a piece is produced, you can launch the next work order
        if self.next_work_order_id.state == 'pending':
            self.next_work_order_id.state = 'ready'

        self.move_line_ids.filtered(
            lambda move_line: not move_line.done_move and not move_line.lot_produced_id and move_line.qty_done > 0
        ).write({
            'lot_produced_id': self.final_lot_id.id,
            'lot_produced_qty': self.qty_producing
        })

        # If last work order, then post lots used
        # TODO: should be same as checking if for every workorder something has been done?
        if not self.next_work_order_id:
            production_move = self.production_id.move_finished_ids.filtered(
                                lambda x: (x.product_id.id == self.production_id.product_id.id) and (x.state not in ('done', 'cancel')))
            if production_move.product_id.tracking != 'none':
                move_line = production_move.move_line_ids.filtered(lambda x: x.lot_id.id == self.final_lot_id.id)
                if move_line:
                    move_line.product_uom_qty += self.qty_producing
                    move_line.qty_done += self.qty_producing
                else:
                    move_line.create({'move_id': production_move.id,
                             'product_id': production_move.product_id.id,
                             'lot_id': self.final_lot_id.id,
                             'product_uom_qty': self.qty_producing,
                             'product_uom_id': production_move.product_uom.id,
                             'qty_done': self.qty_producing,
                             'workorder_id': self.id,
                             'location_id': production_move.location_id.id,
                             'location_dest_id': production_move.location_dest_id.id,
                    })
            else:
                production_move.quantity_done += self.qty_producing

        if not self.next_work_order_id:
            for by_product_move in self.production_id.move_finished_ids.filtered(lambda x: (x.product_id.id != self.production_id.product_id.id) and (x.state not in ('done', 'cancel'))):
                if by_product_move.has_tracking != 'serial':
                    values = self._get_byproduct_move_line(by_product_move, self.qty_producing * by_product_move.unit_factor)
                    self.env['stock.move.line'].create(values)
                elif by_product_move.has_tracking == 'serial':
                    qty_todo = by_product_move.product_uom._compute_quantity(self.qty_producing * by_product_move.unit_factor, by_product_move.product_id.uom_id)
                    for i in range(0, int(float_round(qty_todo, precision_digits=0))):
                        values = self._get_byproduct_move_line(by_product_move, 1)
                        self.env['stock.move.line'].create(values)

        # Update workorder quantity produced
        self.qty_produced += self.qty_producing

        if self.final_lot_id:
            self.final_lot_id.use_next_on_work_order_id = self.next_work_order_id
            self.final_lot_id = False

        # Set a qty producing
        rounding = self.production_id.product_uom_id.rounding
        if float_compare(self.qty_produced, self.production_id.product_qty, precision_rounding=rounding) >= 0:
            self.qty_producing = 0
        elif self.production_id.product_id.tracking == 'serial':
            self._assign_default_final_lot_id()
            self.qty_producing = 1.0
            self._generate_lot_ids()
        else:
            self.qty_producing = float_round(self.production_id.product_qty - self.qty_produced, precision_rounding=rounding)
            self._generate_lot_ids()

        if self.next_work_order_id and self.production_id.product_id.tracking != 'none':
            self.next_work_order_id._assign_default_final_lot_id()

        if float_compare(self.qty_produced, self.production_id.product_qty, precision_rounding=rounding) >= 0:
            self.button_finish()
        return True

    @api.multi
    def button_start(self):
        self.ensure_one()
        # As button_start is automatically called in the new view
        if self.state in ('done', 'cancel'):
            return True

        # Need a loss in case of the real time exceeding the expected
        timeline = self.env['mrp.workcenter.productivity']
        if self.duration < self.duration_expected:
            loss_id = self.env['mrp.workcenter.productivity.loss'].search([('loss_type','=','productive')], limit=1)
            if not len(loss_id):
                raise UserError(_("You need to define at least one productivity loss in the category 'Productivity'. Create one from the Manufacturing app, menu: Configuration / Productivity Losses."))
        else:
            loss_id = self.env['mrp.workcenter.productivity.loss'].search([('loss_type','=','performance')], limit=1)
            if not len(loss_id):
                raise UserError(_("You need to define at least one productivity loss in the category 'Performance'. Create one from the Manufacturing app, menu: Configuration / Productivity Losses."))
        for workorder in self:
            if workorder.production_id.state != 'progress':
                workorder.production_id.write({
                    'state': 'progress',
                    'date_start': datetime.now(),
                })
            timeline.create({
                'workorder_id': workorder.id,
                'workcenter_id': workorder.workcenter_id.id,
                'description': _('Time Tracking: ')+self.env.user.name,
                'loss_id': loss_id[0].id,
                'date_start': datetime.now(),
                'user_id': self.env.user.id
            })
        return self.write({'state': 'progress',
                    'date_start': datetime.now(),
        })

    @api.multi
    def button_finish(self):
        self.ensure_one()
        self.end_all()
        return self.write({'state': 'done', 'date_finished': fields.Datetime.now()})

    @api.multi
    def end_previous(self, doall=False):
        """
        @param: doall:  This will close all open time lines on the open work orders when doall = True, otherwise
        only the one of the current user
        """
        # TDE CLEANME
        timeline_obj = self.env['mrp.workcenter.productivity']
        domain = [('workorder_id', 'in', self.ids), ('date_end', '=', False)]
        if not doall:
            domain.append(('user_id', '=', self.env.user.id))
        not_productive_timelines = timeline_obj.browse()
        for timeline in timeline_obj.search(domain, limit=None if doall else 1):
            wo = timeline.workorder_id
            if wo.duration_expected <= wo.duration:
                if timeline.loss_type == 'productive':
                    not_productive_timelines += timeline
                timeline.write({'date_end': fields.Datetime.now()})
            else:
                maxdate = fields.Datetime.from_string(timeline.date_start) + relativedelta(minutes=wo.duration_expected - wo.duration)
                enddate = datetime.now()
                if maxdate > enddate:
                    timeline.write({'date_end': enddate})
                else:
                    timeline.write({'date_end': maxdate})
                    not_productive_timelines += timeline.copy({'date_start': maxdate, 'date_end': enddate})
        if not_productive_timelines:
            loss_id = self.env['mrp.workcenter.productivity.loss'].search([('loss_type', '=', 'performance')], limit=1)
            if not len(loss_id):
                raise UserError(_("You need to define at least one unactive productivity loss in the category 'Performance'. Create one from the Manufacturing app, menu: Configuration / Productivity Losses."))
            not_productive_timelines.write({'loss_id': loss_id.id})
        return True

    @api.multi
    def end_all(self):
        return self.end_previous(doall=True)

    @api.multi
    def button_pending(self):
        self.end_previous()
        return True

    @api.multi
    def button_unblock(self):
        for order in self:
            order.workcenter_id.unblock()
        return True

    @api.multi
    def action_cancel(self):
        return self.write({'state': 'cancel'})

    @api.multi
    def button_done(self):
        if any([x.state in ('done', 'cancel') for x in self]):
            raise UserError(_('A Manufacturing Order is already done or cancelled!'))
        self.end_all()
        return self.write({'state': 'done',
                    'date_finished': datetime.now()})

    @api.multi
    def button_scrap(self):
        self.ensure_one()
        return {
            'name': _('Scrap'),
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'stock.scrap',
            'view_id': self.env.ref('stock.stock_scrap_form_view2').id,
            'type': 'ir.actions.act_window',
            'context': {'default_workorder_id': self.id, 'default_production_id': self.production_id.id, 'product_ids': (self.production_id.move_raw_ids.filtered(lambda x: x.state not in ('done', 'cancel')) | self.production_id.move_finished_ids.filtered(lambda x: x.state == 'done')).mapped('product_id').ids},
            # 'context': {'product_ids': self.move_raw_ids.filtered(lambda x: x.state not in ('done', 'cancel')).mapped('product_id').ids + [self.production_id.product_id.id]},
            'target': 'new',
        }

    @api.multi
    def action_see_move_scrap(self):
        self.ensure_one()
        action = self.env.ref('stock.action_stock_scrap').read()[0]
        action['domain'] = [('workorder_id', '=', self.id)]
        return action

    @api.multi
    @api.depends('qty_production', 'qty_produced')
    def _compute_qty_remaining(self):
        for wo in self:
            wo.qty_remaining = float_round(wo.qty_production - wo.qty_produced, precision_rounding=wo.production_id.product_uom_id.rounding)
コード例 #28
0
class FleetVehicleCost(models.Model):
    _name = 'fleet.vehicle.cost'
    _description = 'Cost related to a vehicle'
    _order = 'date desc, vehicle_id asc'

    name = fields.Char(related='vehicle_id.name', string='Name', store=True)
    vehicle_id = fields.Many2one('fleet.vehicle',
                                 'Vehicle',
                                 required=True,
                                 help='Vehicle concerned by this log')
    cost_subtype_id = fields.Many2one(
        'fleet.service.type',
        'Type',
        help='Cost type purchased with this cost')
    amount = fields.Float('Total Price')
    cost_type = fields.Selection([('contract', 'Contract'),
                                  ('services', 'Services'), ('fuel', 'Fuel'),
                                  ('other', 'Other')],
                                 'Category of the cost',
                                 default="other",
                                 help='For internal purpose only',
                                 required=True)
    parent_id = fields.Many2one('fleet.vehicle.cost',
                                'Parent',
                                help='Parent cost to this current cost')
    cost_ids = fields.One2many('fleet.vehicle.cost',
                               'parent_id',
                               'Included Services',
                               copy=True)
    odometer_id = fields.Many2one(
        'fleet.vehicle.odometer',
        'Odometer',
        help='Odometer measure of the vehicle at the moment of this log')
    odometer = fields.Float(
        compute="_get_odometer",
        inverse='_set_odometer',
        string='Odometer Value',
        help='Odometer measure of the vehicle at the moment of this log')
    odometer_unit = fields.Selection(related='vehicle_id.odometer_unit',
                                     string="Unit",
                                     readonly=True)
    date = fields.Date(help='Date when the cost has been executed')
    contract_id = fields.Many2one('fleet.vehicle.log.contract',
                                  'Contract',
                                  help='Contract attached to this cost')
    auto_generated = fields.Boolean('Automatically Generated', readonly=True)
    description = fields.Char("Cost Description")

    def _get_odometer(self):
        for record in self:
            if record.odometer_id:
                record.odometer = record.odometer_id.value

    def _set_odometer(self):
        for record in self:
            if not record.odometer:
                raise UserError(
                    _('Emptying the odometer value of a vehicle is not allowed.'
                      ))
            odometer = self.env['fleet.vehicle.odometer'].create({
                'value':
                record.odometer,
                'date':
                record.date or fields.Date.context_today(record),
                'vehicle_id':
                record.vehicle_id.id
            })
            self.odometer_id = odometer

    @api.model
    def create(self, data):
        # make sure that the data are consistent with values of parent and contract records given
        if 'parent_id' in data and data['parent_id']:
            parent = self.browse(data['parent_id'])
            data['vehicle_id'] = parent.vehicle_id.id
            data['date'] = parent.date
            data['cost_type'] = parent.cost_type
        if 'contract_id' in data and data['contract_id']:
            contract = self.env['fleet.vehicle.log.contract'].browse(
                data['contract_id'])
            data['vehicle_id'] = contract.vehicle_id.id
            data['cost_subtype_id'] = contract.cost_subtype_id.id
            data['cost_type'] = contract.cost_type
        if 'odometer' in data and not data['odometer']:
            # if received value for odometer is 0, then remove it from the
            # data as it would result to the creation of a
            # odometer log with 0, which is to be avoided
            del data['odometer']
        return super(FleetVehicleCost, self).create(data)
コード例 #29
0
ファイル: gamification.py プロジェクト: gecoerp/gecoerp
class Challenge(models.Model):
    _inherit = 'gamification.challenge'

    category = fields.Selection(selection_add=[('forum', 'Website / Forum')])
コード例 #30
0
ファイル: mail_channel.py プロジェクト: gecoerp/gecoerp
class MailChannel(models.Model):
    """ Chat Session
        Reprensenting a conversation between users.
        It extends the base method for anonymous usage.
    """

    _name = 'mail.channel'
    _inherit = ['mail.channel', 'rating.mixin']

    anonymous_name = fields.Char('Anonymous Name')
    create_date = fields.Datetime('Create Date', required=True)
    channel_type = fields.Selection(selection_add=[('livechat',
                                                    'Livechat Conversation')])
    livechat_channel_id = fields.Many2one('im_livechat.channel', 'Channel')

    @api.multi
    def _channel_message_notifications(self, message):
        """ When a anonymous user create a mail.channel, the operator is not notify (to avoid massive polling when
            clicking on livechat button). So when the anonymous person is sending its FIRST message, the channel header
            should be added to the notification, since the user cannot be listining to the channel.
        """
        notifications = super(MailChannel,
                              self)._channel_message_notifications(message)
        message_values_dict = notifications[0][1] if len(
            notifications) else dict(message.message_format()[0])
        for channel in self:
            # add uuid for private livechat channels to allow anonymous to listen
            if channel.channel_type == 'livechat':
                notifications.append([channel.uuid, message_values_dict])
        if not message.author_id:
            unpinned_channel_partner = self.mapped(
                'channel_last_seen_partner_ids').filtered(
                    lambda cp: not cp.is_pinned)
            if unpinned_channel_partner:
                unpinned_channel_partner.write({'is_pinned': True})
                notifications = self._channel_channel_notifications(
                    unpinned_channel_partner.mapped(
                        'partner_id').ids) + notifications
        return notifications

    @api.multi
    def channel_info(self, extra_info=False):
        """ Extends the channel header by adding the livechat operator and the 'anonymous' profile
            :rtype : list(dict)
        """
        channel_infos = super(MailChannel, self).channel_info(extra_info)
        # add the operator id
        if self.env.context.get('im_livechat_operator_partner_id'):
            partner_name = self.env['res.partner'].browse(
                self.env.context.get(
                    'im_livechat_operator_partner_id')).name_get()[0]
            for channel_info in channel_infos:
                channel_info['operator_pid'] = partner_name
        channel_infos_dict = dict((c['id'], c) for c in channel_infos)
        for channel in self:
            # add the anonymous name
            if channel.anonymous_name:
                channel_infos_dict[
                    channel.id]['anonymous_name'] = channel.anonymous_name
            # add the last message date
            if channel.channel_type == 'livechat':
                last_msg = self.env['mail.message'].search(
                    [("channel_ids", "in", [channel.id])], limit=1)
                if last_msg:
                    channel_infos_dict[
                        channel.id]['last_message_date'] = last_msg.date
        return list(channel_infos_dict.values())

    @api.model
    def channel_fetch_slot(self):
        values = super(MailChannel, self).channel_fetch_slot()
        pinned_channels = self.env['mail.channel.partner'].search([
            ('partner_id', '=', self.env.user.partner_id.id),
            ('is_pinned', '=', True)
        ]).mapped('channel_id')
        values['channel_livechat'] = self.search([
            ('channel_type', '=', 'livechat'),
            ('id', 'in', pinned_channels.ids)
        ]).channel_info()
        return values

    @api.model
    def remove_empty_livechat_sessions(self):
        hours = 1  # never remove empty session created within the last hour
        self.env.cr.execute(
            """
            SELECT id as id
            FROM mail_channel C
            WHERE NOT EXISTS (
                SELECT *
                FROM mail_message_mail_channel_rel R
                WHERE R.mail_channel_id = C.id
            ) AND C.channel_type = 'livechat' AND livechat_channel_id IS NOT NULL AND
                COALESCE(write_date, create_date, (now() at time zone 'UTC'))::timestamp
                < ((now() at time zone 'UTC') - interval %s)""",
            ("%s hours" % hours, ))
        empty_channel_ids = [item['id'] for item in self.env.cr.dictfetchall()]
        self.browse(empty_channel_ids).unlink()

    @api.model
    def get_empty_list_help(self, help):
        if help:
            return '<p>%s</p>' % (help)
        return super(MailChannel, self).get_empty_list_help(help)

    def _define_command_history(self):
        return {
            'channel_types': ['livechat'],
            'help': _('See 15 last visited pages')
        }

    def _execute_command_history(self, **kwargs):
        notification = []
        notification_values = {
            '_type': 'history_command',
        }
        notification.append([self.uuid, dict(notification_values)])
        return self.env['bus.bus'].sendmany(notification)

    def _send_history_message(self, pid, page_history):
        message_body = _('No history found')
        if page_history:
            html_links = [
                '<li><a href="%s" target="_blank">%s</a></li>' % (page, page)
                for page in page_history
            ]
            message_body = '<span class="o_mail_notification"><ul>%s</ul></span>' % (
                ''.join(html_links))
        self.env['bus.bus'].sendone(
            (self._cr.dbname, 'res.partner', pid), {
                'body': message_body,
                'channel_ids': self.ids,
                'info': 'transient_message',
            })

    # Rating Mixin

    def rating_get_parent_model_name(self, values):
        return 'im_livechat.channel'

    def rating_get_parent_id(self):
        return self.livechat_channel_id.id