class wizard_move_to_hanged_warehouse(models.TransientModel):
    _name = "wizard_move_to_hanged_warehouse"

    @api.model
    def get_stock_move_to_hanged_warehouse_lines(self):
        stock_active_ids = self._context.get('active_ids')
        stock_ids = self.env['stock.quant'].browse(stock_active_ids)

        dict = []
        for stk in stock_ids:
            for line in stk:
                res = self.env['stock.quant.items'].create({
                    'product_id': line.product_id.id,
                    'quantity': line.quantity,
                    'quantity_moved': line.quantity,
                    'location_id': line.location_id.id,
                    'lot_id': line.lot_id.id,

                })
                dict.append(res.id)

        return [(6, 0, dict)]

    # here the action to get data from the wizard showed in list view and create stock picking
    def do_transfer_selected_lines(self):
        active_record_id = self._context.get('active_ids')[0]
        active_obj_location = self.env['stock.quant'].browse(active_record_id)
        for edata in self.stk_quant_ids:
            if edata.quantity_moved == 0 or edata.quantity_moved > edata.quantity:
                raise Warning("you can not make stock picking with this quantity !")
            if edata.location_id != active_obj_location.location_id:
                raise Warning("you can't make this action by selecting lines of different locations")

        self.sequence = self.env['ir.sequence'].get('move_to_hanged_warehouse_number')
        operation = self.env['operation.operation'].search([('location_id', '=', self.location_id.id)])
        vals = {
            'name':self.location_id.name + self.sequence,
            'partner_id': self.partner_id.id,
            'picking_type_id': self.picking_type_id.id,
            'location_id': self.location_id.id,
            'location_dest_id': self.location_dest_id.id,
            'origin': self.location_id.name,
            'operation_id': operation.id,

        }
        move_lines = []
        scan_product_ids_lst = []
        data_line = []
        for component in self.stk_quant_ids:
            line = [0, 0,
                    {'product_id': component.product_id.id,
                     'product_uom': component.product_id.uom_id.id,
                     'product_uom_qty': component.quantity_moved,
                     'name': component.product_id.name
                     }]

            if self.scan_option == 'withoutscan' and (component.product_id.tracking == 'lot' or component.product_id.tracking == 'serial'):
                scan_product_ids_lst.append([0, 0, {
                        'product_id': component.product_id.id,
                        'product_uom_qty': component.quantity_moved,
                        'lot_no': component.lot_id.name,
                    }
                ])

            if component.product_id.tracking == 'lot' or component.product_id.tracking == 'serial':
                data_line.append(component.lot_id.id)
            if component.quantity != 0.0:
                move_lines.append(line)

        vals['move_lines'] = move_lines
        vals['scan_products_ids'] = scan_product_ids_lst

        if len(move_lines) == 0:
            raise Warning(str("you can not make Stock Picking with no lines"))
        res = self.env['stock.picking'].create(vals)
        res.action_confirm()

        return {
            "type": "ir.actions.act_window",
            "res_model": "stock.picking",
            "views": [[False, "form"]],
            "res_id": res.id,
            "target": "current",
        }

        # return res.action_done()

    # get default location
    @api.model
    def _get_location_name(self):
        active_record = self._context.get('active_ids')[0]
        active_obj = self.env['stock.quant'].browse(active_record)
        return active_obj.location_id

    # get default logged in user
    def _default_partner(self):
        active_record = self._context.get('active_ids')[0]
        active_obj = self.env['stock.quant'].browse(active_record)
        location_id = active_obj.location_id
        partner = self.env['res.partner'].search([('operations_location', '=', location_id.location_id.id)], limit=1)
        return partner


    # get picking type
    def _get_picking_type(self):
        active_record = self._context.get('active_ids')[0]
        active_obj = self.env['stock.quant'].browse(active_record)
        warehouse_id = active_obj.location_id.warehouse_id
        # print "picking "+str(warehouse_id.name)
        picking_type = self.env['stock.picking.type'].search(
            [('code', '=', 'internal'), ('warehouse_id', '=', warehouse_id.id), ('surgeries_supply', '=', True)])
        return picking_type

    # get_location_dest_id
    def _get_location_dest_id(self):
        active_record = self._context.get('active_ids')[0]
        active_obj = self.env['stock.quant'].browse(active_record)
        dist_warehouse = self.env['stock.warehouse'].search([('is_hanged_warehouse','=',True)])
        location = self.env['stock.location'].search([('warehouse_id','=',dist_warehouse.id),('is_operation_location','=',True),('usage','=','internal')])
        return location




    location_id = fields.Many2one('stock.location', string="Location", default=_get_location_name, readonly=True)
    location_dest_id = fields.Many2one('stock.location', string="Destination Location", default=_get_location_dest_id, required=True)
    picking_type_id = fields.Many2one('stock.picking.type', string="Picking Type", required=True,default=_get_picking_type,)
    stk_quant_ids = fields.One2many('stock.quant.items', 'wizard_id_hanged', string="Stock Quant Items", required=False,
                                    default=get_stock_move_to_hanged_warehouse_lines)
    name = fields.Char(string="Name")
    partner_id = fields.Many2one('res.partner', string="Partner",default=_default_partner)
    warehouse_id = fields.Many2one(related="location_id.warehouse_id",store=True,readonly=True,string="Warehouse")
    sequence = fields.Char(string="Request Reference", required=True, readonly=True, select=True,
                       copy=False, default='New')
    scan_option = fields.Selection([('withscan', 'With scan'), ('withoutscan', 'Without Scan')], required=True)
class ka_account_payment_export_faktur_pajak_wiz(models.TransientModel):
    _name = "ka_account.payment.export.faktur.pajak.wiz"

    text1 = fields.Char(
        'Name', default='Pilih jenis pajak, Kemudian Klik Export Pajak')
    text2 = fields.Char('Name', default='Download Link')
    state_x = fields.Selection([("choose", "Choose"), ("get", "Get")],
                               string="State X",
                               default="choose",
                               copy=False)
    export = fields.Selection([("ppn", "PPN"), ("pph", "PPh")],
                              string="Jenis Pajak",
                              default="ppn")
    data_x = fields.Binary('File', readonly=True)
    name = fields.Char('Filename', readonly=True)
    no_urut = fields.Integer(string="No urut")

    def find_between(self, s, first, last):
        try:
            start = s.index(first) + len(first)
            end = s.index(last, start)
            return s[start:end]
        except ValueError:
            return ""

    @api.multi
    def create_efaktur(self):
        context = dict(self._context or {})
        data = {}
        active_ids = self.env.context.get('active_ids')
        delimiter = ','
        data['form'] = active_ids
        user = self.env.user
        tz = pytz.timezone(user.tz) if user.tz else pytz.utc
        now = pytz.utc.localize(datetime.now()).astimezone(tz)
        download_time = datetime.strftime(now, "%d-%m-%Y_%H:%M")

        if self.export == "ppn":
            filename = "faktur_pajak_" + download_time + ".csv"

            output_head = '"FM"' + delimiter + '"KD_JENIS_TRANSAKSI"' + delimiter + '"FG_PENGGANTI"' + delimiter + '"NOMOR_FAKTUR"' + delimiter + '"MASA_PAJAK"' + delimiter + '"TAHUN_PAJAK"' + delimiter + "TANGGAL_FAKTUR" + delimiter
            output_head += '"NPWP"' + delimiter + '"NAMA"' + delimiter + '"ALAMAT_LENGKAP"' + delimiter + '"JUMLAH_DPP"' + delimiter + '"JUMLAH_PPN"' + delimiter + '"JUMLAH_PPNBM"' + delimiter + '"IS_CREDITABLE"' + '\n'

            for p in self.env['ka_account.payment'].browse(data['form']):
                if p.efaktur_url is False:
                    raise UserError(_("Please Fill E-Faktur URL"))
                else:
                    barcode = urllib2.urlopen(p.efaktur_url).read()
                    if barcode == '' or not barcode:
                        return

                    kdJenisTransaksi = self.find_between(
                        barcode, "<kdJenisTransaksi>", "</kdJenisTransaksi>")
                    fgPengganti = self.find_between(barcode, "<fgPengganti>",
                                                    "</fgPengganti>")
                    nomorFaktur = self.find_between(barcode, "<nomorFaktur>",
                                                    "</nomorFaktur>")
                    tanggalFaktur = datetime.strftime(
                        datetime.strptime(
                            self.find_between(barcode, "<tanggalFaktur>",
                                              "</tanggalFaktur>"), "%d/%m/%Y"),
                        "%Y-%m-%d")
                    npwpPenjual = self.find_between(barcode, "<npwpPenjual>",
                                                    "</npwpPenjual>")
                    namaPenjual = self.find_between(barcode, "<namaPenjual>",
                                                    "</namaPenjual>")
                    alamatPenjual = self.find_between(barcode,
                                                      "<alamatPenjual>",
                                                      "</alamatPenjual>")
                    jumlahDpp = self.find_between(barcode, "<jumlahDpp>",
                                                  "</jumlahDpp>")
                    jumlahPpn = self.find_between(barcode, "<jumlahPpn>",
                                                  "</jumlahPpn>")
                    jumlahPpnBm = self.find_between(barcode, "<jumlahPpnBm>",
                                                    "</jumlahPpnBm>")

                    output_head += '"FM"' + delimiter + '"' + kdJenisTransaksi + '"' + delimiter + '"' + fgPengganti + '"' + delimiter + '"' + nomorFaktur + '"' + delimiter
                    output_head += '"' + datetime.strftime(
                        now, "%m"
                    ) + '"' + delimiter + '"' + datetime.strftime(
                        now, "%Y"
                    ) + '"' + delimiter + '"' + tanggalFaktur + '"' + delimiter + '"' + npwpPenjual + '"' + delimiter
                    output_head += '"' + namaPenjual + '"' + delimiter + '"' + alamatPenjual + '"' + delimiter + '"' + str(
                        jumlahDpp) + '"' + delimiter + '"' + str(
                            jumlahPpn) + '"' + delimiter
                    output_head += '"' + jumlahPpnBm + '"' + delimiter + '"1"' + '\n'
        elif self.export == "pph":
            filename = "bukti_tagihan_pph_" + download_time + ".csv"

            output_head = ''

            for p in self.env['ka_account.payment'].browse(data['form']):
                if p.state != 'paid':
                    raise UserError(_("Status Tagihan Belum Dibayar!"))
                else:
                    tgl_bayar = p.date_paid[-2:] + "/" + p.date_paid[
                        5:-3] + "/" + p.date_paid[:4]
                    output_head += '"F113304"' + delimiter + '"' + p.date_paid[
                        5:
                        -3] + '"' + delimiter + '"' + p.date_paid[:4] + '"' + delimiter + '"0"' + delimiter + '"' + str(
                            p.no_npwp
                        ) + '"' + delimiter + '"' + p.partner_id.name + '"' + delimiter
                    output_head += '"' + p.partner_id.street + '"' + delimiter + '"' + str(
                        self.no_urut
                    ) + '"' + delimiter + '"' + tgl_bayar + '"' + delimiter
                    output_head += '"0"' + delimiter + '"0,25"' + delimiter + '"0"' + delimiter
                    output_head += '"0"' + delimiter + '"0,1"' + delimiter + '"0"' + delimiter
                    output_head += '"0"' + delimiter + '"0,3"' + delimiter + '"0"' + delimiter
                    output_head += '"0"' + delimiter + '"0,45"' + delimiter + '"0"' + delimiter
                    output_head += '"Farmasi"' + delimiter + '"0"' + delimiter + '"0,25"' + delimiter + '"0"' + delimiter
                    output_head += '""' + delimiter
                    output_head += '"0"' + delimiter + '"0"' + delimiter + '"0"' + delimiter
                    output_head += '""' + delimiter
                    output_head += '"0"' + delimiter + '"0"' + delimiter + '"0"' + delimiter
                    output_head += '"PERKEBUNAN"' + delimiter + '"' + str(
                        int(p.amount_dpp)
                    ) + '"' + delimiter + '"0,25"' + delimiter + '"' + str(
                        int(p.amount_pph)) + '"' + delimiter
                    output_head += '""' + delimiter
                    output_head += '"0"' + delimiter + '"0,25"' + delimiter + '"0"' + delimiter
                    output_head += '""' + delimiter
                    output_head += '"0"' + delimiter + '"0"' + delimiter + '"0"' + delimiter
                    output_head += '""' + delimiter
                    output_head += '"0"' + delimiter + '"0"' + delimiter + '"0"' + delimiter
                    output_head += '"' + str(int(
                        p.amount_dpp)) + '"' + delimiter + '"' + str(
                            int(p.amount_pph)) + '"' + '\n'
                    self.no_urut += 1

        my_utf8 = output_head.encode("utf-8")
        out = base64.b64encode(my_utf8)
        self.write({'state_x': 'get', 'data_x': out, 'name': filename})
        ir_model_data = self.env['ir.model.data']
        form_res = ir_model_data.get_object_reference(
            'ka_account', 'ka_account_payment_export_faktur_pajak_form'
        )  #module 'pti_faktur' and 'id wizard form'
        form_id = form_res and form_res[1] or False
        return {
            'name': _('Download csv'),
            'view_type': 'form',
            'view_mode': 'form',
            'res_model':
            'ka_account.payment.export.faktur.pajak.wiz',  #model wizard
            'res_id': self.id,  #id wizard
            'view_id': False,
            'views': [(form_id, 'form')],
            'type': 'ir.actions.act_window',
            'target': 'current'
        }
Пример #3
0
class SyncTask(models.Model):

    _name = "sync.task"
    _description = "Sync Task"

    project_id = fields.Many2one("sync.project", ondelete="cascade")
    name = fields.Char("Name", help="e.g. Sync Products", required=True)
    code = fields.Text("Code")
    code_check = fields.Text("Syntax check", store=False, readonly=True)
    active = fields.Boolean(default=True)
    cron_ids = fields.One2many("sync.trigger.cron", "sync_task_id", copy=True)
    automation_ids = fields.One2many(
        "sync.trigger.automation", "sync_task_id", copy=True
    )
    webhook_ids = fields.One2many("sync.trigger.webhook", "sync_task_id", copy=True)
    button_ids = fields.One2many(
        "sync.trigger.button", "sync_task_id", string="Manual Triggers", copy=True
    )
    active_cron_ids = fields.Many2many(
        "sync.trigger.cron",
        string="Enabled Crons",
        compute="_compute_active_triggers",
        context={"active_test": False},
    )
    active_automation_ids = fields.Many2many(
        "sync.trigger.automation",
        string="Enabled DB Triggers",
        compute="_compute_active_triggers",
        context={"active_test": False},
    )
    active_webhook_ids = fields.Many2many(
        "sync.trigger.webhook",
        string="Enabled Webhooks",
        compute="_compute_active_triggers",
        context={"active_test": False},
    )
    active_button_ids = fields.Many2many(
        "sync.trigger.button",
        string="Enabled Buttons",
        compute="_compute_active_triggers",
        context={"active_test": False},
    )
    job_ids = fields.One2many("sync.job", "task_id")
    job_count = fields.Integer(compute="_compute_job_count")
    log_ids = fields.One2many("ir.logging", "sync_task_id")
    log_count = fields.Integer(compute="_compute_log_count")

    @api.depends("job_ids")
    def _compute_job_count(self):
        for r in self:
            r.job_count = len(r.job_ids)

    @api.depends("log_ids")
    def _compute_log_count(self):
        for r in self:
            r.log_count = len(r.log_ids)

    @api.constrains("code")
    def _check_python_code(self):
        for r in self.sudo().filtered("code"):
            msg = test_python_expr(expr=r.code, mode="exec")
            if msg:
                raise ValidationError(msg)

    @api.onchange("code")
    def onchange_code(self):
        for r in self:
            if not r.code:
                continue
            msg = test_python_expr(expr=r.code, mode="exec")
            r.code_check = msg

    @api.depends(
        "cron_ids.active",
        "automation_ids.active",
        "webhook_ids.active",
        "button_ids.active",
    )
    def _compute_active_triggers(self):
        for r in self.with_context(active_test=False):
            r.active_cron_ids = r.with_context(active_test=True).cron_ids
            r.active_automation_ids = r.with_context(active_test=True).automation_ids
            r.active_webhook_ids = r.with_context(active_test=True).webhook_ids
            r.active_button_ids = r.with_context(active_test=True).button_ids

    def start(
        self, trigger, args=None, with_delay=False, force=False, raise_on_error=True
    ):
        self.ensure_one()
        if not force and not (self.active and self.project_id.active):
            _logger.info(
                "Triggering archived project or task: %s", trigger.trigger_name
            )
            return None

        job = self.env["sync.job"].create_trigger_job(trigger)
        run = self.with_delay().run if with_delay else self.run
        if not with_delay and self.env.context.get("new_cursor_logs") is not False:
            # log records are created via new cursor and they use job.id value for sync_job_id field
            self.env.cr.commit()  # pylint: disable=invalid-commit

        queue_job_or_result = run(
            job, trigger._sync_handler, args, raise_on_error=raise_on_error
        )
        if with_delay and not self.env.context.get("test_queue_job_no_delay"):
            job.queue_job_id = queue_job_or_result.db_record()
            return job
        else:
            return job, queue_job_or_result

    def run(self, job, function, args=None, kwargs=None, raise_on_error=True):
        log = self.project_id._get_log_function(job, function)
        try:
            eval_context = self.project_id._get_eval_context(job, log)
            code = self.code
            start_time = time.time()
            result = self._eval(code, function, args, kwargs, eval_context)
            log(
                "Executing {}: {:05.3f} sec".format(function, time.time() - start_time),
                LOG_DEBUG,
            )
            log("Job finished")
            return result, log
        except Exception:
            buff = StringIO()
            traceback.print_exc(file=buff)
            log(buff.getvalue(), LOG_CRITICAL)
            if raise_on_error:
                raise

    @api.model
    def _eval(self, code, function, args, kwargs, eval_context):
        ARGS = "EXECUTION_ARGS_"
        KWARGS = "EXECUTION_KWARGS_"
        RESULT = "EXECUTION_RESULT_"

        code += """
{RESULT} = {function}(*{ARGS}, **{KWARGS})
        """.format(
            RESULT=RESULT, function=function, ARGS=ARGS, KWARGS=KWARGS
        )

        eval_context[ARGS] = args or ()
        eval_context[KWARGS] = kwargs or {}

        safe_eval(
            code, eval_context, mode="exec", nocopy=True
        )  # nocopy allows to return RESULT
        return eval_context[RESULT]

    def name_get(self):
        if not self.env.context.get("name_with_project"):
            return super(SyncTask, self).name_get()
        result = []
        for r in self:
            name = r.project_id.name + ": " + r.name
            result.append((r.id, name))
        return result

    def unlink(self):
        self.with_context(active_test=False).mapped("cron_ids").unlink()
        self.with_context(active_test=False).mapped("automation_ids").unlink()
        self.with_context(active_test=False).mapped("webhook_ids").unlink()
        return super(SyncTask, self).unlink()
class sales_channel_instance(models.Model):
    """ For instance of Sales Channel"""
    _name = 'sales.channel.instance'

    def _get_installed_module(self):
        sel_obj = self.env['ir.module.module']
        sele_ids = sel_obj.search([('name', 'in',
                                    ['magento_2_v10', 'magento_odoo_v10', 'ebay_base_ecommerce_merge', 'amazon_odoo_v11',
                                     'woocommerce_odoo', 'virtuemart_odoo']), ('state', '=', 'installed')])
        print("**********sele_ids**************", sele_ids)
        select = []
        for s in sele_ids:
            select.append((str(s.name), s.shortdesc))
        print("#########select######################", select)
        return select

    name = fields.Char(string='Name', size=64, required=True)
    module_id = fields.Selection(_get_installed_module, string='Module', size=100)
    # image = fields.Binary(compute='_get_default_image')
    image = fields.Image(string="Image", max_width=64, max_height=64, compute='_get_default_image')

    @api.model
    def get_module_id(self, module_id):
        return {'value': {'m_id': module_id}}

    @api.model
    def _get_default_image(self):

        # if partner_type in ['other'] and parent_id:
        #     parent_image = self.browse(parent_id).image
        #     image = parent_image and parent_image.decode('base64') or None
        image_path, colorize = False, False

        if self.module_id == 'amazon_odoo_v11':
            image_path = get_module_resource('ebay_base_ecommerce_merge', 'static/images', 'amazon_logo.png')

        if self.module_id == 'ebay_base_ecommerce_merge':
            image_path = get_module_resource('ebay_base_ecommerce_merge', 'static/images', 'EBay_logo.png')

        if self.module_id == 'magento_odoo_v10':
            image_path = get_module_resource('ebay_base_ecommerce_merge', 'static/images', 'logomagento.png')

        if self.module_id == 'shopify_odoo_v10':
            image_path = get_module_resource('ebay_base_ecommerce_merge', 'static/images', 'shopify.png')

        # if image_path:
        #     with open(image_path, 'rb') as f:
        #         image = f.read()
        # if image_path:
        #     f = open(image_path, 'rb')
        #     image = f.read()
        # image=Image.open(image_path)

        # if image and colorize:
        #     image = tools.image_colorize(image)

        # self.image = tools.image_resize_image_big(image.encode('base64'))
        # self.image = base64.b64encode(image).decode('ascii')
        self.image = base64.b64encode(open(image_path, 'rb').read())

    def create_stores(self):
        """ For create store of Sales Channel """
        (instances,) = self
        shop_obj = self.env['sale.shop']
        shop_ids = shop_obj.search([('instance_id', '=', self[0].id)])
        payment_ids = self.env['account.payment.term'].search([])

        if not shop_ids:
            shop_data = {
                'sale_channel_shop': True,
                'name': instances.name + ' Shop',
                'payment_default_id': payment_ids[0].id,
                'warehouse_id': 1,
                'instance_id': self[0].id,
                'marketplace_image': instances.image,
                'order_policy': 'prepaid'
            }
            shop_id = shop_obj.create(shop_data)
        else:
            shop_id = shop_ids[0]
        return shop_id
Пример #5
0
class asset_category(models.Model):
    _description = 'Asset Tags'
    _name = 'asset.category'

    name = fields.Char('Tag', required=True, translate=True)
    asset_ids = fields.Many2many('asset.asset', id1='category_id', id2='asset_id', string='Assets')
Пример #6
0
class Challenge(models.Model):
    """Gamification challenge

    Set of predifined objectives assigned to people with rules for recurrence and
    rewards

    If 'user_ids' is defined and 'period' is different than 'one', the set will
    be assigned to the users for each period (eg: every 1st of each month if
    'monthly' is selected)
    """

    _name = 'gamification.challenge'
    _description = 'Gamification Challenge'
    _inherit = 'mail.thread'
    _order = 'end_date, start_date, name, id'

    name = fields.Char("Challenge Name", required=True, translate=True)
    description = fields.Text("Description", translate=True)
    state = fields.Selection([
            ('draft', "Draft"),
            ('inprogress', "In Progress"),
            ('done', "Done"),
        ], default='draft', copy=False,
        string="State", required=True, track_visibility='onchange')
    manager_id = fields.Many2one(
        'res.users', default=lambda self: self.env.uid,
        string="Responsible", help="The user responsible for the challenge.",)

    user_ids = fields.Many2many('res.users', 'gamification_challenge_users_rel', string="Users", help="List of users participating to the challenge")
    user_domain = fields.Char("User domain", help="Alternative to a list of users")

    period = fields.Selection([
            ('once', "Non recurring"),
            ('daily', "Daily"),
            ('weekly', "Weekly"),
            ('monthly', "Monthly"),
            ('yearly', "Yearly")
        ], default='once',
        string="Periodicity",
        help="Period of automatic goal assigment. If none is selected, should be launched manually.",
        required=True)
    start_date = fields.Date("Start Date", help="The day a new challenge will be automatically started. If no periodicity is set, will use this date as the goal start date.")
    end_date = fields.Date("End Date", help="The day a new challenge will be automatically closed. If no periodicity is set, will use this date as the goal end date.")

    invited_user_ids = fields.Many2many('res.users', 'gamification_invited_user_ids_rel', string="Suggest to users")

    line_ids = fields.One2many('gamification.challenge.line', 'challenge_id',
                                  string="Lines",
                                  help="List of goals that will be set",
                                  required=True, copy=True)

    reward_id = fields.Many2one('gamification.badge', string="For Every Succeeding User")
    reward_first_id = fields.Many2one('gamification.badge', string="For 1st user")
    reward_second_id = fields.Many2one('gamification.badge', string="For 2nd user")
    reward_third_id = fields.Many2one('gamification.badge', string="For 3rd user")
    reward_failure = fields.Boolean("Reward Bests if not Succeeded?")
    reward_realtime = fields.Boolean("Reward as soon as every goal is reached", default=True, help="With this option enabled, a user can receive a badge only once. The top 3 badges are still rewarded only at the end of the challenge.")

    visibility_mode = fields.Selection([
            ('personal', "Individual Goals"),
            ('ranking', "Leader Board (Group Ranking)"),
        ], default='personal',
        string="Display Mode", required=True)

    report_message_frequency = fields.Selection([
            ('never', "Never"),
            ('onchange', "On change"),
            ('daily', "Daily"),
            ('weekly', "Weekly"),
            ('monthly', "Monthly"),
            ('yearly', "Yearly")
        ], default='never',
        string="Report Frequency", required=True)
    report_message_group_id = fields.Many2one('mail.channel', string="Send a copy to", help="Group that will receive a copy of the report in addition to the user")
    report_template_id = fields.Many2one('mail.template', default=lambda self: self._get_report_template(), string="Report Template", required=True)
    remind_update_delay = fields.Integer("Non-updated manual goals will be reminded after", help="Never reminded if no value or zero is specified.")
    last_report_date = fields.Date("Last Report Date", default=fields.Date.today)
    next_report_date = fields.Date("Next Report Date", compute='_get_next_report_date', store=True)

    category = fields.Selection([
        ('hr', 'Human Resources / Engagement'),
        ('other', 'Settings / Gamification Tools'),
    ], string="Appears in", required=True, default='hr',
       help="Define the visibility of the challenge through menus")

    REPORT_OFFSETS = {
        'daily': timedelta(days=1),
        'weekly': timedelta(days=7),
        'monthly': relativedelta(months=1),
        'yearly': relativedelta(years=1),
    }
    @api.depends('last_report_date', 'report_message_frequency')
    def _get_next_report_date(self):
        """ Return the next report date based on the last report date and
        report period.
        """
        for challenge in self:
            last = challenge.last_report_date
            offset = self.REPORT_OFFSETS.get(challenge.report_message_frequency)

            if offset:
                challenge.next_report_date = last + offset
            else:
                challenge.next_report_date = False

    def _get_report_template(self):
        template = self.env.ref('gamification.simple_report_template', raise_if_not_found=False)

        return template.id if template else False

    @api.model
    def create(self, vals):
        """Overwrite the create method to add the user of groups"""

        if vals.get('user_domain'):
            users = self._get_challenger_users(ustr(vals.get('user_domain')))

            if not vals.get('user_ids'):
                vals['user_ids'] = []
            vals['user_ids'].extend((4, user.id) for user in users)

        return super(Challenge, self).create(vals)

    @api.multi
    def write(self, vals):
        if vals.get('user_domain'):
            users = self._get_challenger_users(ustr(vals.get('user_domain')))

            if not vals.get('user_ids'):
                vals['user_ids'] = []
            vals['user_ids'].extend((4, user.id) for user in users)

        write_res = super(Challenge, self).write(vals)

        if vals.get('report_message_frequency', 'never') != 'never':
            # _recompute_challenge_users do not set users for challenges with no reports, subscribing them now
            for challenge in self:
                challenge.message_subscribe([user.partner_id.id for user in challenge.user_ids])

        if vals.get('state') == 'inprogress':
            self._recompute_challenge_users()
            self._generate_goals_from_challenge()

        elif vals.get('state') == 'done':
            self._check_challenge_reward(force=True)

        elif vals.get('state') == 'draft':
            # resetting progress
            if self.env['gamification.goal'].search([('challenge_id', 'in', self.ids), ('state', '=', 'inprogress')], limit=1):
                raise exceptions.UserError(_("You can not reset a challenge with unfinished goals."))

        return write_res


    ##### Update #####

    @api.model # FIXME: check how cron functions are called to see if decorator necessary
    def _cron_update(self, ids=False, commit=True):
        """Daily cron check.

        - Start planned challenges (in draft and with start_date = today)
        - Create the missing goals (eg: modified the challenge to add lines)
        - Update every running challenge
        """
        # start scheduled challenges
        planned_challenges = self.search([
            ('state', '=', 'draft'),
            ('start_date', '<=', fields.Date.today())
        ])
        if planned_challenges:
            planned_challenges.write({'state': 'inprogress'})

        # close scheduled challenges
        scheduled_challenges = self.search([
            ('state', '=', 'inprogress'),
            ('end_date', '<', fields.Date.today())
        ])
        if scheduled_challenges:
            scheduled_challenges.write({'state': 'done'})

        records = self.browse(ids) if ids else self.search([('state', '=', 'inprogress')])

        # in cron mode, will do intermediate commits
        # FIXME: replace by parameter
        return records.with_context(commit_gamification=commit)._update_all()

    def _update_all(self):
        """Update the challenges and related goals

        :param list(int) ids: the ids of the challenges to update, if False will
        update only challenges in progress."""
        if not self:
            return True

        Goals = self.env['gamification.goal']

        # include yesterday goals to update the goals that just ended
        # exclude goals for users that did not connect since the last update
        yesterday = fields.Date.to_string(date.today() - timedelta(days=1))
        self.env.cr.execute("""SELECT gg.id
                        FROM gamification_goal as gg,
                             gamification_challenge as gc,
                             res_users as ru,
                             res_users_log as log
                       WHERE gg.challenge_id = gc.id
                         AND gg.user_id = ru.id
                         AND ru.id = log.create_uid
                         AND gg.write_date < log.create_date
                         AND gg.closed IS false
                         AND gc.id IN %s
                         AND (gg.state = 'inprogress'
                              OR (gg.state = 'reached'
                                  AND (gg.end_date >= %s OR gg.end_date IS NULL)))
                      GROUP BY gg.id
        """, [tuple(self.ids), yesterday])

        Goals.browse(goal_id for [goal_id] in self.env.cr.fetchall()).update_goal()

        self._recompute_challenge_users()
        self._generate_goals_from_challenge()

        for challenge in self:
            if challenge.last_report_date != fields.Date.today():
                # goals closed but still opened at the last report date
                closed_goals_to_report = Goals.search([
                    ('challenge_id', '=', challenge.id),
                    ('start_date', '>=', challenge.last_report_date),
                    ('end_date', '<=', challenge.last_report_date)
                ])

                if challenge.next_report_date and fields.Date.today() >= challenge.next_report_date:
                    challenge.report_progress()
                elif closed_goals_to_report:
                    # some goals need a final report
                    challenge.report_progress(subset_goals=closed_goals_to_report)

        self._check_challenge_reward()
        return True

    def _get_challenger_users(self, domain):
        # FIXME: literal_eval?
        user_domain = safe_eval(domain)
        return self.env['res.users'].search(user_domain)

    def _recompute_challenge_users(self):
        """Recompute the domain to add new users and remove the one no longer matching the domain"""
        for challenge in self.filtered(lambda c: c.user_domain):
            current_users = challenge.user_ids
            new_users = self._get_challenger_users(challenge.user_domain)

            if current_users != new_users:
                challenge.user_ids = new_users

        return True

    @api.multi
    def action_start(self):
        """Start a challenge"""
        return self.write({'state': 'inprogress'})

    @api.multi
    def action_check(self):
        """Check a challenge

        Create goals that haven't been created yet (eg: if added users)
        Recompute the current value for each goal related"""
        self.env['gamification.goal'].search([
            ('challenge_id', 'in', self.ids),
            ('state', '=', 'inprogress')
        ]).unlink()

        return self._update_all()

    @api.multi
    def action_report_progress(self):
        """Manual report of a goal, does not influence automatic report frequency"""
        for challenge in self:
            challenge.report_progress()
        return True

    ##### Automatic actions #####

    def _generate_goals_from_challenge(self):
        """Generate the goals for each line and user.

        If goals already exist for this line and user, the line is skipped. This
        can be called after each change in the list of users or lines.
        :param list(int) ids: the list of challenge concerned"""

        Goals = self.env['gamification.goal']
        for challenge in self:
            (start_date, end_date) = start_end_date_for_period(challenge.period, challenge.start_date, challenge.end_date)
            to_update = Goals.browse(())

            for line in challenge.line_ids:
                # there is potentially a lot of users
                # detect the ones with no goal linked to this line
                date_clause = ""
                query_params = [line.id]
                if start_date:
                    date_clause += "AND g.start_date = %s"
                    query_params.append(start_date)
                if end_date:
                    date_clause += "AND g.end_date = %s"
                    query_params.append(end_date)
            
                query = """SELECT u.id AS user_id
                             FROM res_users u
                        LEFT JOIN gamification_goal g
                               ON (u.id = g.user_id)
                            WHERE line_id = %s
                              {date_clause}
                        """.format(date_clause=date_clause)
                self.env.cr.execute(query, query_params)
                user_with_goal_ids = {it for [it] in self.env.cr._obj}

                participant_user_ids = set(challenge.user_ids.ids)
                user_squating_challenge_ids = user_with_goal_ids - participant_user_ids
                if user_squating_challenge_ids:
                    # users that used to match the challenge 
                    Goals.search([
                        ('challenge_id', '=', challenge.id),
                        ('user_id', 'in', list(user_squating_challenge_ids))
                    ]).unlink()

                values = {
                    'definition_id': line.definition_id.id,
                    'line_id': line.id,
                    'target_goal': line.target_goal,
                    'state': 'inprogress',
                }

                if start_date:
                    values['start_date'] = start_date
                if end_date:
                    values['end_date'] = end_date

                    # the goal is initialised over the limit to make sure we will compute it at least once
                    if line.condition == 'higher':
                        values['current'] = line.target_goal - 1
                    else:
                        values['current'] = line.target_goal + 1

                if challenge.remind_update_delay:
                    values['remind_update_delay'] = challenge.remind_update_delay

                for user_id in (participant_user_ids - user_with_goal_ids):
                    values['user_id'] = user_id
                    to_update |= Goals.create(values)

            to_update.update_goal()

        return True

    ##### JS utilities #####

    def _get_serialized_challenge_lines(self, user=(), restrict_goals=(), restrict_top=0):
        """Return a serialised version of the goals information if the user has not completed every goal

        :param user: user retrieving progress (False if no distinction,
                     only for ranking challenges)
        :param restrict_goals: compute only the results for this subset of
                               gamification.goal ids, if False retrieve every
                               goal of current running challenge
        :param int restrict_top: for challenge lines where visibility_mode is
                                 ``ranking``, retrieve only the best
                                 ``restrict_top`` results and itself, if 0
                                 retrieve all restrict_goal_ids has priority
                                 over restrict_top

        format list
        # if visibility_mode == 'ranking'
        {
            'name': <gamification.goal.description name>,
            'description': <gamification.goal.description description>,
            'condition': <reach condition {lower,higher}>,
            'computation_mode': <target computation {manually,count,sum,python}>,
            'monetary': <{True,False}>,
            'suffix': <value suffix>,
            'action': <{True,False}>,
            'display_mode': <{progress,boolean}>,
            'target': <challenge line target>,
            'own_goal_id': <gamification.goal id where user_id == uid>,
            'goals': [
                {
                    'id': <gamification.goal id>,
                    'rank': <user ranking>,
                    'user_id': <res.users id>,
                    'name': <res.users name>,
                    'state': <gamification.goal state {draft,inprogress,reached,failed,canceled}>,
                    'completeness': <percentage>,
                    'current': <current value>,
                }
            ]
        },
        # if visibility_mode == 'personal'
        {
            'id': <gamification.goal id>,
            'name': <gamification.goal.description name>,
            'description': <gamification.goal.description description>,
            'condition': <reach condition {lower,higher}>,
            'computation_mode': <target computation {manually,count,sum,python}>,
            'monetary': <{True,False}>,
            'suffix': <value suffix>,
            'action': <{True,False}>,
            'display_mode': <{progress,boolean}>,
            'target': <challenge line target>,
            'state': <gamification.goal state {draft,inprogress,reached,failed,canceled}>,                                
            'completeness': <percentage>,
            'current': <current value>,
        }
        """
        Goals = self.env['gamification.goal']
        (start_date, end_date) = start_end_date_for_period(self.period)

        res_lines = []
        for line in self.line_ids:
            line_data = {
                'name': line.definition_id.name,
                'description': line.definition_id.description,
                'condition': line.definition_id.condition,
                'computation_mode': line.definition_id.computation_mode,
                'monetary': line.definition_id.monetary,
                'suffix': line.definition_id.suffix,
                'action': True if line.definition_id.action_id else False,
                'display_mode': line.definition_id.display_mode,
                'target': line.target_goal,
            }
            domain = [
                ('line_id', '=', line.id),
                ('state', '!=', 'draft'),
            ]
            if restrict_goals:
                domain.append(('ids', 'in', restrict_goals.ids))
            else:
                # if no subset goals, use the dates for restriction
                if start_date:
                    domain.append(('start_date', '=', start_date))
                if end_date:
                    domain.append(('end_date', '=', end_date))

            if self.visibility_mode == 'personal':
                if not user:
                    raise exceptions.UserError(_("Retrieving progress for personal challenge without user information"))

                domain.append(('user_id', '=', user.id))

                goal = Goals.search(domain, limit=1)
                if not goal:
                    continue

                if goal.state != 'reached':
                    return []
                line_data.update(goal.read(['id', 'current', 'completeness', 'state'])[0])
                res_lines.append(line_data)
                continue

            line_data['own_goal_id'] = False,
            line_data['goals'] = []
            if line.condition=='higher':
                goals = Goals.search(domain, order="completeness desc, current desc")
            else:
                goals = Goals.search(domain, order="completeness desc, current asc")
            if not goals:
                continue

            for ranking, goal in enumerate(goals):
                if user and goal.user_id == user:
                    line_data['own_goal_id'] = goal.id
                elif restrict_top and ranking > restrict_top:
                    # not own goal and too low to be in top
                    continue

                line_data['goals'].append({
                    'id': goal.id,
                    'user_id': goal.user_id.id,
                    'name': goal.user_id.name,
                    'rank': ranking,
                    'current': goal.current,
                    'completeness': goal.completeness,
                    'state': goal.state,
                })
            if len(goals) < 3:
                # display at least the top 3 in the results
                missing = 3 - len(goals)
                for ranking, mock_goal in enumerate([{'id': False,
                                                      'user_id': False,
                                                      'name': '',
                                                      'current': 0,
                                                      'completeness': 0,
                                                      'state': False}] * missing,
                                                    start=len(goals)):
                    mock_goal['rank'] = ranking
                    line_data['goals'].append(mock_goal)

            res_lines.append(line_data)
        return res_lines

    ##### Reporting #####

    def report_progress(self, users=(), subset_goals=False):
        """Post report about the progress of the goals

        :param users: users that are concerned by the report. If False, will
                      send the report to every user concerned (goal users and
                      group that receive a copy). Only used for challenge with
                      a visibility mode set to 'personal'.
        :param subset_goals: goals to restrict the report
        """

        challenge = self

        MailTemplates = self.env['mail.template']
        if challenge.visibility_mode == 'ranking':
            lines_boards = challenge._get_serialized_challenge_lines(restrict_goals=subset_goals)

            body_html = MailTemplates.with_context(challenge_lines=lines_boards)._render_template(challenge.report_template_id.body_html, 'gamification.challenge', challenge.id)

            # send to every follower and participant of the challenge
            challenge.message_post(
                body=body_html,
                partner_ids=challenge.mapped('user_ids.partner_id.id'),
                subtype='mail.mt_comment',
                notif_layout='mail.mail_notification_light',
                )
            if challenge.report_message_group_id:
                challenge.report_message_group_id.message_post(
                    body=body_html,
                    subtype='mail.mt_comment')

        else:
            # generate individual reports
            for user in (users or challenge.user_ids):
                lines = challenge._get_serialized_challenge_lines(user, restrict_goals=subset_goals)
                if not lines:
                    continue

                body_html = MailTemplates.sudo(user).with_context(challenge_lines=lines)._render_template(
                    challenge.report_template_id.body_html,
                    'gamification.challenge',
                    challenge.id)

                # send message only to users, not on the challenge
                self.env['gamification.challenge'].message_post(
                    body=body_html,
                    partner_ids=[(4, user.partner_id.id)],
                    subtype='mail.mt_comment',
                    notif_layout='mail.mail_notification_light',
                )
                if challenge.report_message_group_id:
                    challenge.report_message_group_id.message_post(
                        body=body_html,
                        subtype='mail.mt_comment',
                        notif_layout='mail.mail_notification_light',
                    )
        return challenge.write({'last_report_date': fields.Date.today()})

    ##### Challenges #####
    @api.multi
    def accept_challenge(self):
        user = self.env.user
        sudoed = self.sudo()
        sudoed.message_post(body=_("%s has joined the challenge") % user.name)
        sudoed.write({'invited_user_ids': [(3, user.id)], 'user_ids': [(4, user.id)]})
        return sudoed._generate_goals_from_challenge()

    @api.multi
    def discard_challenge(self):
        """The user discard the suggested challenge"""
        user = self.env.user
        sudoed = self.sudo()
        sudoed.message_post(body=_("%s has refused the challenge") % user.name)
        return sudoed.write({'invited_user_ids': (3, user.id)})

    def _check_challenge_reward(self, force=False):
        """Actions for the end of a challenge

        If a reward was selected, grant it to the correct users.
        Rewards granted at:
            - the end date for a challenge with no periodicity
            - the end of a period for challenge with periodicity
            - when a challenge is manually closed
        (if no end date, a running challenge is never rewarded)
        """
        commit = self.env.context.get('commit_gamification') and self.env.cr.commit

        for challenge in self:
            (start_date, end_date) = start_end_date_for_period(challenge.period, challenge.start_date, challenge.end_date)
            yesterday = date.today() - timedelta(days=1)

            rewarded_users = self.env['res.users']
            challenge_ended = force or end_date == fields.Date.to_string(yesterday)
            if challenge.reward_id and (challenge_ended or challenge.reward_realtime):
                # not using start_date as intemportal goals have a start date but no end_date
                reached_goals = self.env['gamification.goal'].read_group([
                    ('challenge_id', '=', challenge.id),
                    ('end_date', '=', end_date),
                    ('state', '=', 'reached')
                ], fields=['user_id'], groupby=['user_id'])
                for reach_goals_user in reached_goals:
                    if reach_goals_user['user_id_count'] == len(challenge.line_ids):
                        # the user has succeeded every assigned goal
                        user = self.env['res.users'].browse(reach_goals_user['user_id'][0])
                        if challenge.reward_realtime:
                            badges = self.env['gamification.badge.user'].search_count([
                                ('challenge_id', '=', challenge.id),
                                ('badge_id', '=', challenge.reward_id.id),
                                ('user_id', '=', user.id),
                            ])
                            if badges > 0:
                                # has already recieved the badge for this challenge
                                continue
                        challenge._reward_user(user, challenge.reward_id)
                        rewarded_users |= user
                        if commit:
                            commit()

            if challenge_ended:
                # open chatter message
                message_body = _("The challenge %s is finished.") % challenge.name

                if rewarded_users:
                    user_names = rewarded_users.name_get()
                    message_body += _("<br/>Reward (badge %s) for every succeeding user was sent to %s.") % (challenge.reward_id.name, ", ".join(name for (user_id, name) in user_names))
                else:
                    message_body += _("<br/>Nobody has succeeded to reach every goal, no badge is rewarded for this challenge.")

                # reward bests
                reward_message = _("<br/> %(rank)d. %(user_name)s - %(reward_name)s")
                if challenge.reward_first_id:
                    (first_user, second_user, third_user) = challenge._get_topN_users(MAX_VISIBILITY_RANKING)
                    if first_user:
                        challenge._reward_user(first_user, challenge.reward_first_id)
                        message_body += _("<br/>Special rewards were sent to the top competing users. The ranking for this challenge is :")
                        message_body += reward_message % {
                            'rank': 1,
                            'user_name': first_user.name,
                            'reward_name': challenge.reward_first_id.name,
                        }
                    else:
                        message_body += _("Nobody reached the required conditions to receive special badges.")

                    if second_user and challenge.reward_second_id:
                        challenge._reward_user(second_user, challenge.reward_second_id)
                        message_body += reward_message % {
                            'rank': 2,
                            'user_name': second_user.name,
                            'reward_name': challenge.reward_second_id.name,
                        }
                    if third_user and challenge.reward_third_id:
                        challenge._reward_user(third_user, challenge.reward_third_id)
                        message_body += reward_message % {
                            'rank': 3,
                            'user_name': third_user.name,
                            'reward_name': challenge.reward_third_id.name,
                        }

                challenge.message_post(
                    partner_ids=[user.partner_id.id for user in challenge.user_ids],
                    body=message_body)
                if commit:
                    commit()

        return True

    def _get_topN_users(self, n):
        """Get the top N users for a defined challenge

        Ranking criterias:
            1. succeed every goal of the challenge
            2. total completeness of each goal (can be over 100)

        Only users having reached every goal of the challenge will be returned
        unless the challenge ``reward_failure`` is set, in which case any user
        may be considered.

        :returns: an iterable of exactly N records, either User objects or
                  False if there was no user for the rank. There can be no
                  False between two users (if users[k] = False then
                  users[k+1] = False
        """
        Goals = self.env['gamification.goal']
        (start_date, end_date) = start_end_date_for_period(self.period, self.start_date, self.end_date)
        challengers = []
        for user in self.user_ids:
            all_reached = True
            total_completeness = 0
            # every goal of the user for the running period
            goal_ids = Goals.search([
                ('challenge_id', '=', self.id),
                ('user_id', '=', user.id),
                ('start_date', '=', start_date),
                ('end_date', '=', end_date)
            ])
            for goal in goal_ids:
                if goal.state != 'reached':
                    all_reached = False
                if goal.definition_condition == 'higher':
                    # can be over 100
                    total_completeness += 100.0 * goal.current / goal.target_goal
                elif goal.state == 'reached':
                    # for lower goals, can not get percentage so 0 or 100
                    total_completeness += 100

            challengers.append({'user': user, 'all_reached': all_reached, 'total_completeness': total_completeness})

        challengers.sort(key=lambda k: (k['all_reached'], k['total_completeness']), reverse=True)
        if not self.reward_failure:
            # only keep the fully successful challengers at the front, could
            # probably use filter since the successful ones are at the front
            challengers = itertools.takewhile(lambda c: c['all_reached'], challengers)

        # append a tail of False, then keep the first N
        challengers = itertools.islice(
            itertools.chain(
                (c['user'] for c in challengers),
                itertools.repeat(False),
            ), 0, n
        )

        return tuple(challengers)

    def _reward_user(self, user, badge):
        """Create a badge user and send the badge to him

        :param user: the user to reward
        :param badge: the concerned badge
        """
        return self.env['gamification.badge.user'].create({
            'user_id': user.id,
            'badge_id': badge.id,
            'challenge_id': self.id
        })._send_badge()
Пример #7
0
class WorkshopVehicleLogServices(models.Model):
    _name = 'workshop.vehicle.log.services'
    _inherits = {'workshop.vehicle.cost': 'cost_id'}
    _description = 'Services for vehicles'

    @api.model
    def default_get(self, default_fields):
        res = super(WorkshopVehicleLogServices,
                    self).default_get(default_fields)
        service = self.env.ref('workshop.type_service_service_8',
                               raise_if_not_found=False)
        res.update({
            'date': fields.Date.context_today(self),
            'date_in': fields.Date.context_today(self),
            'cost_subtype_id': service and service.id or False,
            'cost_type': 'services'
        })
        return res

    name = fields.Char(string='Job Card #',
                       required=True,
                       copy=False,
                       readonly=True,
                       index=True,
                       default=lambda self: _('New'))
    task_id = fields.Many2one('project.task', 'Work Order')
    date_in = fields.Date(
        help='The date the vehicle was brought into the workshop', store=True)
    date_out = fields.Date(help='The date the vehicle left the workshop',
                           store=True)
    project_id = fields.Many2one('project.project',
                                 string='Nature Of Job',
                                 required=True,
                                 domain=[
                                     ('name', 'in',
                                      ['In Workshop Service', 'Field Service'])
                                 ])
    state = fields.Selection([
        ('pending', 'Pending'),
        ('awaiting_confirmation', 'Awaiting Confirmation'),
        ('awaiting_parts', 'Awaiting Parts'),
        ('in_progress', 'Repair In Progress'),
        ('awaiting_payment', 'Awaiting Payment'),
        ('done', 'Complete'),
    ],
                             string='Status',
                             readonly=True,
                             copy=False,
                             index=True,
                             tracking=3,
                             default='pending')
    fuel_log = fields.Selection([('empty', 'Empty'), ('half', 'Half Full'),
                                 ('full', 'Full')],
                                string='Fuel')
    # vendor_id = fields.Many2one('res.partner', 'Vendor')
    # 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 and (2) fields that aren't stored in database
    cost_amount = fields.Float(related='task_id.material_line_total_price',
                               string='Amount',
                               store=True,
                               readonly=True)
    notes = fields.Text()
    cost_id = fields.Many2one('workshop.vehicle.cost',
                              'Cost',
                              required=True,
                              ondelete='cascade')

    def action_in_progress(self):
        return self.write({'state': 'in_progress'})

    def action_done(self):
        return self.write({'state': 'done'})

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

    @api.onchange('task_id.material_line_total_price')
    def _onchange_cost_amount(self):
        self.amount = self.cost_amount

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

    @api.model
    def create(self, vals):

        seq_date = None
        if vals.get('name', _('New')) == _('New'):
            vals['name'] = self.env['ir.sequence'].next_by_code(
                'workshop.job.card.sequence',
                sequence_date=seq_date) or _('New')

        result = super(WorkshopVehicleLogServices, self).create(vals)
        return result

    def action_view_task(self):
        self.ensure_one()
        return {
            "type": "ir.actions.act_window",
            "res_model": "project.task",
            "views": [[False, "form"]],
            "res_id": self.task_id.id,
            "context": {
                "create": False,
                "show_sale": True
            },
        }
Пример #8
0
class WeatherForecastDate(models.Model):
    _name = "weather.forecast.date"
    _description = "Weather Forecast Date"
    _rec_name = "date"
    _order = "id asc"

    # This function is currently cannot work due to timeout
    # def update_weather_data(self):
    #     try:
    #         file = requests.get('https://weather.com/en-GB/weather/tenday/l/21.00,105.84')
    #         soup = BeautifulSoup(file.text, 'html.parser')
    #         list = []
    #         content = soup.find_all("div", {"data-testid": "DetailsSummary"})
    #         for items in content:
    #             dict = {}
    #             day = items.find_all("span")
    #             try:
    #                 dict["high_temp"] = int(day[0].text.split('°')[0])
    #                 dict["low_temp"] = int(day[2].text.split('°')[0])
    #                 dict["weather"] = day[3].text.split('/')[0].replace('AM ', '')
    #                 dict["rain_chance"] = int(day[4].text.split('%')[0])
    #                 dict["wind_speed"] = round(int(day[5].text.split(' ')[1]) * 1.609344, 1)
    #             except:
    #                 dict["high_temp"] = ""
    #                 dict["low_temp"] = ""
    #                 dict["weather"] = "None"
    #                 dict["rain_chance"] = ""
    #                 dict["wind_speed"] = ""
    #             list.append(dict)
    #         date = datetime.date.today()
    #         for i in range(9):
    #             list[i]['date'] = date
    #             date_info = request.env['weather.forecast.date'].sudo().search([('date', '=', date)], limit=1)
    #             if date_info:
    #                 date_info.sudo().write(list[i])
    #                 print(date_info)
    #             else:
    #                 new_date_info = request.env['weather.forecast.date'].sudo().create({'date': date})
    #                 new_date_info.sudo().write(list[i])
    #                 print(new_date_info)
    #             date += datetime.timedelta(days=1)
    #         print("Data updated")
    #     except:
    #         print("Error")

    @api.constrains('date')
    def _check_date(self):
        date = self.env['weather.forecast.date'].search([('date', '=', self.date), ('id', '!=', self.id)])
        if date:
            raise ValidationError(_('Record of this day already existed.'))

    @api.depends('weather')
    def _choose_icon(self):
        for rec in self:
            if 'Cloud' in rec.weather:
                rec.weather_icon = 'cloud'
            elif 'Storm' in rec.weather or 'storm' in rec.weather:
                rec.weather_icon = 'storm'
            elif 'Rain' in rec.weather or 'Shower' in rec.weather:
                rec.weather_icon = 'rain'
            else:
                rec.weather_icon = 'sun'

    @api.depends('date')
    def _get_day(self):
        for rec in self:
            if rec.date.weekday() == 0:
                rec.day = 'Monday'
            elif rec.date.weekday() == 1:
                rec.day = 'Tuesday'
            elif rec.date.weekday() == 2:
                rec.day = 'Wednesday'
            elif rec.date.weekday() == 3:
                rec.day = 'Thursday'
            elif rec.date.weekday() == 4:
                rec.day = 'Friday'
            elif rec.date.weekday() == 5:
                rec.day = 'Saturday'
            else:
                rec.day = 'Sunday'

    date = fields.Date(string='Date', required=True, copy=False)
    day = fields.Selection([
        ('Monday', 'Monday'),
        ('Tuesday', 'Tuesday'),
        ('Wednesday', 'Wednesday'),
        ('Thursday', 'Thursday'),
        ('Friday', 'Friday'),
        ('Saturday', 'Saturday'),
        ('Sunday', 'Sunday'),
    ], default='mon', compute='_get_day', readonly=True)
    weather_icon = fields.Selection([
        ('rain', 'Rainy'),
        ('sun', 'Sunny'),
        ('cloud', 'Cloudy'),
        ('storm', 'Storm'),
    ], default='sun', string="Weather Icon", compute='_choose_icon')
    weather = fields.Char(string='Weather')
    rain_chance = fields.Integer(string='Chance of Rain (%)')
    high_temp = fields.Integer(string='Highest Temperature(°C)')
    low_temp = fields.Integer(string='Lowest Temperature(°C)')
    wind_speed = fields.Float(string='Wind Speed (kmh)')
Пример #9
0
class EducationStudentsAttendance(models.Model):
    _name = 'education.attendance'

    name = fields.Char(string='Name', default='New')
    class_id = fields.Many2one('education.class', string='Class')
    division_id = fields.Many2one('education.class.division', string='Division', required=True)
    date = fields.Date(string='Date', default=fields.Date.today, required=True)
    attendance_line = fields.One2many('education.attendance.line', 'attendance_id', string='Attendance Line')
    attendance_created = fields.Boolean(string='Attendance Created')
    all_marked_morning = fields.Boolean(string='All students are present in the morning')
    all_marked_afternoon = fields.Boolean(string='All students are present in the afternoon')
    state = fields.Selection([('draft', 'Draft'), ('done', 'Done')], default='draft')
    company_id = fields.Many2one('res.company', string='Company',
                                 default=lambda self: self.env['res.company']._company_default_get())
    academic_year = fields.Many2one('education.academic.year', string='Academic Year',
                                    related='division_id.academic_year_id', store=True)
    faculty_id = fields.Many2one('education.faculty', string='Faculty')

    @api.model
    def create(self, vals):
        res = super(EducationStudentsAttendance, self).create(vals)
        res.class_id = res.division_id.class_id.id
        attendance_obj = self.env['education.attendance']
        already_created_attendance = attendance_obj.search(
            [('division_id', '=', res.division_id.id), ('date', '=', res.date), ('company_id', '=', res.company_id.id)])
        if len(already_created_attendance) > 1:
            raise ValidationError(
                _('Attendance register of %s is already created on "%s"', ) % (res.division_id.name, res.date))
        return res

    @api.multi
    def create_attendance_line(self):
        self.name = str(self.date)
        attendance_line_obj = self.env['education.attendance.line']
        students = self.division_id.student_ids
        if len(students) < 1:
            raise UserError(_('There are no students in this Division'))
        for student in students:
            data = {
                'name': self.name,
                'attendance_id': self.id,
                'student_id': student.id,
                'student_name': student.name,
                'class_id': self.division_id.class_id.id,
                'division_id': self.division_id.id,
                'date': self.date,
            }
            attendance_line_obj.create(data)
        self.attendance_created = True

    @api.multi
    def mark_all_present_morning(self):
        for records in self.attendance_line:
            records.present_morning = True
        self.all_marked_morning = True

    @api.multi
    def un_mark_all_present_morning(self):
        for records in self.attendance_line:
            records.present_morning = False
        self.all_marked_morning = False

    @api.multi
    def mark_all_present_afternoon(self):
        for records in self.attendance_line:
            records.present_afternoon = True
        self.all_marked_afternoon = True

    @api.multi
    def un_mark_all_present_afternoon(self):
        for records in self.attendance_line:
            records.present_afternoon = False
        self.all_marked_afternoon = False

    @api.multi
    def attendance_done(self):
        for records in self.attendance_line:
            records.state = 'done'
            if not records.present_morning and not records.present_afternoon:
                records.full_day_absent = 1
            elif not records.present_morning:
                records.half_day_absent = 1
            elif not records.present_afternoon:
                records.half_day_absent = 1
        self.state = 'done'

    @api.multi
    def set_to_draft(self):
        for records in self.attendance_line:
            records.state = 'draft'
        self.state = 'draft'
Пример #10
0
class AccountMoveLine(models.Model):
    _inherit = 'account.move.line'

    invoice_tax_line_id = fields.Many2one(
        comodel_name='account.invoice.tax',
        copy=True,
        ondelete='restrict',
    )
    payment_tax_line_id = fields.Many2one(
        comodel_name='account.payment.tax',
        copy=True,
        ondelete='restrict',
    )
    tax_invoice_manual = fields.Char(
        string='Tax Invoice Number',
        copy=True,
    )
    tax_invoice = fields.Char(
        compute='_compute_tax_invoice',
        store=True,
    )
    tax_date_manual = fields.Date(
        copy=True,
    )
    tax_date = fields.Char(
        compute='_compute_tax_invoice',
        store=True,
    )

    @api.model
    def create(self, vals):
        """ Create payment tax line for clear undue vat """
        if self._context.get('cash_basis_entry_move_line', False):
            move_line = self._context['cash_basis_entry_move_line']
            payment = self._context.get('payment')
            invoice_tax_line = move_line.invoice_tax_line_id
            payment_tax_line_id = False
            if move_line.tax_line_id.tax_exigibility == 'on_payment' and \
                    move_line.tax_line_id.type_tax_use == 'purchase':
                payment_tax = self.env['account.payment.tax'].\
                    search([('invoice_tax_line_id', '=', invoice_tax_line.id),
                            ('payment_id', '=', payment.id)])
                if not payment_tax:  # If not already created for this payment
                    currency = self.env.user.company_id.currency_id
                    payment_tax = self.env['account.payment.tax'].create({
                        'partner_id': payment.partner_id.id,
                        'invoice_tax_line_id': invoice_tax_line.id,
                        'name': invoice_tax_line.name,
                        'company_currency_id': currency.id,
                        'payment_id': payment.id,
                    })
                payment_tax_line_id = payment_tax.id
            vals.update({
                'invoice_tax_line_id': invoice_tax_line.id,
                'tax_invoice_manual': invoice_tax_line.tax_invoice_manual,
                'payment_tax_line_id': payment_tax_line_id,
            })
        res = super().create(vals)
        return res

    @api.multi
    @api.depends('tax_invoice_manual',
                 'tax_date_manual',
                 'invoice_tax_line_id',
                 'invoice_id.number',
                 'invoice_id.date_invoice')
    def _compute_tax_invoice(self):
        for ml in self.filtered('tax_line_id'):
            # For undue tax on supplier invoice
            if ml.tax_line_id.tax_exigibility == 'on_payment' and \
                    ml.tax_line_id.type_tax_use == 'purchase' and \
                    ml.invoice_id:
                ml.tax_invoice = False
                ml.tax_date = False
            else:  # other cases
                ml.tax_invoice = ml.tax_invoice_manual or \
                    ml.invoice_tax_line_id.tax_invoice or \
                    ml.invoice_id.number
                ml.tax_date = ml.tax_date_manual or \
                    ml.invoice_tax_line_id.tax_date or \
                    ml.invoice_id.date_invoice
        return True
Пример #11
0
class IrConfigParameter(models.Model):
    """Per-database storage of configuration key-value pairs."""
    _name = 'ir.config_parameter'
    _description = 'System Parameter'
    _rec_name = 'key'
    _order = 'key'

    key = fields.Char(required=True, index=True)
    value = fields.Text(required=True)

    _sql_constraints = [('key_uniq', 'unique (key)', 'Key must be unique.')]

    @mute_logger('odoo.addons.base.models.ir_config_parameter')
    def init(self, force=False):
        """
        Initializes the parameters listed in _default_parameters.
        It overrides existing parameters if force is ``True``.
        """
        for key, func in _default_parameters.items():
            # force=True skips search and always performs the 'if' body (because ids=False)
            params = self.sudo().search([('key', '=', key)])
            if force or not params:
                params.set_param(key, func())

    @api.model
    def get_param(self, key, default=False):
        """Retrieve the value for a given key.

        :param string key: The key of the parameter value to retrieve.
        :param string default: default value if parameter is missing.
        :return: The value of the parameter, or ``default`` if it does not exist.
        :rtype: string
        """
        return self._get_param(key) or default

    @api.model
    @ormcache('self.env.uid', 'self.env.su', 'key')
    def _get_param(self, key):
        params = self.search_read([('key', '=', key)],
                                  fields=['value'],
                                  limit=1)
        return params[0]['value'] if params else None

    @api.model
    def set_param(self, key, value):
        """Sets the value of a parameter.

        :param string key: The key of the parameter value to set.
        :param string value: The value to set.
        :return: the previous value of the parameter or False if it did
                 not exist.
        :rtype: string
        """
        param = self.search([('key', '=', key)])
        if param:
            old = param.value
            if value is not False and value is not None:
                if str(value) != old:
                    param.write({'value': value})
            else:
                param.unlink()
            return old
        else:
            if value is not False and value is not None:
                self.create({'key': key, 'value': value})
            return False

    @api.model_create_multi
    def create(self, vals_list):
        self.clear_caches()
        return super(IrConfigParameter, self).create(vals_list)

    @api.multi
    def write(self, vals):
        self.clear_caches()
        return super(IrConfigParameter, self).write(vals)

    @api.multi
    def unlink(self):
        self.clear_caches()
        return super(IrConfigParameter, self).unlink()
Пример #12
0
class OrderType(models.Model):
    '''new model order.type'''
    _name = 'order.type'
    _description = 'Order Type'

    name = fields.Char('Order Type')
Пример #13
0
class tup(models.Model):
    _name = 'anggaran.tup'
    name = fields.Char('Nomor', readonly=True, required=True)
    tanggal = fields.Date('Tanggal', required=True)
    tahun_id = fields.Many2one('anggaran.fiscalyear', 'Tahun')
    lampiran = fields.Integer('Lampiran')
    perihal = fields.Char('Perihal', required=True)
    kepada = fields.Text('Kepada', required=True)
    dasar_rkat = fields.Char('Dasar RKAT Nomor/Tanggal', required=True)
    jumlah = fields.Float('Jumlah', required=True)
    unit_id = fields.Many2one('anggaran.unit', 'Atas Nama', required=True)
    nomor_rek = fields.Char('Nomor Rekening')
    nama_bank = fields.Char('Nama Bank')

    pumkc_id = fields.Many2one('hr.employee', 'PUMKC')
    nip_pumkc = fields.Related('pumkc_id',
                               'otherid',
                               type='char',
                               relation='hr.employee',
                               string='NIP PUMKC',
                               store=True,
                               readonly=True)
    atasan_pumkc_id = fields.Many2one('hr.employee', 'Atasan Langsung PUMKC')
    nip_atasan_pumkc = fields.Related('atasan_pumkc_id',
                                      'otherid',
                                      type='char',
                                      relation='hr.employee',
                                      string='NIP Atasan PUMKC',
                                      store=True,
                                      readonly=True)

    state = fields.Selection(TUP_STATES,
                             'Status',
                             readonly=True,
                             required=True)
    user_id = fields.Many2one('res.users', 'Created')

    _defaults = {
        'state': TUP_STATES[0][0],
        'tanggal': lambda *a: time.strftime("%Y-%m-%d"),
        'user_id': lambda obj, cr, uid, context: uid,
        'name': lambda obj, cr, uid, context: '/',
    }

    def create(self, vals):

        if vals.get('name', '/') == '/':
            vals['name'] = self.env['ir.sequence'].get('anggaran.tup') or '/'
        new_id = super(tup, self).create(vals)
        return new_id

    def action_draft(self, cr, uid, ids, context=None):
        #set to "draft" state
        return self.write(cr,
                          uid,
                          ids, {'state': TUP_STATES[0][0]},
                          context=context)

    def action_confirm(self, cr, uid, ids, context=None):
        #set to "confirmed" state
        return self.write(cr,
                          uid,
                          ids, {'state': TUP_STATES[1][0]},
                          context=context)

    def action_reject(self, cr, uid, ids, context=None):
        #set to "reject" state
        return self.write(cr,
                          uid,
                          ids, {'state': TUP_STATES[2][0]},
                          context=context)

    def action_done(self, cr, uid, ids, context=None):
        #set to "done" state
        return self.write(cr,
                          uid,
                          ids, {'state': TUP_STATES[3][0]},
                          context=context)

    def action_create_spm(self, cr, uid, ids, context=None):
        tup = self.browse(ids[0], context)
        spm_obj = self.env["anggaran.spm"]
        data = {
            'name': '/',
            'tanggal': time.strftime("%Y-%m-%d"),
            'cara_bayar': 'up',
            'unit_id': tup.unit_id.id,
            'tahun_id': tup.tahun_id.id,
            'jumlah': tup.jumlah,
            'sisa': 0.0,
            'user_id': uid,
            'state': 'draft',
            'tup_id': tup.id
        }
        spm_id = spm_obj.create(cr, uid, data, context)
        return spm_id
Пример #14
0
class AccountMove(models.Model):
    _inherit = 'account.move'

    l10n_it_send_state = fields.Selection([
        ('new', 'New'),
        ('other', 'Other'),
        ('to_send', 'Not yet send'),
        ('sent', 'Sent, waiting for response'),
        ('invalid', 'Sent, but invalid'),
        ('delivered', 'This invoice is delivered'),
        ('delivered_accepted', 'This invoice is delivered and accepted by destinatory'),
        ('delivered_refused', 'This invoice is delivered and refused by destinatory'),
        ('delivered_expired', 'This invoice is delivered and expired (expiry of the maximum term for communication of acceptance/refusal)'),
        ('failed_delivery', 'Delivery impossible, ES certify that it has received the invoice and that the file \
                        could not be delivered to the addressee') # ok we must do nothing
    ], default='to_send', copy=False)

    l10n_it_stamp_duty = fields.Float(default=0, string="Dati Bollo", readonly=True, states={'draft': [('readonly', False)]})

    l10n_it_ddt_id = fields.Many2one('l10n_it.ddt', string='DDT', readonly=True, states={'draft': [('readonly', False)]}, copy=False)

    l10n_it_einvoice_name = fields.Char(readonly=True, copy=False)

    l10n_it_einvoice_id = fields.Many2one('ir.attachment', string="Electronic invoice", copy=False)

    def post(self):
        # OVERRIDE
        super(AccountMove, self).post()

        # Retrieve invoices to generate the xml.
        invoices_to_export = self.filtered(lambda move:
                move.company_id.country_id == self.env.ref('base.it') and
                move.is_sale_document() and
                move.l10n_it_send_state not in ['sent', 'delivered', 'delivered_accepted'])

        invoices_to_export.write({'l10n_it_send_state': 'other'})
        invoices_to_send = self.env['account.move']
        invoices_other = self.env['account.move']
        for invoice in invoices_to_export:
            invoice._check_before_xml_exporting()
            invoice.invoice_generate_xml()
            if len(invoice.commercial_partner_id.l10n_it_pa_index or '') == 6:
                invoice.message_post(
                    body=(_("Invoices for PA are not managed by Odoo, you can download the document and send it on your own."))
                )
                invoices_other += invoice
            else:
                invoices_to_send += invoice

        invoices_other.write({'l10n_it_send_state': 'other'})
        invoices_to_send.write({'l10n_it_send_state': 'to_send'})

        for invoice in invoices_to_send:
            invoice.send_pec_mail()

    def _check_before_xml_exporting(self):
        seller = self.company_id
        buyer = self.commercial_partner_id

        # <1.1.1.1>
        if not seller.country_id:
            raise UserError(_("%s must have a country") % (seller.display_name))

        # <1.1.1.2>
        if not seller.vat:
            raise UserError(_("%s must have a VAT number") % (seller.display_name))
        elif len(seller.vat) > 30:
            raise UserError(_("The maximum length for VAT number is 30. %s have a VAT number too long: %s.") % (seller.display_name, seller.vat))

        # <1.2.1.2>
        if not seller.l10n_it_codice_fiscale:
            raise UserError(_("%s must have a codice fiscale number") % (seller.display_name))

        # <1.2.1.8>
        if not seller.l10n_it_tax_system:
            raise UserError(_("The seller's company must have a tax system."))

        # <1.2.2>
        if not seller.street and not seller.street2:
            raise UserError(_("%s must have a street.") % (seller.display_name))
        if not seller.zip:
            raise UserError(_("%s must have a post code.") % (seller.display_name))
        if len(seller.zip) != 5 and seller.country_id.code == 'IT':
            raise UserError(_("%s must have a post code of length 5.") % (seller.display_name))
        if not seller.city:
            raise UserError(_("%s must have a city.") % (seller.display_name))
        if not seller.country_id:
            raise UserError(_("%s must have a country.") % (seller.display_name))

        if seller.l10n_it_has_tax_representative and not seller.l10n_it_tax_representative_partner_id.vat:
            raise UserError(_("Tax representative partner %s of %s must have a tax number.") % (seller.l10n_it_tax_representative_partner_id.display_name, seller.display_name))

        # <1.4.1>
        if not buyer.vat and not buyer.l10n_it_codice_fiscale and buyer.country_id.code == 'IT':
            raise UserError(_("The buyer, %s, or his company must have either a VAT number either a tax code (Codice Fiscale).") % (buyer.display_name))

        # <1.4.2>
        if not buyer.street and not buyer.street2:
            raise UserError(_("%s must have a street.") % (buyer.display_name))
        if not buyer.zip:
            raise UserError(_("%s must have a post code.") % (buyer.display_name))
        if len(buyer.zip) != 5 and buyer.country_id.code == 'IT':
            raise UserError(_("%s must have a post code of length 5.") % (buyer.display_name))
        if not buyer.city:
            raise UserError(_("%s must have a city.") % (buyer.display_name))
        if not buyer.country_id:
            raise UserError(_("%s must have a country.") % (buyer.display_name))

        # <2.2.1>
        for invoice_line in self.invoice_line_ids:
            if len(invoice_line.tax_ids) != 1:
                raise UserError(_("You must select one and only one tax by line."))

        for tax_line in self.line_ids.filtered(lambda line: line.tax_line_id):
            if not tax_line.tax_line_id.l10n_it_has_exoneration and tax_line.tax_line_id.amount == 0:
                raise ValidationError(_("%s has an amount of 0.0, you must indicate the kind of exoneration." % tax_line.name))

    def invoice_generate_xml(self):
        for invoice in self:
            if invoice.l10n_it_einvoice_id and invoice.l10n_it_send_state not in ['invalid', 'to_send']:
                raise UserError(_("You can't regenerate an E-Invoice when the first one is sent and there are no errors"))
            if invoice.l10n_it_einvoice_id:
                invoice.l10n_it_einvoice_id.unlink()

            a = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
            n = invoice.id
            progressive_number = ""
            while n:
                (n,m) = divmod(n,len(a))
                progressive_number = a[m] + progressive_number

            report_name = '%(country_code)s%(codice)s_%(progressive_number)s.xml' % {
                'country_code': invoice.company_id.country_id.code,
                'codice': invoice.company_id.l10n_it_codice_fiscale,
                'progressive_number': progressive_number.zfill(5),
                }
            invoice.l10n_it_einvoice_name = report_name

            data = b"<?xml version='1.0' encoding='UTF-8'?>" + invoice._export_as_xml()
            description = _('Italian invoice: %s') % invoice.move_type
            invoice.l10n_it_einvoice_id = self.env['ir.attachment'].create({
                'name': report_name,
                'res_id': invoice.id,
                'res_model': invoice._name,
                'datas': base64.encodebytes(data),
                'description': description,
                'type': 'binary',
                })

            invoice.message_post(
                body=(_("E-Invoice is generated on %s by %s") % (fields.Datetime.now(), self.env.user.display_name))
            )

    def _export_as_xml(self):
        ''' Create the xml file content.
        :return: The XML content as str.
        '''
        self.ensure_one()

        def format_date(dt):
            # Format the date in the italian standard.
            dt = dt or datetime.now()
            return dt.strftime(DEFAULT_FACTUR_ITALIAN_DATE_FORMAT)

        def format_monetary(number, currency):
            # Format the monetary values to avoid trailing decimals (e.g. 90.85000000000001).
            return float_repr(number, min(2, currency.decimal_places))

        def format_numbers(number):
            #format number to str with between 2 and 8 decimals (event if it's .00)
            number_splited = str(number).split('.')
            if len(number_splited) == 1:
                return "%.02f" % number

            cents = number_splited[1]
            if len(cents) > 8:
                return "%.08f" % number
            return float_repr(number, max(2, len(cents)))

        def format_numbers_two(number):
            #format number to str with 2 (event if it's .00)
            return "%.02f" % number

        def discount_type(discount):
            return 'SC' if discount > 0 else 'MG'

        def format_phone(number):
            if not number:
                return False
            number = number.replace(' ', '').replace('/', '').replace('.', '')
            if len(number) > 4 and len(number) < 13:
                return number
            return False

        def get_vat_number(vat):
            return vat[2:].replace(' ', '')

        def get_vat_country(vat):
            return vat[:2].upper()

        formato_trasmissione = "FPR12"
        if len(self.commercial_partner_id.l10n_it_pa_index or '1') == 6:
            formato_trasmissione = "FPA12"

        if self.move_type == 'out_invoice':
            document_type = 'TD01'
        elif self.move_type == 'out_refund':
            document_type = 'TD04'
        else:
            document_type = 'TD0X'

        pdf = self.env.ref('account.account_invoices')._render_qweb_pdf(self.id)[0]
        pdf = base64.b64encode(pdf)
        pdf_name = re.sub(r'\W+', '', self.name) + '.pdf'

        # Create file content.
        template_values = {
            'record': self,
            'format_date': format_date,
            'format_monetary': format_monetary,
            'format_numbers': format_numbers,
            'format_numbers_two': format_numbers_two,
            'format_phone': format_phone,
            'discount_type': discount_type,
            'get_vat_number': get_vat_number,
            'get_vat_country': get_vat_country,
            'abs': abs,
            'formato_trasmissione': formato_trasmissione,
            'document_type': document_type,
            'pdf': pdf,
            'pdf_name': pdf_name,
        }
        content = self.env.ref('l10n_it_edi.account_invoice_it_FatturaPA_export')._render(template_values)
        return content

    def send_pec_mail(self):
        self.ensure_one()
        allowed_state = ['to_send', 'invalid']

        if (
            not self.company_id.l10n_it_mail_pec_server_id
            or not self.company_id.l10n_it_mail_pec_server_id.active
            or not self.company_id.l10n_it_address_send_fatturapa
        ):
            self.message_post(
                body=(_("Error when sending mail with E-Invoice: Your company must have a mail PEC server and must indicate the mail PEC that will send electronic invoice."))
                )
            self.l10n_it_send_state = 'invalid'
            return

        if self.l10n_it_send_state not in allowed_state:
            raise UserError(_("%s isn't in a right state. It must be in a 'Not yet send' or 'Invalid' state.") % (self.display_name))

        message = self.env['mail.message'].create({
            'subject': _('Sending file: %s') % (self.l10n_it_einvoice_id.name),
            'body': _('Sending file: %s to ES: %s') % (self.l10n_it_einvoice_id.name, self.env.company.l10n_it_address_recipient_fatturapa),
            'author_id': self.env.user.partner_id.id,
            'email_from': self.env.company.l10n_it_address_send_fatturapa,
            'reply_to': self.env.company.l10n_it_address_send_fatturapa,
            'mail_server_id': self.env.company.l10n_it_mail_pec_server_id.id,
            'attachment_ids': [(6, 0, self.l10n_it_einvoice_id.ids)],
        })

        mail_fattura = self.env['mail.mail'].sudo().with_context(wo_bounce_return_path=True).create({
            'mail_message_id': message.id,
            'email_to': self.env.company.l10n_it_address_recipient_fatturapa,
        })
        try:
            mail_fattura.send(raise_exception=True)
            self.message_post(
                body=(_("Mail sent on %s by %s") % (fields.Datetime.now(), self.env.user.display_name))
                )
            self.l10n_it_send_state = 'sent'
        except MailDeliveryException as error:
            self.message_post(
                body=(_("Error when sending mail with E-Invoice: %s") % (error.args[0]))
                )
            self.l10n_it_send_state = 'invalid'

    def _import_xml_invoice(self, tree):
        ''' Extract invoice values from the E-Invoice xml tree passed as parameter.

        :param content: The tree of the xml file.
        :return: A dictionary containing account.invoice values to create/update it.
        '''

        invoices = self.env['account.move']
        multi = False

        # possible to have multiple invoices in the case of an invoice batch, the batch itself is repeated for every invoice of the batch
        for body_tree in tree.xpath('//FatturaElettronicaBody'):
            if multi:
                # make sure all the iterations create a new invoice record (except the first which could have already created one)
                self = self.env['account.move']
            multi = True

            elements = tree.xpath('//DatiGeneraliDocumento/TipoDocumento')
            if elements and elements[0].text and elements[0].text == 'TD01':
                self_ctx = self.with_context(default_move_type='in_invoice')
            elif elements and elements[0].text and elements[0].text == 'TD04':
                self_ctx = self.with_context(default_move_type='in_refund')
            else:
                _logger.info(_('Document type not managed: %s.') % (elements[0].text))

            # type must be present in the context to get the right behavior of the _default_journal method (account.move).
            # journal_id must be present in the context to get the right behavior of the _default_account method (account.move.line).

            elements = tree.xpath('//CessionarioCommittente//IdCodice')
            company = elements and self.env['res.company'].search([('vat', 'ilike', elements[0].text)], limit=1)
            if not company:
                elements = tree.xpath('//CessionarioCommittente//CodiceFiscale')
                company = elements and self.env['res.company'].search([('l10n_it_codice_fiscale', 'ilike', elements[0].text)], limit=1)

            if company:
                self_ctx = self_ctx.with_context(company_id=company.id)
            else:
                company = self.env.company
                if elements:
                    _logger.info(_('Company not found with codice fiscale: %s. The company\'s user is set by default.') % elements[0].text)
                else:
                    _logger.info(_('Company not found. The company\'s user is set by default.'))

            if not self.env.is_superuser():
                if self.env.company != company:
                    raise UserError(_("You can only import invoice concern your current company: %s") % self.env.company.display_name)

            # Refund type.
            # TD01 == invoice
            # TD02 == advance/down payment on invoice
            # TD03 == advance/down payment on fee
            # TD04 == credit note
            # TD05 == debit note
            # TD06 == fee
            elements = tree.xpath('//DatiGeneraliDocumento/TipoDocumento')
            if elements and elements[0].text and elements[0].text == 'TD01':
                type = 'in_invoice'
            elif elements and elements[0].text and elements[0].text == 'TD04':
                type = 'in_refund'
            # self could be a single record (editing) or be empty (new).
            with Form(self.with_context(default_move_type=type)) as invoice_form:
                message_to_log = []

                # Partner (first step to avoid warning 'Warning! You must first select a partner.'). <1.2>
                elements = tree.xpath('//CedentePrestatore//IdCodice')
                partner = elements and self.env['res.partner'].search(['&', ('vat', 'ilike', elements[0].text), '|', ('company_id', '=', company.id), ('company_id', '=', False)], limit=1)
                if not partner:
                    elements = tree.xpath('//CedentePrestatore//CodiceFiscale')
                    partner = elements and self.env['res.partner'].search(['&', ('l10n_it_codice_fiscale', '=', elements[0].text), '|', ('company_id', '=', company.id), ('company_id', '=', False)], limit=1)
                if not partner:
                    elements = tree.xpath('//DatiTrasmissione//Email')
                    partner = elements and self.env['res.partner'].search(['&', '|', ('email', '=', elements[0].text), ('l10n_it_pec_email', '=', elements[0].text), '|', ('company_id', '=', company.id), ('company_id', '=', False)], limit=1)
                if partner:
                    invoice_form.partner_id = partner
                else:
                    message_to_log.append("%s<br/>%s" % (
                        _("Vendor not found, useful informations from XML file:"),
                        self._compose_info_message(
                            tree, './/CedentePrestatore')))

                # Numbering attributed by the transmitter. <1.1.2>
                elements = tree.xpath('//ProgressivoInvio')
                if elements:
                    invoice_form.payment_reference = elements[0].text

                elements = body_tree.xpath('.//DatiGeneraliDocumento//Numero')
                if elements:
                    invoice_form.ref = elements[0].text

                # Currency. <2.1.1.2>
                elements = body_tree.xpath('.//DatiGeneraliDocumento/Divisa')
                if elements:
                    currency_str = elements[0].text
                    currency = self.env.ref('base.%s' % currency_str.upper(), raise_if_not_found=False)
                    if currency != self.env.company.currency_id and currency.active:
                        invoice_form.currency_id = currency

                # Date. <2.1.1.3>
                elements = body_tree.xpath('.//DatiGeneraliDocumento/Data')
                if elements:
                    date_str = elements[0].text
                    date_obj = datetime.strptime(date_str, DEFAULT_FACTUR_ITALIAN_DATE_FORMAT)
                    invoice_form.invoice_date = date_obj.strftime(DEFAULT_FACTUR_ITALIAN_DATE_FORMAT)

                #  Dati Bollo. <2.1.1.6>
                elements = body_tree.xpath('.//DatiGeneraliDocumento/DatiBollo/ImportoBollo')
                if elements:
                    invoice_form.l10n_it_stamp_duty = float(elements[0].text)

                # List of all amount discount (will be add after all article to avoid to have a negative sum)
                discount_list = []
                percentage_global_discount = 1.0

                # Global discount. <2.1.1.8>
                discount_elements = body_tree.xpath('.//DatiGeneraliDocumento/ScontoMaggiorazione')
                total_discount_amount = 0.0
                if discount_elements:
                    for discount_element in discount_elements:
                        discount_line = discount_element.xpath('.//Tipo')
                        discount_sign = -1
                        if discount_line and discount_line[0].text == 'SC':
                            discount_sign = 1
                        discount_percentage = discount_element.xpath('.//Percentuale')
                        if discount_percentage and discount_percentage[0].text:
                            percentage_global_discount *= 1 - float(discount_percentage[0].text)/100 * discount_sign

                        discount_amount_text = discount_element.xpath('.//Importo')
                        if discount_amount_text and discount_amount_text[0].text:
                            discount_amount = float(discount_amount_text[0].text) * discount_sign * -1
                            discount = {}
                            discount["seq"] = 0

                            if discount_amount < 0:
                                discount["name"] = _('GLOBAL DISCOUNT')
                            else:
                                discount["name"] = _('GLOBAL EXTRA CHARGE')
                            discount["amount"] = discount_amount
                            discount["tax"] = []
                            discount_list.append(discount)

                # Comment. <2.1.1.11>
                elements = body_tree.xpath('.//DatiGeneraliDocumento//Causale')
                for element in elements:
                    invoice_form.narration = '%s%s\n' % (invoice_form.narration or '', element.text)

                # Informations relative to the purchase order, the contract, the agreement,
                # the reception phase or invoices previously transmitted
                # <2.1.2> - <2.1.6>
                for document_type in ['DatiOrdineAcquisto', 'DatiContratto', 'DatiConvenzione', 'DatiRicezione', 'DatiFattureCollegate']:
                    elements = body_tree.xpath('.//DatiGenerali/' + document_type)
                    if elements:
                        for element in elements:
                            message_to_log.append("%s %s<br/>%s" % (document_type, _("from XML file:"),
                            self._compose_info_message(element, '.')))

                #  Dati DDT. <2.1.8>
                elements = body_tree.xpath('.//DatiGenerali/DatiDDT')
                if elements:
                    message_to_log.append("%s<br/>%s" % (
                        _("Transport informations from XML file:"),
                        self._compose_info_message(body_tree, './/DatiGenerali/DatiDDT')))

                # Due date. <2.4.2.5>
                elements = body_tree.xpath('.//DatiPagamento/DettaglioPagamento/DataScadenzaPagamento')
                if elements:
                    date_str = elements[0].text
                    date_obj = datetime.strptime(date_str, DEFAULT_FACTUR_ITALIAN_DATE_FORMAT)
                    invoice_form.invoice_date_due = fields.Date.to_string(date_obj)

                # Total amount. <2.4.2.6>
                elements = body_tree.xpath('.//ImportoPagamento')
                amount_total_import = 0
                for element in elements:
                    amount_total_import += float(element.text)
                if amount_total_import:
                    message_to_log.append(_("Total amount from the XML File: %s") % (
                        amount_total_import))

                # Bank account. <2.4.2.13>
                if invoice_form.type not in ('out_invoice', 'in_refund'):
                    elements = body_tree.xpath('.//DatiPagamento/DettaglioPagamento/IBAN')
                    if elements:
                        if invoice_form.partner_id and invoice_form.partner_id.commercial_partner_id:
                            bank = self.env['res.partner.bank'].search([
                                ('acc_number', '=', elements[0].text),
                                ('partner_id.id', '=', invoice_form.partner_id.commercial_partner_id.id)
                                ])
                        else:
                            bank = self.env['res.partner.bank'].search([('acc_number', '=', elements[0].text)])
                        if bank:
                            invoice_form.partner_bank_id = bank
                        else:
                            message_to_log.append("%s<br/>%s" % (
                                _("Bank account not found, useful informations from XML file:"),
                                self._compose_multi_info_message(
                                    body_tree, ['.//DatiPagamento//Beneficiario',
                                        './/DatiPagamento//IstitutoFinanziario',
                                        './/DatiPagamento//IBAN',
                                        './/DatiPagamento//ABI',
                                        './/DatiPagamento//CAB',
                                        './/DatiPagamento//BIC',
                                        './/DatiPagamento//ModalitaPagamento'])))
                    else:
                        elements = body_tree.xpath('.//DatiPagamento/DettaglioPagamento')
                        if elements:
                            message_to_log.append("%s<br/>%s" % (
                                _("Bank account not found, useful informations from XML file:"),
                                self._compose_info_message(body_tree, './/DatiPagamento')))

                # Invoice lines. <2.2.1>
                elements = body_tree.xpath('.//DettaglioLinee')
                if elements:
                    for element in elements:
                        with invoice_form.invoice_line_ids.new() as invoice_line_form:

                            # Sequence.
                            line_elements = element.xpath('.//NumeroLinea')
                            if line_elements:
                                invoice_line_form.sequence = int(line_elements[0].text) * 2

                            # Product.
                            line_elements = element.xpath('.//Descrizione')
                            if line_elements:
                                invoice_line_form.name = " ".join(line_elements[0].text.split())

                            elements_code = element.xpath('.//CodiceArticolo')
                            if elements_code:
                                for element_code in elements_code:
                                    type_code = element_code.xpath('.//CodiceTipo')[0]
                                    code = element_code.xpath('.//CodiceValore')[0]
                                    if type_code.text == 'EAN':
                                        product = self.env['product.product'].search([('barcode', '=', code.text)])
                                        if product:
                                            invoice_line_form.product_id = product
                                            break
                                    if partner:
                                        product_supplier = self.env['product.supplierinfo'].search([('name', '=', partner.id), ('product_code', '=', code.text)])
                                        if product_supplier and product_supplier.product_id:
                                            invoice_line_form.product_id = product_supplier.product_id
                                            break
                                if not invoice_line_form.product_id:
                                    for element_code in elements_code:
                                        code = element_code.xpath('.//CodiceValore')[0]
                                        product = self.env['product.product'].search([('default_code', '=', code.text)])
                                        if product:
                                            invoice_line_form.product_id = product
                                            break

                            # Price Unit.
                            line_elements = element.xpath('.//PrezzoUnitario')
                            if line_elements:
                                invoice_line_form.price_unit = float(line_elements[0].text)

                            # Quantity.
                            line_elements = element.xpath('.//Quantita')
                            if line_elements:
                                invoice_line_form.quantity = float(line_elements[0].text)
                            else:
                                invoice_line_form.quantity = 1

                            # Taxes
                            tax_element = element.xpath('.//AliquotaIVA')
                            natura_element = element.xpath('.//Natura')
                            invoice_line_form.tax_ids.clear()
                            if tax_element and tax_element[0].text:
                                percentage = float(tax_element[0].text)
                                if natura_element and natura_element[0].text:
                                    l10n_it_kind_exoneration = natura_element[0].text
                                    tax = self.env['account.tax'].search([
                                        ('company_id', '=', invoice_form.company_id.id),
                                        ('amount_type', '=', 'percent'),
                                        ('type_tax_use', '=', 'purchase'),
                                        ('amount', '=', percentage),
                                        ('l10n_it_kind_exoneration', '=', l10n_it_kind_exoneration),
                                    ], limit=1)
                                else:
                                    tax = self.env['account.tax'].search([
                                        ('company_id', '=', invoice_form.company_id.id),
                                        ('amount_type', '=', 'percent'),
                                        ('type_tax_use', '=', 'purchase'),
                                        ('amount', '=', percentage),
                                    ], limit=1)
                                    l10n_it_kind_exoneration = ''

                                if tax:
                                    invoice_line_form.tax_ids.add(tax)
                                else:
                                    if l10n_it_kind_exoneration:
                                        message_to_log.append(_("Tax not found with percentage: %s and exoneration %s for the article: %s") % (
                                            percentage,
                                            l10n_it_kind_exoneration,
                                            invoice_line_form.name))
                                    else:
                                        message_to_log.append(_("Tax not found with percentage: %s for the article: %s") % (
                                            percentage,
                                            invoice_line_form.name))

                            # Discount in cascade mode.
                            # if 3 discounts : -10% -50€ -20%
                            # the result must be : (((price -10%)-50€) -20%)
                            # Generic form : (((price -P1%)-A1€) -P2%)
                            # It will be split in two parts: fix amount and pourcent amount
                            # example: (((((price - A1€) -P2%) -A3€) -A4€) -P5€)
                            # pourcent: 1-(1-P2)*(1-P5)
                            # fix amount: A1*(1-P2)*(1-P5)+A3*(1-P5)+A4*(1-P5) (we must take account of all
                            # percentage present after the fix amount)
                            line_elements = element.xpath('.//ScontoMaggiorazione')
                            total_discount_amount = 0.0
                            total_discount_percentage = percentage_global_discount
                            if line_elements:
                                for line_element in line_elements:
                                    discount_line = line_element.xpath('.//Tipo')
                                    discount_sign = -1
                                    if discount_line and discount_line[0].text == 'SC':
                                        discount_sign = 1
                                    discount_percentage = line_element.xpath('.//Percentuale')
                                    if discount_percentage and discount_percentage[0].text:
                                        pourcentage_actual = 1 - float(discount_percentage[0].text)/100 * discount_sign
                                        total_discount_percentage *= pourcentage_actual
                                        total_discount_amount *= pourcentage_actual

                                    discount_amount = line_element.xpath('.//Importo')
                                    if discount_amount and discount_amount[0].text:
                                        total_discount_amount += float(discount_amount[0].text) * discount_sign * -1

                                # Save amount discount.
                                if total_discount_amount != 0:
                                    discount = {}
                                    discount["seq"] = invoice_line_form.sequence + 1

                                    if total_discount_amount < 0:
                                        discount["name"] = _('DISCOUNT: ') + invoice_line_form.name
                                    else:
                                        discount["name"] = _('EXTRA CHARGE: ') + invoice_line_form.name
                                    discount["amount"] = total_discount_amount
                                    discount["tax"] = []
                                    for tax in invoice_line_form.tax_ids:
                                        discount["tax"].append(tax)
                                    discount_list.append(discount)
                            invoice_line_form.discount = (1 - total_discount_percentage) * 100

                # Apply amount discount.
                for discount in discount_list:
                    with invoice_form.invoice_line_ids.new() as invoice_line_form_discount:
                        invoice_line_form_discount.tax_ids.clear()
                        invoice_line_form_discount.sequence = discount["seq"]
                        invoice_line_form_discount.name = discount["name"]
                        invoice_line_form_discount.price_unit = discount["amount"]

            new_invoice = invoice_form.save()
            new_invoice.l10n_it_send_state = "other"

            elements = body_tree.xpath('.//Allegati')
            if elements:
                for element in elements:
                    name_attachment = element.xpath('.//NomeAttachment')[0].text
                    attachment_64 = str.encode(element.xpath('.//Attachment')[0].text)
                    attachment_64 = self.env['ir.attachment'].create({
                        'name': name_attachment,
                        'datas': attachment_64,
                        'type': 'binary',
                    })

                    # default_res_id is had to context to avoid facturx to import his content
                    # no_new_invoice to prevent from looping on the message_post that would create a new invoice without it
                    new_invoice.with_context(no_new_invoice=True, default_res_id=new_invoice.id).message_post(
                        body=(_("Attachment from XML")),
                        attachment_ids=[attachment_64.id]
                    )

            for message in message_to_log:
                new_invoice.message_post(body=message)

            invoices += new_invoice
        return invoices

    def _compose_info_message(self, tree, element_tags):
        output_str = ""
        elements = tree.xpath(element_tags)
        for element in elements:
            output_str += "<ul>"
            for line in element.iter():
                if line.text:
                    text = " ".join(line.text.split())
                    if text:
                        output_str += "<li>%s: %s</li>" % (line.tag, text)
            output_str += "</ul>"
        return output_str

    def _compose_multi_info_message(self, tree, element_tags):
        output_str = "<ul>"

        for element_tag in element_tags:
            elements = tree.xpath(element_tag)
            if not elements:
                continue
            for element in elements:
                text = " ".join(element.text.split())
                if text:
                    output_str += "<li>%s: %s</li>" % (element.tag, text)
        return output_str + "</ul>"

    @api.model
    def _get_xml_decoders(self):
        # Override
        ubl_decoders = [('Italy', self._detect_italy_edi, self._import_xml_invoice)]
        return super(AccountMove, self)._get_xml_decoders() + ubl_decoders

    @api.model
    def _detect_italy_edi(self, tree, file_name):
        # Quick check the file name looks like the one of an Italian EDI XML.
        flag = re.search("([A-Z]{2}[A-Za-z0-9]{2,28}_[A-Za-z0-9]{0,5}.(xml.p7m|xml))", file_name)
        error = None

        return {'flag': flag, 'error': error}
Пример #15
0
class MonthPlan(models.Model):
    '''
    月计划, 用计划计算的时候才将年计划的数据拿出来,然后更新. 月计划重新计算规则,
    找到最后执行的计划,之后的以此作为历史记录,然后进行偏移。
    '''
    _name = 'metro_park_maintenance.month_plan'
    _description = '月计划,月计划中添加里程修'
    _rec_name = 'plan_name'
    _track_log = True
    _order = "year desc, month desc"

    year_plan = fields.Many2one(
        string="所属年计划",
        comodel_name="metro_park_maintenance.year_plan",
        ondelete="restrict",
        help="必需要月计划删除了以后年计划才能删除,"
        "防止不小心删除年计划把所有的数据都删除了")

    year = fields.Integer(string="年")
    month = fields.Integer(string="月")

    start_date = fields.Date(string="开始时间",
                             compute="compute_date",
                             store=True,
                             help='这个字段用于搜索')

    end_date = fields.Date(string="结束时间",
                           compute="compute_date",
                           store=True,
                           help='这个字段用于搜索')
    pms_work_class_info = fields.Many2one(comodel_name='pms.department',
                                          string='工班')

    def get_default_sequence(self):
        '''
        取得默认的序号
        :return:
        '''
        return self.env['ir.sequence'].next_by_code('month.plan.number')

    plan_no = fields.Char(string='计划编号')
    plan_name = fields.Char(string='计划名称')

    state = fields.Selection(string='状态',
                             selection=[('draft', '草稿'), ('published', '发布')],
                             default='draft')
    operation_buttons = fields.Char(string='操作')
    plan_datas = fields.Many2many(
        string="月计划数据",
        comodel_name="metro_park_maintenance.plan_data",
        relation="month_plan_and_plan_data_rel",
        column1="plan_id",
        column2="data_id")
    remark = fields.Char(string='备注')
    active = fields.Boolean(default=True)

    _sql_constraints = [('plan_no_unique', 'UNIQUE(plan_no)', "计划编号不能重复")]

    @api.one
    @api.constrains('plan_name')
    def _check_plan_name(self):
        '''
        检查年计划名称
        :return:
        '''
        records = self.search([('plan_name', '=', self.plan_name),
                               ('id', '!=', self.id)])
        if records:
            raise exceptions.ValidationError("年计划名称重复,请选用其它名称!")

    # 这里实际上显示年月用
    @api.one
    @api.depends('year', 'month')
    def compute_date(self):
        '''
        计算日期
        :return:
        '''
        if self.month and self.year:
            tmp_date = pendulum.Date(int(self.year), int(self.month), 1)
            self.start_date = tmp_date.format('YYYY-MM-DD')
            days = tmp_date.days_in_month
            end_date = tmp_date.add(days=days - 1)
            self.end_date = end_date.format('YYYY-MM-DD')

    @api.multi
    def publish_plan(self):
        '''
        发布计划
        '''
        self.ensure_one()
        self.state = 'published'

        # 相关的数据设置为published
        rule_infos = self.env["metro_park_maintenance.rule_info"] \
            .search([("month", "=", self.month),
                     ("year", "=", self.year),
                     ("data_source", "=", "month")])
        for info in rule_infos:
            if not info.work_class and not info.pms_work_class:
                pass
                # raise exceptions.Warning("没有安排工班, 无法发布!")

        rule_infos.write({"state": "published"})

        self.env['metro_park_maintenance.plan_log'] \
            .sudo() \
            .add_plan_log('{year}年{month},月计划发布成功!'
                          .format(year=self.year, month=self.month))

        config = self.env['metro_park_base.system_config'].get_configs()
        use_pms_maintaince = config.get('start_pms', False)

        try:
            if use_pms_maintaince == 'yes':
                self.env['mdias_pms_interface'].sudo(
                ).year_month_week_day_plan(self, 'M', '1')
        except Exception as error:
            _logger.error('PMS接口获取失败{error}'.format(error=error))

    @api.multi
    def reback_plan(self):
        '''
        撤回计划
        :return:
        '''
        year = self.year
        month = self.month
        date_start = pendulum.Date(year, month, 1)
        date_start_str = date_start.format('YYYY-MM-DD')
        date_end_str = pendulum.Date(year, month, date_start.days_in_month) \
            .format('YYYY-MM-DD')
        # 查询当前是否已经有发布了的周计划
        week_plans = self.env['metro_park_maintenance.week_plan'] \
            .search(['|',
                     '&',
                     ('start_date', '>=', date_start_str),
                     ('start_date', '<=', date_end_str),
                     '&',
                     ('end_date', '>=', date_start_str),
                     ('end_date', '<=', date_end_str),
                     ('state', '=', 'published')])
        if len(week_plans) > 0:
            raise exceptions.ValidationError('当前月份有周计划已经发布,无法撤回,请先撤回相应的周计划!')

        self.state = 'draft'
        try:
            config = self.env['metro_park_base.system_config'].get_configs()
            use_pms_maintaince = config.get('start_pms', False)
        except Exception as e:
            _logger.error('PMS基础信息配置错误{error}'.format(error=e))
        try:
            if use_pms_maintaince == 'yes':
                self.env['mdias_pms_interface'].sudo(
                ).year_month_week_day_plan(self, 'M', '3')
        except Exception as error:
            _logger.error('PMS接口获取失败{error}'.format(error=error))

    @api.multi
    def view_month_plan_action(self):
        '''
        取得月计划
        :return:
        '''
        return {
            "type": "ir.actions.client",
            "name": "{year}_{month}".format(year=self.year, month=self.month),
            "tag": "month_month_plan_editor",
            "context": {
                "month_plan_id": self.id
            }
        }

    @api.multi
    def view_month_plan_works(self):
        '''
        查看生产作业大表
        :return:
        '''
        return {
            "type": "ir.actions.client",
            "name": "生产作业{year}_{month}".format(year=self.year,
                                                month=self.month),
            "tag": "month_plan_works",
            "context": {
                "month_plan_id": self.id
            }
        }

    @api.multi
    def view_month_plan_works_import(self):
        form_id = self.env.ref(
            "metro_park_maintenance.view_month_plan_works_import").id

        return {
            'type': 'ir.actions.act_window',
            'views': [[form_id, 'form']],
            'view_mode': 'form',
            'res_model': "metro_park_maintenance.month_plan_import_wizard",
            'target': 'new',
        }

    @api.multi
    def month_plan_export(self):
        """
        导出月计划
        :return:
        """
        return {
            'name':
            '月计划下载',
            'type':
            'ir.actions.act_url',
            'url':
            '/export_month_plan/{month_plan_id}'.format(month_plan_id=self.id)
        }

    @api.multi
    def unlink(self):
        '''
        必需要周计划删除以后才能删除月计划
        :return:
        '''
        for record in self:
            start_date = record.start_date
            end_date = record.end_date
            records = self.env["metro_park_maintenance.week_plan"] \
                .search(['|',
                         '&',
                         ('start_date', '>=', start_date),
                         ('start_date', '<=', end_date),
                         '&',
                         ('end_date', '>=', start_date),
                         ('end_date', '<=', end_date)])
            if len(records) > 0:
                raise exceptions.ValidationError(
                    "{month}还有相应的计划没有删除".format(month=record.month))

        for record in self:
            # 先删除具体信息
            records = self.env["metro_park_maintenance.rule_info"] \
                .search([('date', '>=', record.start_date),
                         ('date', '<=', record.end_date),
                         ('data_source', '=', 'month')])
            records.write({"active": False})

            # 对应用很多设备, 只有具体信息删除完了以后才删除计划数据
            records = self.env["metro_park_maintenance.plan_data"] \
                .search([("date", '>=', record.start_date), ("date", "<=", record.end_date)])
            for tmp_record in records:
                if tmp_record.rule_infos:
                    tmp_record.write({"active": False})

    @api.multi
    def unlink(self):
        '''
        删除月计划, 改成软删除
        :return:
        '''
        for record in self:
            # 删除月计划数据。
            records = self.env["metro_park_maintenance.rule_info"] \
                .search([("plan_id", '=', 'metro_park_maintenance.month_plan, {plan_id}'
                          .format(plan_id=record.id))])
            records.write({"active": False})
        self.write({"active": False})

    @api.multi
    def clear_month_info(self):
        '''
        删除当月的月计划产生的数据
        :return:
        '''
        for record in self:
            # 删除月计划数据。
            records = self.env["metro_park_maintenance.rule_info"] \
                .search([("plan_id", '=', 'metro_park_maintenance.month_plan, {plan_id}'
                          .format(plan_id=record.id))])
            records.write({"active": False})

    @api.multi
    def import_month_works(self):
        '''
        导入生产作业
        :return:
        '''
        return {
            "type":
            "ir.actions.act_window",
            "res_model":
            "metro_park_maintenance.import_month_works",
            'view_mode':
            'form',
            'target':
            'new',
            'context': {
                "month_plan_id": self.id,
            },
            "views": [[
                self.env.ref(
                    'metro_park_maintenance.import_month_works_form').id,
                "form"
            ]]
        }

    @api.multi
    def re_compute_plan(self):
        '''
        校正月计划,如果历史记划没有执行则进行校正, 扣车等要自动移动检修
        :return:
        '''
        rule_infos = self.env["metro_park_maintenance.rule_info"]\
            .search([[('year', '=', self.year),
                      ('month', '=', self.month), ('rule_type', '=', self.month)]])
        return rule_infos

    @api.multi
    def get_plan_work_class_data(self):
        '''
        安排工班, 工班选择按照轮换的方式,要求任务均衡,
        :return:
        '''

        department_property_work_class = \
            self.env.ref("metro_park_base.department_property_balance_work_class").id

        # 取得有均衡修工班属性的工班
        work_classes = self.env["funenc.wechat.department"].search([
            ("properties", "in", [department_property_work_class])
        ])
        work_class_count = len(work_classes)

        # 取得所有的任务, 由于数据复制了一份,所以这里还是要限制计划id
        works = self.env["metro_park_maintenance.rule_info"].search(
            [("rule.target_plan_type", "in", ["year", "month"]),
             ('date', '>=', str(self.start_date)),
             ('date', '<=', str(self.end_date)),
             ('plan_id', '=',
              'metro_park_maintenance.month_plan, {plan_id}'.format(
                  plan_id=self.id))],
            order="dev asc, date asc")

        tasks = []
        date_tasks = {}
        pre_task = None
        for work in works:
            # 默认都有第几天,导入的时候要处理下
            task_index = len(tasks)
            task = {
                "index": task_index,
                "date": str(work.date),
                "dev": work.dev.id,
                "val": 0,
                "rule_id": work.rule.id,
                "need_platform": True if 'D' in work.rule_no else False,
                "same_with_pre_task": -1,
                "id": work.id
            }
            if pre_task:
                pre_date = pendulum.parse(pre_task["date"]).add(days=1)
                cur_date = pendulum.parse(task["date"])
                if pre_date == cur_date \
                        and pre_task["rule_id"] == task["rule_id"] \
                        and pre_task["dev"] == task["dev"]:
                    task["same_with_pre_task"] = pre_task["index"]
            tasks.append(task)

            pre_task = task
            date_tasks.setdefault(str(work.date), []).append(task_index)

        # 统计每个地方的工班数量, 原则上一个工班只属于一个地方
        location_work_class_info = {}
        for work_class in work_classes:
            if len(work_class.locations) > 0:
                location = work_class.locations[0]
                location_work_class_info.setdefault(location.id,
                                                    []).append(work_class.id)

        # 取得本月的天数
        start_date = pendulum.parse(str(self.start_date))

        # 取得服务器配置
        config = self.env["metro_park_base.system_config"].get_configs()

        return {
            "work_classes": work_classes.mapped("id"),
            "work_class_count": work_class_count,
            "tasks": tasks,
            "tasks_count": len(tasks),
            "location_work_class_info": location_work_class_info,
            "day_count": start_date.days_in_month,
            "date_tasks": date_tasks,
            "calc_host": config.get("calc_host")
        }

    @api.model
    def deal_plan_work_class_result(self, plan_data, rst):
        '''
        处理计划结果数据, 将工班结果写入
        :return:
        '''
        rst = rst["datas"]
        tasks = plan_data["tasks"]
        work_classes = plan_data["work_classes"]
        for index, task in enumerate(tasks):
            rule_info = self.env["metro_park_maintenance.rule_info"]\
                    .browse(task["id"])
            val = rst[index]
            _logger.info('the val is:{val}'.format(val=val))
            rule_info.work_class = [(6, 0, [work_classes[val - 1]])]

    @api.multi
    def get_month_plan_data(self, wizard_id):
        '''
        计算月计划,本月原来安排的只能优选选择
        :return:
        '''
        wizard_info = \
            self.env["metro_park_maintenance.month_plan_compute_wizard"].browse(wizard_id)

        # 当月的计划, 有可能是扣车移动过来的,同时可能有月计划的内容
        real_infos = self.env["metro_park_maintenance.rule_info"]\
            .search([('plan_id', '=', 'metro_park_maintenance.month_plan, {plan_id}'.format(
                         plan_id=self.id)),
                     ('rule.target_plan_type', '=', 'year')], order="date asc")

        # 年计划就要求均衡
        dev_tasks_cache = dict()
        for info in real_infos:
            dev_tasks_cache.setdefault(info.dev.id, []).append(info)

        # 暂时没有考虑跨月的项,跨月的项要要作预先安排
        # for key in dev_tasks_cache:
        #     infos = dev_tasks_cache[key]
        #     if len(infos) > 0 and infos[0].repair_num != 1:
        #         while len(infos) > 0 and infos[0].repair_num != 1:
        #             infos = infos[1:]
        #     dev_tasks_cache[key] = infos

        month_start = pendulum.date(self.year, self.month, 1)
        month_days = month_start.days_in_month

        # 白名单
        month_end = month_start.add(days=month_days)
        white_list = self.env["metro_park_maintenance.white_list"]\
            .search([("date", ">=", month_start.format('YYYY-MM-DD')),
                     ('date', '<=', month_end.format('YYYY-MM-DD'))])
        white_dates = []
        for record in white_list:
            white_dates.append(
                pendulum.parse(str(record.date)).format("YYYY-MM-DD"))

        # 查询特殊日期配置, 黑名单
        special_days = \
            self.env['metro_park_maintenance.holidays'].search([])
        special_days_cache = {
            str(record.date): True
            for record in special_days
        }

        # 节假日
        holidays = []
        for tmp_index in range(1, month_days + 1):
            tmp_date = pendulum.date(self.year, self.month, tmp_index)
            tmp_date_str = tmp_date.format('YYYY-MM-DD')
            if (tmp_date.day_of_week == 6 or tmp_date.day_of_week == 0) \
                    and tmp_date_str not in white_dates:
                holidays.append(tmp_index)
            if tmp_date_str in special_days_cache and tmp_date_str not in white_dates:
                holidays.append(tmp_index)

        # 取得缓存信息, 扣车等会自动挪动,或是用户手动移动
        month_start_date = pendulum.date(self.year, self.month, 1)
        history_plans = self.env["metro_park_maintenance.rule_info"]\
            .get_year_plan_history_info(month_start_date, False)
        history_plan_cache = {plan.dev.id: plan for plan in history_plans}

        tasks = []
        for dev_id in dev_tasks_cache:

            dev_tasks = []
            infos = dev_tasks_cache[dev_id]
            pre_info = None
            for tmp_index, info in enumerate(infos):

                # 过滤掉复复的情况
                if pre_info and pre_info.rule.id == info.rule.id:
                    pre_info = info
                    continue
                else:
                    pre_info = info

                rule = info.rule

                task = {
                    "index": len(tasks),
                    "id": info.id,
                    "rule_id": info.rule.id,
                    "dev": dev_id,
                    # 同一设备不限定
                    "pre_index": len(tasks) - 1 if tmp_index != 0 else -1,
                    "period": info.rule.period,
                    "positive_offset": info.rule.positive_offset,
                    "negative_offset": info.rule.negative_offset,
                    "repeat_index": -1,
                    # 检修第一天
                    "repair_day": 1,
                    "repair_days": info.rule.repair_days,
                    "left_days": info.rule.period,
                    "info_count": len(infos),
                    "month_start_val": 1,
                    "month_end_val": month_days,
                    "repeat": False,
                    "repair_num": info.repair_num,
                    "attach_index": -1,
                    "is_extra": False
                }

                # 第一个月需要考虑历史信息, 但是这个不考虑修次,决定了最终的位置
                if tmp_index == 0:
                    history_repair_info = history_plan_cache.get(dev_id, False)
                    if history_repair_info:

                        # 最后一个计划开始的日期
                        tmp_date = pendulum.parse(
                            str(history_repair_info["date"]))
                        tmp_delta = month_start_date - tmp_date

                        # 已经使用了的天数
                        offset_days = tmp_delta.days - 1

                        # 已经超超期,需要直接安排
                        if offset_days > rule['period'] + rule[
                                'positive_offset']:
                            task['left_days'] = 0
                            task['positive_offset'] = 0
                            task['negative_offset'] = 0
                        # 在负区间内
                        elif rule['period'] - rule[
                                'negative_offset'] <= offset_days < rule[
                                    'period']:
                            task['left_days'] = rule['period'] - offset_days
                            task['negative_offset'] = rule[
                                'period'] - offset_days
                        # 在正区间内, 这个有可能刚好剩下的就是星期六和星期天,这个就有点尴尬了
                        elif rule['period'] <= offset_days <= rule[
                                'period'] + rule['positive_offset']:
                            task['left_days'] = 0
                            task['negative_offset'] = 0
                            task['positive_offset'] = \
                                rule['period'] + rule['positive_offset'] - offset_days
                        else:
                            # 还没有到达检修周期, 肯定是在上个月进行的,所以无论如何都可以排
                            task['left_days'] = rule['period'] - offset_days

                tasks.append(task)
                dev_tasks.append(task)

                # 时于按时间排序,均衡修不可能同一天进行多个,所以,后面的几个正好是重复的
                repair_days = rule.repair_days
                is_extra = False
                for day in range(1, repair_days):
                    tmp_task = copy.copy(task)

                    # 跨月的情况, 从下月找到相应的数据
                    if tmp_index + day > len(infos) - 1:
                        is_extra = True
                        tmp_year = self.year
                        next_month = self.month + 1
                        if next_month > 12:
                            next_month = 1
                            tmp_year += 1
                        left_rule_infos = \
                            self.env["metro_park_maintenance.rule_info"].search(
                                [('year', '=', tmp_year),
                                 ('month', "=", next_month),
                                 ('dev', '=', dev_id),
                                 ('rule', '=', rule.id)], order="date asc")
                        for tmp_info in left_rule_infos:
                            infos.append(tmp_info)

                    # assert infos[tmp_index + day].rule.id == info.rule.id
                    # assert infos[tmp_index + day].id != info.id

                    tmp_info = infos[tmp_index + day]
                    tmp_task["id"] = tmp_info.id
                    tmp_task["repeat_index"] = len(tasks) - 1
                    tmp_task["pre_index"] = len(tasks) - 1
                    tmp_task["repair_day"] = day + 1
                    tmp_task["index"] = len(tasks)
                    tmp_task["repeat"] = True
                    tmp_task["is_extra"] = is_extra

                    tasks.append(tmp_task)

        # 取得计算服务器
        config = self.env['metro_park_base.system_config'].get_configs()
        calc_host = config.get('calc_host', False)

        attach_tasks = []

        # 附加空调专检的task 如果是第一月份的话, 第一个月尽量安排
        plan_kt_month_1 = self.env.ref("metro_park_base.plan_kt_month_1").id
        plan_kt_month_2 = self.env.ref("metro_park_base.plan_kt_month_2").id
        if wizard_info.plan_kt \
                and wizard_info.plan_kt_month == plan_kt_month_1:
            attach_tasks = []
            for task in tasks:
                # 只按排修程只有一天的
                if task["repair_days"] == 1 and task["repair_day"] == 1:
                    attach_task = {
                        "index": len(attach_tasks),
                        "attach_index": task["index"],
                        "force": False,
                        "dev_id": task['dev']
                    }
                    # 相互关联
                    task["attach_index"] = attach_task["index"]
                    repair_num = task["repair_num"]
                    # 下一次没法进行的情况
                    next_repair_info = \
                        self.env['metro_park_maintenance.plan_config_data'].get_next_repair_info(repair_num)
                    if next_repair_info.repair_days > 1:
                        attach_task["force"] = True
                    attach_tasks.append(attach_task)
        elif wizard_info.plan_kt \
                and wizard_info.plan_kt_month.id == plan_kt_month_2:
            # 如果上一个月没有安排空调专检的话这个月就必需要安排
            kt_rule_id = self.env.ref(
                "metro_park_base_data_10.repair_rule_kt").id
            prev_month = self.month - 1
            prev_year = self.year
            if prev_month <= 0:
                prev_month = 12
                prev_year = prev_year - 1
            his_kt_infos = self.env["metro_park_maintenance.rule_info"].search(
                [("rule", '=', kt_rule_id), ("month", "=", prev_month),
                 ("year", '=', prev_year)])

            his_kt_info_cache = dict()
            for tmp_info in his_kt_infos:
                his_kt_info_cache[tmp_info.dev.id] = tmp_info

            black_dev = []
            for task in tasks:
                if task["dev"] not in his_kt_info_cache \
                        and task['repair_day'] == 1 and task["dev"] not in black_dev:
                    black_dev.append(task['dev'])
                    attach_task = {
                        "index": len(attach_tasks),
                        "attach_index": task["index"],
                        "force": False,
                        "dev_id": task['dev']
                    }
                    # 相互关联
                    task["attach_index"] = len(attach_tasks)
                    attach_tasks.append(attach_task)

        return {
            "tasks":
            tasks,
            "month":
            self.month,
            "year":
            self.year,
            "month_days":
            month_days,
            "holidays":
            holidays,
            "calc_host":
            calc_host,
            "max_plan_per_day":
            wizard_info.max_plan_per_day,
            "attach_tasks":
            attach_tasks or [],
            "plan_kt":
            True if wizard_info.plan_kt.value == 'yes' else False,
            "plan_kt_month":
            1 if wizard_info.plan_kt_month.id == plan_kt_month_1 else 2
        }

    @api.multi
    def deal_month_plan_data(self, plan_data, result):
        '''
        处理月计划数据, 只是调整计划时间
        :return:
        '''
        if result['status'] != 200:
            raise exceptions.ValidationError("计算错错,未能找正确解")

        result = result['datas']
        plan_result = result["plan_result"]

        year = plan_data["year"]
        month = plan_data["month"]
        month_start = pendulum.date(year, month, 1)
        plan_id = self.id
        kt_rule_id = self.env.ref("metro_park_base_data_10.repair_rule_kt").id

        vals = []
        attach_result = result['attach_result']
        tasks = plan_data['tasks']
        for task in tasks:
            info_id = task["id"]
            index = task["index"]
            value = plan_result[index]

            # value是从1开始
            plan_date = month_start.add(days=value - 1)
            record = self.env["metro_park_maintenance.rule_info"].browse(
                info_id)
            if not task["is_extra"]:
                record.write({
                    "date": plan_date.format("YYYY-MM-DD"),
                    "year": plan_date.year,
                    "month": plan_date.month,
                    "day": plan_date.day_of_year
                })
            else:
                # 下月放在本月的情况,创建本月数据
                self.env["metro_park_maintenance.rule_info"].create([{
                    "date":
                    plan_date.format("YYYY-MM-DD"),
                    "dev":
                    task['dev'],
                    "year":
                    plan_date.year,
                    "month":
                    plan_date.month,
                    "day":
                    plan_date.day,
                    "plan_id":
                    "metro_park_maintenance.month_plan, {plan_id}".format(
                        plan_id=plan_id),
                    "rule":
                    task["rule_id"],
                    'data_source':
                    'month'
                }])

            attach_index = task["attach_index"]
            if attach_index != -1 and attach_result[attach_index] == 1:
                plan_date = plan_date.subtract(days=1)
                vals.append({
                    "date":
                    plan_date.format("YYYY-MM-DD"),
                    "dev":
                    task['dev'],
                    "year":
                    plan_date.year,
                    "month":
                    plan_date.month,
                    "day":
                    plan_date.day,
                    "plan_id":
                    "metro_park_maintenance.month_plan, {plan_id}".format(
                        plan_id=plan_id),
                    "rule":
                    kt_rule_id,
                    'data_source':
                    'month'
                })

        self.env["metro_park_maintenance.rule_info"].create(vals)

    @api.multi
    def view_delta(self):
        '''
        查看本月修程和上月的间隔
        :return:
        '''
        month = self.month
        year = self.year

        pre_year = year
        pre_month = month - 1
        if month == 1:
            pre_year = year - 1
            pre_month = 12

        # 取得历修程信息, 取得后一天的
        history_plan_cache = dict()
        pre_date = pendulum.date(pre_year, pre_month, 1)
        pre_date = pendulum.date(pre_year, pre_month, pre_date.days_in_month)
        pre_plans = self.env["metro_park_maintenance.rule_info"].search(
            [("date", '<', pre_date), ('year', '=', pre_year),
             ('data_source', '=', 'month')],
            order='date asc')
        for tmp_plan in pre_plans:
            history_plan_cache[tmp_plan.dev.id] = tmp_plan

        # 取得当月的信息
        month_plans = self.env["metro_park_maintenance.rule_info"].search(
            [("year", '=', year), ('month', '=', month),
             ('data_source', '=', 'month')],
            order='date asc')
        cur_month_cache = {}
        for month_plan in month_plans:
            cur_month_cache.setdefault(month_plan.dev.id,
                                       []).append(month_plan)

        vals = []
        for dev_id in cur_month_cache:
            plan = cur_month_cache[dev_id][0]
            tmp_end_date = pendulum.parse(str(plan.date))
            if dev_id in history_plan_cache:
                his_plan = history_plan_cache.get(dev_id, None)
                if his_plan:
                    tmp_start_date = pendulum.parse(str(his_plan.date))
                    delta = tmp_end_date - tmp_start_date
                    vals.append({"dev": dev_id, "days": delta.days})

        records = self.env[
            "metro_park_maintenance.month_plan_delta_info"].create(vals)

        tree_id = self.env.ref(
            'metro_park_maintenance.month_plan_delta_info_list').id

        return {
            "type": "ir.actions.act_window",
            "target": "new",
            "res_model": "metro_park_maintenance.month_plan_delta_info",
            "name": "计划偏移",
            "views": [[tree_id, 'list'], [False, 'form']],
            "domain": [("id", "in", records.ids)]
        }

    @api.multi
    def syn_year_info(self):
        '''
        同步年计划数据
        :return:
        '''

        # 清除自身的内容
        infos = self.env["metro_park_maintenance.rule_info"].search([
            ('plan_id', '=',
             'metro_park_maintenance.month_plan, {plan_id}'.format(
                 plan_id=self.id))
        ])
        infos.write({"active": False})

        rule_infos = self.env["metro_park_maintenance.rule_info"].search([
            ('data_source', '=', 'year'), ('month', '=', self.month),
            ('plan_id', '=',
             "metro_park_maintenance.year_plan, {plan_id}".format(
                 plan_id=self.year_plan.id))
        ])

        vals = []
        for info in rule_infos:
            vals.append({
                'dev':
                info.dev.id,
                'rule':
                info.rule.id,
                'data_source':
                'month',
                'date':
                str(info.date),
                'parent_id':
                info.id,
                'repair_num':
                info.repair_num,
                'repair_day':
                info.repair_day,
                'plan_id':
                'metro_park_maintenance.month_plan, {plan_id}'.format(
                    plan_id=self.id)
            })

        return self.env["metro_park_maintenance.rule_info"] \
            .create(vals)

    @api.model
    def export_produce_plan(self):
        """
        导出生产计划
        :return:
        """
        now_day = pendulum.today()
        pre_year = now_day.subtract(years=1)

        return {
            "type":
            "ir.actions.act_window",
            "res_model":
            "metro_park_maintenance.export_produce_plan_wizard",
            'view_mode':
            'form',
            "target":
            "new",
            'context': {
                "default_start_year":
                self.env["metro_park_maintenance.year"].search(
                    domain=[("val", "=", pre_year.year)]).id,
                "default_start_month":
                self.env["metro_park_maintenance.month"].search(
                    domain=[("val", "=", pre_year.month)]).id,
                "default_end_year":
                self.env["metro_park_maintenance.year"].search(
                    domain=[("val", "=", now_day.year)]).id,
                "default_end_month":
                self.env["metro_park_maintenance.month"].search(
                    domain=[("val", "=", now_day.month)]).id,
            },
            "views": [[
                self.env.ref(
                    'metro_park_maintenance.export_produce_plan_wizard_form').
                id, "form"
            ]]
        }

    @api.multi
    def clear_duplicate(self):
        '''
        去除一个月排了多个修程的情况
        :return:
        '''
        dev_rule_cache = {}
        records = self.env["metro_park_maintenance.rule_info"].search(
            [('plan_id', '=',
              'metro_park_maintenance.month_plan, {plan_id}'.format(
                  plan_id=self.id))],
            order='date asc')
        for record in records:
            dev_rule_cache.setdefault(record.dev.id, []).append(record.rule.id)

        # 去除一个月排了多个修程
        del_info = []
        for key in dev_rule_cache:
            rules = set(dev_rule_cache[key])
            if len(rules) > 1:
                del_info.append({"dev_id": int(key), "rule": list(rules)[-1]})

        # 数量不会太多,一个一个进行删除
        for info in del_info:
            record = self.env["metro_park_maintenance.rule_info"].search([
                ('dev', '=', info['dev_id']), ('rule.id', '=', info['rule'])
            ])
            # 移动到下一个月去
            record.unlink()

    @api.multi
    def import_work_class_info(self):
        '''
        导入生产说明,
        :return:
        '''
        return {
            "type":
            "ir.actions.act_window",
            "res_model":
            "metro_park_base.import_produce_work_class",
            'view_mode':
            'form',
            'target':
            'new',
            'context': {
                'default_month_plan': self.id,
            },
            "views": [[
                self.env.ref(
                    'metro_park_maintenance.import_produce_work_class_form').
                id, "form"
            ]]
        }
Пример #16
0
class ResPartnerBank(models.Model):
    _inherit = 'res.partner.bank'

    l10n_br_convenio_pagamento = fields.Char('Cód de Convênio Pagamento')
class SaleGenerator(models.Model):
    _name = 'sale.generator'

    name = fields.Char(string='Generator', default='/')
    partner_ids = fields.Many2many(comodel_name='res.partner',
                                   string="Partner")
    sale_ids = fields.One2many(comodel_name='sale.order',
                               inverse_name='generator_id',
                               string="Sales")
    tmpl_sale_id = fields.Many2one(comodel_name='sale.order',
                                   string="Sale Template",
                                   required=True,
                                   domain=[('is_template', '=', True)])
    date_order = fields.Datetime(string='Date',
                                 oldname='date',
                                 default=fields.Datetime.now())
    warehouse_id = fields.Many2one(comodel_name='stock.warehouse',
                                   required=True,
                                   string="Warehouse")
    state = fields.Selection([
        ('draft', 'Draft'),
        ('generating', 'Generating Order'),
        ('done', 'Done'),
    ],
                             string='State',
                             readonly=True,
                             default='draft')
    company_id = fields.Many2one(comodel_name='res.company',
                                 string="Company",
                                 related="warehouse_id.company_id",
                                 store=True)

    @api.multi
    def _prepare_copy_vals(self, partner):
        self.ensure_one()
        vals = {
            'partner_id': partner.id,
            'generator_id': self.id,
            'warehouse_id': self.warehouse_id.id,
            'company_id': self.warehouse_id.company_id.id,
            'is_template': False,
        }
        return vals

    @api.multi
    def _create_order_for_partner(self, partner):
        self.ensure_one()
        vals = self._prepare_copy_vals(partner)
        self.tmpl_sale_id.copy(vals)

    @api.multi
    def button_update_order(self):
        for res in self:
            if not res.partner_ids:
                raise UserError(
                    _("Can't generate sale order without customer "))
            else:
                res.write({'state': 'generating'})
                res._update_order()

    @api.multi
    def _update_order(self):
        self.ensure_one()
        partners_with_order = self.sale_ids.mapped('partner_id')
        for partner in self.partner_ids.filtered(
                lambda record: record not in partners_with_order):
            self._create_order_for_partner(partner)
        for sale in self.sale_ids.filtered(
                lambda record: record.partner_id not in self.partner_ids):
            sale.unlink()

    @api.multi
    def action_confirm(self):
        for res in self:
            res.write({'state': 'done'})
            res.sale_ids.action_confirm()

    @api.multi
    def write(self, vals):
        res = super(SaleGenerator, self).write(vals)
        if 'partner_ids' in vals and self.state == 'generating':
            self._update_order()
        return res

    def add_generated_partner(self):
        return {
            'type': 'ir.actions.act_window',
            'res_model': 'res.partner',
            'name': u"New Customer",
            'id': self.env.ref('base.view_partner_form').id,
            'view_mode': 'form',
            'target': 'new',
        }

    @api.model
    def create(self, vals):
        if vals.get('name', '/') == '/':
            vals['name'] = self.env['ir.sequence'].next_by_code(
                'sale.order.generator') or '/'
        return super(SaleGenerator, self).create(vals)
Пример #18
0
class HrEmployeeBase(models.AbstractModel):
    _inherit = "hr.employee.base"

    leave_manager_id = fields.Many2one(
        'res.users', string='Time Off',
        compute='_compute_leave_manager', store=True, readonly=False,
        help="User responsible of leaves approval.")
    remaining_leaves = fields.Float(
        compute='_compute_remaining_leaves', string='Remaining Paid Time Off',
        help='Total number of paid time off allocated to this employee, change this value to create allocation/time off request. '
             'Total based on all the time off types without overriding limit.')
    current_leave_state = fields.Selection(compute='_compute_leave_status', string="Current Time Off Status",
        selection=[
            ('draft', 'New'),
            ('confirm', 'Waiting Approval'),
            ('refuse', 'Refused'),
            ('validate1', 'Waiting Second Approval'),
            ('validate', 'Approved'),
            ('cancel', 'Cancelled')
        ])
    current_leave_id = fields.Many2one('hr.leave.type', compute='_compute_leave_status', string="Current Time Off Type")
    leave_date_from = fields.Date('From Date', compute='_compute_leave_status')
    leave_date_to = fields.Date('To Date', compute='_compute_leave_status')
    leave_consolidated_date_to = fields.Date('Leave Consolidated To Date', compute='_compute_leave_consolidated_date_to')
    leaves_count = fields.Float('Number of Time Off', compute='_compute_remaining_leaves')
    allocation_count = fields.Float('Total number of days allocated.', compute='_compute_allocation_count')
    allocation_used_count = fields.Float('Total number of days off used', compute='_compute_total_allocation_used')
    show_leaves = fields.Boolean('Able to see Remaining Time Off', compute='_compute_show_leaves')
    is_absent = fields.Boolean('Absent Today', compute='_compute_leave_status', search='_search_absent_employee')
    allocation_display = fields.Char(compute='_compute_allocation_count')
    allocation_used_display = fields.Char(compute='_compute_total_allocation_used')

    def _get_date_start_work(self):
        return self.create_date

    def _get_remaining_leaves(self):
        """ Helper to compute the remaining leaves for the current employees
            :returns dict where the key is the employee id, and the value is the remain leaves
        """
        self._cr.execute("""
            SELECT
                sum(h.number_of_days) AS days,
                h.employee_id
            FROM
                (
                    SELECT holiday_status_id, number_of_days,
                        state, employee_id
                    FROM hr_leave_allocation
                    UNION ALL
                    SELECT holiday_status_id, (number_of_days * -1) as number_of_days,
                        state, employee_id
                    FROM hr_leave
                ) h
                join hr_leave_type s ON (s.id=h.holiday_status_id)
            WHERE
                s.active = true AND h.state='validate' AND
                (s.allocation_type='fixed' OR s.allocation_type='fixed_allocation') AND
                h.employee_id in %s
            GROUP BY h.employee_id""", (tuple(self.ids),))
        return dict((row['employee_id'], row['days']) for row in self._cr.dictfetchall())

    def _compute_remaining_leaves(self):
        remaining = self._get_remaining_leaves()
        for employee in self:
            value = float_round(remaining.get(employee.id, 0.0), precision_digits=2)
            employee.leaves_count = value
            employee.remaining_leaves = value

    def _compute_allocation_count(self):
        for employee in self:
            allocations = self.env['hr.leave.allocation'].search([
                ('employee_id', '=', employee.id),
                ('holiday_status_id.active', '=', True),
                ('state', '=', 'validate'),
                '|',
                    ('date_to', '=', False),
                    ('date_to', '>=', datetime.date.today()),
            ])
            employee.allocation_count = sum(allocations.mapped('number_of_days'))
            employee.allocation_display = "%g" % employee.allocation_count

    def _compute_total_allocation_used(self):
        for employee in self:
            employee.allocation_used_count = employee.allocation_count - employee.remaining_leaves
            employee.allocation_used_display = "%g" % employee.allocation_used_count

    def _compute_presence_state(self):
        super()._compute_presence_state()
        employees = self.filtered(lambda employee: employee.hr_presence_state != 'present' and employee.is_absent)
        employees.update({'hr_presence_state': 'absent'})

    def _compute_leave_consolidated_date_to(self):
        holidays = self.env['hr.leave'].sudo().search([
            ('employee_id', 'in', self.ids),
            ('date_to', '>=', fields.Datetime.now()),
            ('state', 'not in', ('cancel', 'refuse'))
        ], order="date_from")

        leave_data = defaultdict(list)
        for holiday in holidays:
            leave_data[holiday.employee_id].append({'date_to': holiday.date_to.date(), 'date_from': holiday.date_from.date()})

        for employee, leave_data in leave_data.items():
            first_leave = leave_data[0]
            date_to = first_leave['date_to']
            working_days = employee.mapped('resource_calendar_id.attendance_ids.dayofweek')
            days = 1
            for next_leave in leave_data:
                while str((first_leave['date_to'] + timedelta(days=days)).weekday()) not in working_days:
                    days += 1
                if next_leave['date_from'] == first_leave['date_to'] + timedelta(days=days):
                    days = 1
                    date_to = next_leave['date_to']
                    first_leave = next_leave
            employee.leave_consolidated_date_to = date_to

        for employee in self - holidays.employee_id:
            employee.leave_consolidated_date_to = False

    def _compute_leave_status(self):
        # Used SUPERUSER_ID to forcefully get status of other user's leave, to bypass record rule
        holidays = self.env['hr.leave'].sudo().search([
            ('employee_id', 'in', self.ids),
            ('date_from', '<=', fields.Datetime.now()),
            ('date_to', '>=', fields.Datetime.now()),
            ('state', 'not in', ('cancel', 'refuse'))
        ])
        leave_data = {}
        for holiday in holidays:
            leave_data[holiday.employee_id.id] = {}
            leave_data[holiday.employee_id.id]['leave_date_from'] = holiday.date_from.date()
            leave_data[holiday.employee_id.id]['leave_date_to'] = holiday.date_to.date()
            leave_data[holiday.employee_id.id]['current_leave_state'] = holiday.state
            leave_data[holiday.employee_id.id]['current_leave_id'] = holiday.holiday_status_id.id

        for employee in self:
            employee.leave_date_from = leave_data.get(employee.id, {}).get('leave_date_from')
            employee.leave_date_to = leave_data.get(employee.id, {}).get('leave_date_to')
            employee.current_leave_state = leave_data.get(employee.id, {}).get('current_leave_state')
            employee.current_leave_id = leave_data.get(employee.id, {}).get('current_leave_id')
            employee.is_absent = leave_data.get(employee.id) and leave_data.get(employee.id, {}).get('current_leave_state') not in ['cancel', 'refuse', 'draft']

    @api.depends('parent_id')
    def _compute_leave_manager(self):
        for employee in self:
            previous_manager = employee._origin.parent_id.user_id
            manager = employee.parent_id.user_id
            if manager and employee.leave_manager_id == previous_manager or not employee.leave_manager_id:
                employee.leave_manager_id = manager

    def _compute_show_leaves(self):
        show_leaves = self.env['res.users'].has_group('hr_holidays.group_hr_holidays_user')
        for employee in self:
            if show_leaves or employee.user_id == self.env.user:
                employee.show_leaves = True
            else:
                employee.show_leaves = False

    def _search_absent_employee(self, operator, value):
        holidays = self.env['hr.leave'].sudo().search([
            ('employee_id', '!=', False),
            ('state', 'not in', ['cancel', 'refuse']),
            ('date_from', '<=', datetime.datetime.utcnow()),
            ('date_to', '>=', datetime.datetime.utcnow())
        ])
        return [('id', 'in', holidays.mapped('employee_id').ids)]

    def write(self, values):
        res = super(HrEmployeeBase, self).write(values)
        if 'parent_id' in values or 'department_id' in values:
            today_date = fields.Datetime.now()
            hr_vals = {}
            if values.get('parent_id') is not None:
                hr_vals['manager_id'] = values['parent_id']
            if values.get('department_id') is not None:
                hr_vals['department_id'] = values['department_id']
            holidays = self.env['hr.leave'].sudo().search(['|', ('state', 'in', ['draft', 'confirm']), ('date_from', '>', today_date), ('employee_id', 'in', self.ids)])
            holidays.write(hr_vals)
            allocations = self.env['hr.leave.allocation'].sudo().search([('state', 'in', ['draft', 'confirm']), ('employee_id', 'in', self.ids)])
            allocations.write(hr_vals)
        return res
Пример #19
0
class DeliveryCarrier(models.Model):
    _name = 'delivery.carrier'
    _description = "Delivery Methods"
    _order = 'sequence, id'
    ''' A Shipping Provider

    In order to add your own external provider, follow these steps:

    1. Create your model MyProvider that _inherit 'delivery.carrier'
    2. Extend the selection of the field "delivery_type" with a pair
       ('<my_provider>', 'My Provider')
    3. Add your methods:
       <my_provider>_rate_shipment
       <my_provider>_send_shipping
       <my_provider>_get_tracking_link
       <my_provider>_cancel_shipment
       _<my_provider>_get_default_custom_package_code
       (they are documented hereunder)
    '''

    # -------------------------------- #
    # Internals for shipping providers #
    # -------------------------------- #

    name = fields.Char('Delivery Method', required=True, translate=True)
    active = fields.Boolean(default=True)
    sequence = fields.Integer(help="Determine the display order", default=10)
    # This field will be overwritten by internal shipping providers by adding their own type (ex: 'fedex')
    delivery_type = fields.Selection([('fixed', 'Fixed Price')],
                                     string='Provider',
                                     default='fixed',
                                     required=True)
    integration_level = fields.Selection(
        [('rate', 'Get Rate'),
         ('rate_and_ship', 'Get Rate and Create Shipment')],
        string="Integration Level",
        default='rate_and_ship',
        help="Action while validating Delivery Orders")
    prod_environment = fields.Boolean(
        "Environment",
        help="Set to True if your credentials are certified for production.")
    debug_logging = fields.Boolean(
        'Debug logging', help="Log requests in order to ease debugging")
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 related='product_id.company_id',
                                 store=True,
                                 readonly=False)
    product_id = fields.Many2one('product.product',
                                 string='Delivery Product',
                                 required=True,
                                 ondelete='restrict')

    invoice_policy = fields.Selection(
        [('estimated', 'Estimated cost'), ('real', 'Real cost')],
        string='Invoicing Policy',
        default='estimated',
        required=True,
        help=
        "Estimated Cost: the customer will be invoiced the estimated cost of the shipping.\nReal Cost: the customer will be invoiced the real cost of the shipping, the cost of the shipping will be updated on the SO after the delivery."
    )

    country_ids = fields.Many2many('res.country',
                                   'delivery_carrier_country_rel',
                                   'carrier_id', 'country_id', 'Countries')
    state_ids = fields.Many2many('res.country.state',
                                 'delivery_carrier_state_rel', 'carrier_id',
                                 'state_id', 'States')
    zip_from = fields.Char('Zip From')
    zip_to = fields.Char('Zip To')

    margin = fields.Integer(
        help='This percentage will be added to the shipping price.')
    free_over = fields.Boolean(
        'Free if order amount is above',
        help=
        "If the order total amount (shipping excluded) is above or equal to this value, the customer benefits from a free shipping",
        default=False,
        oldname='free_if_more_than')
    amount = fields.Float(
        string='Amount',
        help=
        "Amount of the order to benefit from a free shipping, expressed in the company currency"
    )

    _sql_constraints = [
        ('margin_not_under_100_percent', 'CHECK (margin >= -100)',
         'Margin cannot be lower than -100%'),
    ]

    def toggle_prod_environment(self):
        for c in self:
            c.prod_environment = not c.prod_environment

    def toggle_debug(self):
        for c in self:
            c.debug_logging = not c.debug_logging

    @api.multi
    def install_more_provider(self):
        return {
            'name':
            'New Providers',
            'view_mode':
            'kanban,form',
            'res_model':
            'ir.module.module',
            'domain': [['name', 'ilike', 'delivery_']],
            'type':
            'ir.actions.act_window',
            'help':
            _('''<p class="o_view_nocontent">
                    Buy Odoo Enterprise now to get more providers.
                </p>'''),
        }

    def available_carriers(self, partner):
        return self.filtered(lambda c: c._match_address(partner))

    def _match_address(self, partner):
        self.ensure_one()
        if self.country_ids and partner.country_id not in self.country_ids:
            return False
        if self.state_ids and partner.state_id not in self.state_ids:
            return False
        if self.zip_from and (partner.zip
                              or '').upper() < self.zip_from.upper():
            return False
        if self.zip_to and (partner.zip or '').upper() > self.zip_to.upper():
            return False
        return True

    @api.onchange('state_ids')
    def onchange_states(self):
        self.country_ids = [
            (6, 0,
             self.country_ids.ids + self.state_ids.mapped('country_id.id'))
        ]

    @api.onchange('country_ids')
    def onchange_countries(self):
        self.state_ids = [
            (6, 0,
             self.state_ids.filtered(lambda state: state.id in self.country_ids
                                     .mapped('state_ids').ids).ids)
        ]

    # -------------------------- #
    # API for external providers #
    # -------------------------- #

    def rate_shipment(self, order):
        ''' Compute the price of the order shipment

        :param order: record of sale.order
        :return dict: {'success': boolean,
                       'price': a float,
                       'error_message': a string containing an error message,
                       'warning_message': a string containing a warning message}
                       # TODO maybe the currency code?
        '''
        self.ensure_one()
        if hasattr(self, '%s_rate_shipment' % self.delivery_type):
            res = getattr(self, '%s_rate_shipment' % self.delivery_type)(order)
            # apply margin on computed price
            res['price'] = float(res['price']) * (1.0 +
                                                  (float(self.margin) / 100.0))
            # free when order is large enough
            if res['success'] and self.free_over and order._compute_amount_total_without_delivery(
            ) >= self.amount:
                res['warning_message'] = _(
                    'Info:\nThe shipping is free because the order amount exceeds %.2f.\n(The actual shipping cost is: %.2f)'
                ) % (self.amount, res['price'])
                res['price'] = 0.0
            return res

    def send_shipping(self, pickings):
        ''' Send the package to the service provider

        :param pickings: A recordset of pickings
        :return list: A list of dictionaries (one per picking) containing of the form::
                         { 'exact_price': price,
                           'tracking_number': number }
                           # TODO missing labels per package
                           # TODO missing currency
                           # TODO missing success, error, warnings
        '''
        self.ensure_one()
        if hasattr(self, '%s_send_shipping' % self.delivery_type):
            return getattr(self,
                           '%s_send_shipping' % self.delivery_type)(pickings)

    def get_tracking_link(self, picking):
        ''' Ask the tracking link to the service provider

        :param picking: record of stock.picking
        :return str: an URL containing the tracking link or False
        '''
        self.ensure_one()
        if hasattr(self, '%s_get_tracking_link' % self.delivery_type):
            return getattr(self, '%s_get_tracking_link' %
                           self.delivery_type)(picking)

    def cancel_shipment(self, pickings):
        ''' Cancel a shipment

        :param pickings: A recordset of pickings
        '''
        self.ensure_one()
        if hasattr(self, '%s_cancel_shipment' % self.delivery_type):
            return getattr(self,
                           '%s_cancel_shipment' % self.delivery_type)(pickings)

    def log_xml(self, xml_string, func):
        self.ensure_one()

        if self.debug_logging:
            db_name = self._cr.dbname

            # Use a new cursor to avoid rollback that could be caused by an upper method
            try:
                db_registry = registry(db_name)
                with db_registry.cursor() as cr:
                    env = api.Environment(cr, SUPERUSER_ID, {})
                    IrLogging = env['ir.logging']
                    IrLogging.sudo().create({
                        'name': 'delivery.carrier',
                        'type': 'server',
                        'dbname': db_name,
                        'level': 'DEBUG',
                        'message': xml_string,
                        'path': self.delivery_type,
                        'func': func,
                        'line': 1
                    })
            except psycopg2.Error:
                pass

    def _get_default_custom_package_code(self):
        """ Some delivery carriers require a prefix to be sent in order to use custom
        packages (ie not official ones). This optional method will return it as a string.
        """
        self.ensure_one()
        if hasattr(self,
                   '_%s_get_default_custom_package_code' % self.delivery_type):
            return getattr(
                self,
                '_%s_get_default_custom_package_code' % self.delivery_type)()
        else:
            return False

    # ------------------------------------------------ #
    # Fixed price shipping, aka a very simple provider #
    # ------------------------------------------------ #

    fixed_price = fields.Float(compute='_compute_fixed_price',
                               inverse='_set_product_fixed_price',
                               store=True,
                               string='Fixed Price')

    @api.depends('product_id.list_price',
                 'product_id.product_tmpl_id.list_price')
    def _compute_fixed_price(self):
        for carrier in self:
            carrier.fixed_price = carrier.product_id.list_price

    def _set_product_fixed_price(self):
        for carrier in self:
            carrier.product_id.list_price = carrier.fixed_price

    def fixed_rate_shipment(self, order):
        carrier = self._match_address(order.partner_shipping_id)
        if not carrier:
            return {
                'success':
                False,
                'price':
                0.0,
                'error_message':
                _('Error: this delivery method is not available for this address.'
                  ),
                'warning_message':
                False
            }
        price = self.fixed_price
        if self.company_id and self.company_id.currency_id.id != order.currency_id.id:
            price = self.env['res.currency']._compute(
                self.company_id.currency_id, order.currency_id, price)
        return {
            'success': True,
            'price': price,
            'error_message': False,
            'warning_message': False
        }

    def fixed_send_shipping(self, pickings):
        res = []
        for p in pickings:
            res = res + [{
                'exact_price': p.carrier_id.fixed_price,
                'tracking_number': False
            }]
        return res

    def fixed_get_tracking_link(self, picking):
        return False

    def fixed_cancel_shipment(self, pickings):
        raise NotImplementedError()
Пример #20
0
class BuyAdjustLine(models.Model):
    _name = 'buy.adjust.line'
    _description = u'采购变更单明细'

    @api.one
    @api.depends('goods_id')
    def _compute_using_attribute(self):
        '''返回订单行中商品是否使用属性'''
        self.using_attribute = self.goods_id.attribute_ids and True or False

    @api.one
    @api.depends('quantity', 'price_taxed', 'discount_amount', 'tax_rate')
    def _compute_all_amount(self):
        '''当订单行的数量、单价、折扣额、税率改变时,改变购货金额、税额、价税合计'''
        if self.tax_rate > 100:
            raise UserError(u'税率不能输入超过100的数\n税率:%s!' % self.tax_rate)
        if self.tax_rate < 0:
            raise UserError(u'税率不能输入负数\n税率:%s!' % self.tax_rate)
        self.subtotal = self.price_taxed * self.quantity - self.discount_amount  # 价税合计
        self.tax_amount = self.subtotal / \
            (100 + self.tax_rate) * self.tax_rate  # 税额
        self.amount = self.subtotal - self.tax_amount  # 金额

    @api.onchange('price', 'tax_rate')
    def onchange_price(self):
        '''当订单行的不含税单价改变时,改变含税单价'''
        price = self.price_taxed / (1 + self.tax_rate * 0.01)  # 不含税单价
        decimal = self.env.ref('core.decimal_price')
        if float_compare(price, self.price,
                         precision_digits=decimal.digits) != 0:
            self.price_taxed = self.price * (1 + self.tax_rate * 0.01)

    order_id = fields.Many2one('buy.adjust',
                               u'订单编号',
                               index=True,
                               required=True,
                               ondelete='cascade',
                               help=u'关联的变更单编号')
    goods_id = fields.Many2one('goods', u'商品', ondelete='restrict', help=u'商品')
    using_attribute = fields.Boolean(u'使用属性',
                                     compute=_compute_using_attribute,
                                     help=u'商品是否使用属性')
    attribute_id = fields.Many2one('attribute',
                                   u'属性',
                                   ondelete='restrict',
                                   domain="[('goods_id', '=', goods_id)]",
                                   help=u'商品的属性,当商品有属性时,该字段必输')
    uom_id = fields.Many2one('uom', u'单位', ondelete='restrict', help=u'商品计量单位')
    quantity = fields.Float(u'调整数量',
                            default=1,
                            required=True,
                            digits=dp.get_precision('Quantity'),
                            help=u'相对于原单据对应明细行的调整数量,可正可负')
    price = fields.Float(u'购货单价',
                         store=True,
                         digits=dp.get_precision('Price'),
                         help=u'不含税单价,由含税单价计算得出')
    price_taxed = fields.Float(u'含税单价',
                               digits=dp.get_precision('Price'),
                               help=u'含税单价,取自商品成本')
    discount_rate = fields.Float(u'折扣率%', help=u'折扣率')
    discount_amount = fields.Float(u'折扣额',
                                   digits=dp.get_precision('Amount'),
                                   help=u'输入折扣率后自动计算得出,也可手动输入折扣额')
    amount = fields.Float(u'金额',
                          compute=_compute_all_amount,
                          store=True,
                          readonly=True,
                          digits=dp.get_precision('Amount'),
                          help=u'金额  = 价税合计  - 税额')
    tax_rate = fields.Float(
        u'税率(%)',
        default=lambda self: self.env.user.company_id.import_tax_rate,
        help=u'默认值取公司进项税率')
    tax_amount = fields.Float(u'税额',
                              compute=_compute_all_amount,
                              store=True,
                              readonly=True,
                              digits=dp.get_precision('Amount'),
                              help=u'由税率计算得出')
    subtotal = fields.Float(u'价税合计',
                            compute=_compute_all_amount,
                            store=True,
                            readonly=True,
                            digits=dp.get_precision('Amount'),
                            help=u'含税单价 乘以 数量')
    note = fields.Char(u'备注', help=u'本行备注')
    company_id = fields.Many2one(
        'res.company',
        string=u'公司',
        change_default=True,
        default=lambda self: self.env['res.company']._company_default_get())

    @api.onchange('goods_id')
    def onchange_goods_id(self):
        '''当订单行的商品变化时,带出商品上的单位、默认仓库、成本价'''
        if self.goods_id:
            self.uom_id = self.goods_id.uom_id
            self.price_taxed = self.goods_id.cost
            self.tax_rate = self.goods_id.get_tax_rate(
                self.goods_id, self.order_id.order_id.partner_id, 'buy')

    @api.onchange('quantity', 'price_taxed', 'discount_rate')
    def onchange_discount_rate(self):
        '''当数量、单价或优惠率发生变化时,优惠金额发生变化'''
        self.price = self.price_taxed / (1 + self.tax_rate * 0.01)
        self.discount_amount = (self.quantity * self.price *
                                self.discount_rate * 0.01)

    @api.one
    @api.constrains('attribute_id')
    def check_attribute(self):
        '''检查属性是否填充,防止无权限人员不填就可以保存'''
        if self.using_attribute and not self.attribute_id:
            raise UserError(u'请输入商品:%s 的属性' % self.goods_id.name)
Пример #21
0
class WorkshopVehicleCost(models.Model):
    _inherit = ['mail.thread', 'mail.activity.mixin']
    _name = 'workshop.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,
                       readonly=False)
    owner_id = fields.Many2one('res.partner',
                               related='vehicle_id.driver_id',
                               string='Owner',
                               store=True,
                               readonly=True)
    vehicle_id = fields.Many2one('workshop.vehicle',
                                 'Vehicle',
                                 required=True,
                                 help='Vehicle concerned by this log')
    cost_subtype_id = fields.Many2one(
        'workshop.service.type',
        'Type',
        help='Cost type purchased with this cost')
    amount = fields.Monetary('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)

    operation_type = fields.Selection([('action_taken', 'Action Taken'),
                                       ('complaint', 'Customers Complaint'),
                                       ('diagnosis', 'Diagnostic Finding')])
    parent_id = fields.Many2one('workshop.vehicle.cost',
                                'Parent',
                                help='Parent cost to this current cost')
    cost_ids = fields.One2many('workshop.vehicle.cost',
                               'parent_id',
                               'Included Services',
                               copy=True)
    odometer_id = fields.Many2one(
        'workshop.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")
    company_id = fields.Many2one('res.company',
                                 'Company',
                                 default=lambda self: self.env.company)
    currency_id = fields.Many2one('res.currency',
                                  related='company_id.currency_id')

    def _get_odometer(self):
        self.odometer = 0.0
        for record in self:
            record.odometer = False
            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['workshop.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_create_multi
    def create(self, vals_list):
        for data in vals_list:
            # 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['workshop.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(WorkshopVehicleCost, self).create(vals_list)
Пример #22
0
class BuyAdjust(models.Model):
    _name = "buy.adjust"
    _inherit = ['mail.thread']
    _description = u"采购变更单"
    _order = 'date desc, id desc'

    name = fields.Char(u'单据编号', copy=False, help=u'变更单编号,保存时可自动生成')
    order_id = fields.Many2one('buy.order',
                               u'原始单据',
                               states=READONLY_STATES,
                               copy=False,
                               ondelete='restrict',
                               help=u'要调整的原始购货订单,只能调整已确认且没有全部入库的购货订单')
    date = fields.Date(u'单据日期',
                       states=READONLY_STATES,
                       default=lambda self: fields.Date.context_today(self),
                       index=True,
                       copy=False,
                       help=u'变更单创建日期,默认是当前日期')
    line_ids = fields.One2many('buy.adjust.line',
                               'order_id',
                               u'变更单行',
                               states=READONLY_STATES,
                               copy=True,
                               help=u'变更单明细行,不允许为空')
    approve_uid = fields.Many2one('res.users',
                                  u'确认人',
                                  copy=False,
                                  ondelete='restrict',
                                  help=u'确认变更单的人')
    state = fields.Selection(BUY_ORDER_STATES,
                             u'确认状态',
                             index=True,
                             copy=False,
                             default='draft',
                             help=u'变更单确认状态')
    note = fields.Text(u'备注', help=u'单据备注')
    user_id = fields.Many2one(
        'res.users',
        u'经办人',
        ondelete='restrict',
        states=READONLY_STATES,
        default=lambda self: self.env.user,
        help=u'单据经办人',
    )
    company_id = fields.Many2one(
        'res.company',
        string=u'公司',
        change_default=True,
        default=lambda self: self.env['res.company']._company_default_get())

    def _get_vals(self, line):
        '''返回创建 buy order line 时所需数据'''
        return {
            'order_id': self.order_id.id,
            'goods_id': line.goods_id.id,
            'attribute_id': line.attribute_id.id,
            'quantity': line.quantity,
            'uom_id': line.uom_id.id,
            'price_taxed': line.price_taxed,
            'discount_rate': line.discount_rate,
            'discount_amount': line.discount_amount,
            'tax_rate': line.tax_rate,
            'note': line.note or '',
        }

    @api.one
    def buy_adjust_done(self):
        '''确认采购变更单:
        当调整后数量 < 原单据中已入库数量,则报错;
        当调整后数量 > 原单据中已入库数量,则更新原单据及入库单分单的数量;
        当调整后数量 = 原单据中已入库数量,则更新原单据数量,删除入库单分单;
        当新增商品时,则更新原单据及入库单分单明细行。
        '''
        if self.state == 'done':
            raise UserError(u'请不要重复确认!\n采购变更单%s已确认' % self.name)
        if not self.line_ids:
            raise UserError(u'请输入商品明细行!')
        for line in self.line_ids:
            if line.price_taxed < 0:
                raise UserError(u'商品含税单价不能小于0!\n单价:%s' % line.price_taxed)
        buy_receipt = self.env['buy.receipt'].search([('order_id', '=',
                                                       self.order_id.id),
                                                      ('state', '=', 'draft')])
        if not buy_receipt:
            raise UserError(u'采购入库单已全部入库,不能调整')
        for line in self.line_ids:
            origin_line = self.env['buy.order.line'].search([
                ('goods_id', '=', line.goods_id.id),
                ('attribute_id', '=', line.attribute_id.id),
                ('order_id', '=', self.order_id.id)
            ])
            if len(origin_line) > 1:
                raise UserError(u'要调整的商品%s在原始单据中不唯一' % line.goods_id.name)
            if origin_line:
                origin_line.quantity += line.quantity  # 调整后数量
                new_note = u'变更单:%s %s。\n' % (self.name, line.note)
                origin_line.note = (origin_line.note
                                    and origin_line.note + new_note
                                    or new_note)
                if origin_line.quantity < origin_line.quantity_in:
                    raise UserError(u'%s调整后数量不能小于原订单已入库数量' %
                                    line.goods_id.name)
                elif origin_line.quantity > origin_line.quantity_in:
                    # 查找出原购货订单产生的草稿状态的入库单明细行,并更新它
                    move_line = self.env['wh.move.line'].search([
                        ('buy_line_id', '=', origin_line.id),
                        ('state', '=', 'draft')
                    ])
                    if move_line:
                        move_line.goods_qty += line.quantity
                        move_line.note = (move_line.note and move_line.note
                                          or move_line.note + origin_line.note)
                    else:
                        raise UserError(u'商品%s已全部入库,建议新建购货订单' %
                                        line.goods_id.name)
                # 调整后数量与已入库数量相等时,删除产生的入库单分单
                else:
                    buy_receipt.unlink()
            else:
                new_line = self.env['buy.order.line'].create(
                    self._get_vals(line))
                receipt_line = []
                if line.goods_id.force_batch_one:
                    i = 0
                    while i < line.quantity:
                        i += 1
                        receipt_line.append(
                            self.order_id.get_receipt_line(new_line,
                                                           single=True))
                else:
                    receipt_line.append(
                        self.order_id.get_receipt_line(new_line, single=False))
                buy_receipt.write(
                    {'line_in_ids': [(0, 0, li[0]) for li in receipt_line]})
        self.state = 'done'
        self.approve_uid = self._uid
Пример #23
0
class AcademyTestsTest(models.Model):
    """ Stored tests which can be reused in future
    """

    _name = 'academy.tests.test'
    _description = u'Academy tests, test'

    _rec_name = 'name'
    _order = 'write_date DESC, create_date DESC'

    _inherit = ['image.mixin', 'mail.thread']

    name = fields.Char(string='Name',
                       required=True,
                       readonly=False,
                       index=True,
                       default=None,
                       help="Name for this test",
                       size=255,
                       translate=True,
                       track_visibility='onchange')

    description = fields.Text(string='Description',
                              required=False,
                              readonly=False,
                              index=False,
                              default=None,
                              help='Something about this test',
                              translate=True)

    active = fields.Boolean(
        string='Active',
        required=False,
        readonly=False,
        index=False,
        default=True,
        help=('If the active field is set to false, it will allow you to '
              'hide record without removing it'))

    code = fields.Char(string='Code',
                       required=False,
                       readonly=False,
                       index=True,
                       default=None,
                       help='Internal code',
                       size=20,
                       translate=False)

    preamble = fields.Text(string='Preamble',
                           required=False,
                           readonly=False,
                           index=False,
                           default=None,
                           help='What it is said before beginning to test',
                           translate=True)

    question_ids = fields.One2many(
        string='Questions',
        required=False,
        readonly=False,
        index=False,
        default=None,
        help=False,
        comodel_name='academy.tests.test.question.rel',
        inverse_name='test_id',
        domain=[],
        context={},
        auto_join=False,
        limit=None,
        # oldname='academy_question_ids'
    )

    answers_table_ids = fields.One2many(
        string='Answers table',
        required=False,
        readonly=True,
        index=False,
        default=None,
        help='Summary with answers table',
        comodel_name='academy.tests.answers.table',
        inverse_name='test_id',
        domain=[],
        context={},
        auto_join=False,
        limit=None,
        # oldname='academy_answers_table_ids'
    )

    random_template_id = fields.Many2one(
        string='Template',
        required=False,
        readonly=True,
        index=False,
        default=None,
        help='Template has been used to pupulate this tests',
        comodel_name='academy.tests.random.template',
        domain=[],
        context={},
        ondelete='cascade',
        auto_join=False)

    owner_id = fields.Many2one(
        string='Owner',
        required=True,
        readonly=False,
        index=False,
        default=lambda self: self._default_owner_id(),
        help='Current test owner',
        comodel_name='res.users',
        domain=[],
        context={},
        ondelete='cascade',
        auto_join=False,
    )

    test_kind_id = fields.Many2one(string='Kind of test',
                                   required=False,
                                   readonly=False,
                                   index=False,
                                   default=None,
                                   help='Choose the kind for this test',
                                   comodel_name='academy.tests.test.kind',
                                   domain=[],
                                   context={},
                                   ondelete='cascade',
                                   auto_join=False)

    first_use_id = fields.Many2one(string='First use',
                                   required=False,
                                   readonly=False,
                                   index=False,
                                   default=None,
                                   help=False,
                                   comodel_name='res.partner',
                                   domain=[],
                                   context={},
                                   ondelete='cascade',
                                   auto_join=False)

    training_action_ids = fields.Many2many(
        string='Training actions',
        required=False,
        readonly=False,
        index=False,
        default=None,
        help='Choose the training actions in which this test will be available',
        comodel_name='academy.training.action',
        relation='academy_tests_test_training_action_rel',
        column1='test_id',
        column2='training_action_id',
        domain=[],
        context={},
        limit=None)

    training_activity_ids = fields.Many2many(
        string='Training activities',
        required=False,
        readonly=False,
        index=False,
        default=None,
        help=
        'Choose the training activities in which this test will be available',
        comodel_name='academy.training.activity',
        relation='academy_tests_test_training_activity_rel',
        column1='test_id',
        column2='training_activity_id',
        domain=[],
        context={},
        limit=None)

    training_module_ids = fields.Many2many(
        string='Training modules',
        required=False,
        readonly=False,
        index=False,
        default=None,
        help='Choose the training modules in which this test will be available',
        comodel_name='academy.training.module',
        relation='academy_tests_test_training_module_rel',
        column1='test_id',
        column2='training_module_id',
        domain=[('training_module_id', '=', False)],
        context={},
        limit=None)

    competency_unit_ids = fields.Many2many(
        string='Competency units',
        required=False,
        readonly=False,
        index=False,
        default=None,
        help='Choose the competency units in which this test will be available',
        comodel_name='academy.competency.unit',
        relation='academy_tests_test_competency_unit_rel',
        column1='test_id',
        column2='competency_unit_id',
        domain=[],
        context={},
        limit=None)

    lesson_ids = fields.Many2many(
        string='Lessons',
        required=False,
        readonly=False,
        index=False,
        default=None,
        help='Choose the lessons in which this test will be available',
        comodel_name='academy.training.lesson',
        relation='academy_tests_test_training_lesson_rel',
        column1='test_id',
        column2='lesson_id',
        domain=[],
        context={},
        limit=None)

    time_by = fields.Selection(string='Time by',
                               required=False,
                               readonly=False,
                               index=False,
                               default=False,
                               help=False,
                               selection=[('test', 'Test'),
                                          ('question', 'Question')])

    available_time = fields.Float(
        string='Time',
        required=False,
        readonly=False,
        index=False,
        default=0.0,
        digits=(16, 2),
        help='Available time to complete the exercise')

    lock_time = fields.Boolean(
        string='Lock time',
        required=False,
        readonly=False,
        index=False,
        default=True,
        help=
        'Check to not allow the user to continue with the test once the time has passed'
    )

    correction_scale_id = fields.Many2one(
        string='Correction scale',
        required=False,
        readonly=False,
        index=False,
        default=None,
        help='Choose the scale of correction',
        comodel_name='academy.tests.correction.scale',
        domain=[],
        context={},
        ondelete='cascade',
        auto_join=False)

    available_in = fields.One2many(
        string='Available in',
        required=False,
        readonly=True,
        index=False,
        default=None,
        help='This test is directly related to',
        comodel_name='academy.tests.test.availability',
        inverse_name='test_id',
        domain=[],
        context={},
        auto_join=False,
        limit=None)

    # -------------------------- MANAGEMENT FIELDS ----------------------------

    question_count = fields.Integer(string='Number of questions',
                                    required=False,
                                    readonly=False,
                                    index=False,
                                    default=0,
                                    help='Number of questions in test',
                                    compute='_compute_question_count')

    @api.depends('question_ids')
    def _compute_question_count(self):
        for record in self:
            record.question_count = len(record.question_ids)

    topic_ids = Many2manyThroughView(string='Topics',
                                     required=False,
                                     readonly=True,
                                     index=False,
                                     default=None,
                                     help=False,
                                     comodel_name='academy.tests.topic',
                                     relation='academy_tests_test_topic_rel',
                                     column1='test_id',
                                     column2='topic_id',
                                     domain=[],
                                     context={},
                                     limit=None,
                                     sql=ACADEMY_TESTS_TEST_TOPIC_IDS_SQL)

    topic_count = fields.Integer(
        string='Number of topics',
        required=False,
        readonly=True,
        index=False,
        default=0,
        help='Display the number of topics related with test',
        compute=lambda self: self._compute_topic_count())

    @api.depends('question_ids')
    def _compute_topic_count(self):
        for record in self:
            question_set = record.question_ids.mapped('question_id')
            topic_set = question_set.mapped('topic_id')
            ids = topic_set.mapped('id')

            record.topic_count = len(ids)

    topic_id = fields.Many2one(string='Topic',
                               required=False,
                               readonly=True,
                               index=False,
                               default=None,
                               help=False,
                               comodel_name='academy.tests.topic',
                               domain=[],
                               context={},
                               ondelete='cascade',
                               auto_join=False,
                               compute=lambda self: self._compute_topic_id())

    @api.depends('question_ids')
    def _compute_topic_id(self):
        for record in self:
            rel_ids = record.question_ids.filtered(
                lambda rel: rel.question_id.topic_id)
            question_ids = rel_ids.mapped('question_id')
            topics = {k.id: 0 for k in question_ids.mapped('topic_id')}

            if not topics:
                record.topic_id = None
            else:
                for question_id in question_ids:
                    _id = question_id.topic_id.id
                    topics[_id] = topics[_id] + 1

                topic_id = max(topics.items(), key=itemgetter(1))[0]

                topic_obj = self.env['academy.tests.topic']
                record.topic_id = topic_obj.browse(topic_id)

    available_in_enrolment_ids = Many2manyThroughView(
        string='Tests',
        required=False,
        readonly=False,
        index=False,
        default=None,
        help='Choose the enrolments in which this test will be available',
        comodel_name='academy.training.action.enrolment',
        relation=
        'academy_tests_test_available_in_training_action_enrolment_rel',
        column1='test_id',
        column2='enrolment_id',
        domain=[],
        context={},
        limit=None,
        sql=ACADEMY_ENROLMENT_AVAILABLE_TESTS)

    lang = fields.Char(
        string='Language',
        required=True,
        readonly=True,
        index=False,
        help=False,
        size=255,
        translate=False,
        compute='_compute_lang',
    )

    # ----------------------- AUXILIARY FIELD METHODS -------------------------

    def _default_owner_id(self):
        uid = 1
        if 'uid' in self.env.context:
            uid = self.env.context['uid']

        return uid

    @api.depends('name')
    def _compute_lang(self):
        """ Gets the language used by the current user and sets it as `lang`
            field value
        """

        user_id = self.env['res.users'].browse(self.env.uid)

        for record in self:
            record.lang = user_id.lang

    def import_questions(self):
        """ Runs a wizard to import questions from plain text
        @note: actually this method is not used
        """
        return {
            'type': 'ir.actions.act_window',
            'res_model': 'academy.tests.question.import.wizard',
            'view_mode': 'form',
            'views': [(False, 'form')],
            'target': 'new',
            'context': {
                'default_test_id': self.id
            }
        }

    def random_questions(self):
        """ Runs wizard to append random questions. This allows uses to set
        filter criteria, maximum number of questions, etc.
        """
        return {
            'type': 'ir.actions.act_window',
            'res_model': 'academy.tests.random.wizard',
            'view_mode': 'form',
            'views': [(False, 'form')],
            'target': 'new',
            'context': {
                'default_test_id': self.id
            }
        }

    def show_questions(self):
        """ Runs default view for academy.tests.question with a filter to
        show only current test questions
        """

        act_wnd = self.env.ref(
            'academy_tests.action_questions_keep_items_act_window')

        if act_wnd.domain:
            if isinstance(act_wnd.domain, str):
                domain = safe_eval(act_wnd.domain)
            else:
                domain = act_wnd.domain
        else:
            domain = []

        ids = self.question_ids.mapped('question_id').mapped('id')
        domain.append(('id', 'in', ids))

        values = {
            'type': act_wnd['type'],
            'name': act_wnd['name'],
            'res_model': act_wnd['res_model'],
            'view_mode': act_wnd['view_mode'],
            'target': act_wnd['target'],
            'domain': domain,
            'context': self.env.context,
            'limit': act_wnd['limit'],
            'help': act_wnd['help'],
            'view_ids': act_wnd['view_ids'],
            'views': act_wnd['views']
        }

        if act_wnd.search_view_id:
            values['search_view_id'] = act_wnd['search_view_id'].id

        return values

    @api.model
    def create(self, values):
        """ Create a new record for a model AcademyTestsTest
            @param values: provides a data for new record

            @return: returns a id of new record
        """
        result = super(AcademyTestsTest, self).create(values)
        result.resequence()

        return result

    def write(self, values):
        """ Update all record(s) in recordset, with new value comes as {values}
            @param values: dict of new values to be set

            @return: True on success, False otherwise
        """

        result = super(AcademyTestsTest, self).write(values)
        self.resequence()

        return result

    @api.returns('self', lambda value: value.id)
    def copy(self, default=None):
        self.ensure_one()

        # STEP 1: Ensure default is a dictionary
        if default is None:
            default = {}

        # STEP 2: Make new name adding ``(copy)``
        if 'name' not in default:
            default['name'] = _("%s (copy)") % self.name

        # STEP 3: Create new links for all questions in the original test
        if self.question_ids:
            leafs = self.question_ids.mapped(self._mapped_question_ids)
            if (leafs):
                default['question_ids'] = leafs

        # STEP 4: Call parent method
        result = super(AcademyTestsTest, self).copy(default=default)

        return result

    @staticmethod
    def _mapped_question_ids(item):
        return (0, 0, {
            'test_id': item.test_id.id,
            'question_id': item.question_id.id,
            'sequence': item.sequence,
            'active': item.active
        })

    def resequence(self):
        """ This updates the sequence of the questions into the test
        """

        # order_by = 'sequence ASC, write_date ASC, create_date ASC, id ASC'
        # rel_domain = [('test_id', '=', self.id)]
        # rel_obj = self.env['academy.tests.test.question.rel']
        # rel_set = rel_obj.search(rel_domain, order=order_by)

        for record in self:
            rel_set = record.question_ids.sorted()

            index = 1
            for rel_item in rel_set:
                rel_item.write({'sequence': index})
                index = index + 1

    def shuffle(self):
        qpositions = list(range(0, len(self.question_ids)))
        sequence = 1

        random_shuffle(qpositions)

        for qposition in qpositions:
            self.question_ids[qposition].sequence = sequence
            sequence += 1

    def _creation_subtype(self):
        xid = 'academy_tests.academy_tests_test_created'
        return self.env.ref(xid)

    def _track_subtype(self, init_values):
        self.ensure_one()

        xid = 'academy_tests.academy_tests_test_written'
        return self.env.ref(xid)

    @api.returns('mail.message', lambda value: value.id)
    def message_post(self,
                     *,
                     body='',
                     subject=None,
                     message_type='notification',
                     email_from=None,
                     author_id=None,
                     parent_id=False,
                     subtype_id=False,
                     subtype=None,
                     partner_ids=None,
                     channel_ids=None,
                     attachments=None,
                     attachment_ids=None,
                     add_sign=True,
                     record_name=False,
                     **kwargs):

        for record in self:
            for enrolment in record.available_in_enrolment_ids:
                enrolment.message_post(body=body,
                                       subject=subject,
                                       message_type=message_type,
                                       email_from=email_from,
                                       author_id=author_id,
                                       parent_id=parent_id,
                                       subtype_id=subtype_id,
                                       subtype=subtype,
                                       partner_ids=partner_ids,
                                       channel_ids=channel_ids,
                                       attachments=attachments,
                                       attachment_ids=attachment_ids,
                                       add_sign=add_sign,
                                       record_name=record_name,
                                       **kwargs)

        return super(AcademyTestsTest,
                     self).message_post(body=body,
                                        subject=subject,
                                        message_type=message_type,
                                        email_from=email_from,
                                        author_id=author_id,
                                        parent_id=parent_id,
                                        subtype_id=subtype_id,
                                        subtype=subtype,
                                        partner_ids=partner_ids,
                                        channel_ids=channel_ids,
                                        attachments=attachments,
                                        attachment_ids=attachment_ids,
                                        add_sign=add_sign,
                                        record_name=record_name,
                                        **kwargs)
Пример #24
0
class HolidaysAllocation(models.Model):
    """ Allocation Requests Access specifications: similar to leave requests """
    _name = "hr.leave.allocation"
    _description = "Time Off Allocation"
    _order = "create_date desc"
    _inherit = ['mail.thread', 'mail.activity.mixin']
    _mail_post_access = 'read'

    def _default_holiday_status_id(self):
        if self.user_has_groups('hr_holidays.group_hr_holidays_user'):
            domain = [('has_valid_allocation', '=', True), ('requires_allocation', '=', 'yes')]
        else:
            domain = [('has_valid_allocation', '=', True), ('requires_allocation', '=', 'yes'), ('employee_requests', '=', 'yes')]
        return self.env['hr.leave.type'].search(domain, limit=1)

    def _domain_holiday_status_id(self):
        if self.user_has_groups('hr_holidays.group_hr_holidays_user'):
            return [('requires_allocation', '=', 'yes')]
        return [('employee_requests', '=', 'yes')]

    name = fields.Char('Description', compute='_compute_description', inverse='_inverse_description', search='_search_description', compute_sudo=False)
    name_validity = fields.Char('Description with validity', compute='_compute_description_validity')
    active = fields.Boolean(default=True)
    private_name = fields.Char('Allocation Description', groups='hr_holidays.group_hr_holidays_user')
    state = fields.Selection([
        ('draft', 'To Submit'),
        ('cancel', 'Cancelled'),
        ('confirm', 'To Approve'),
        ('refuse', 'Refused'),
        ('validate', 'Approved')
        ], string='Status', readonly=True, tracking=True, copy=False, default='draft',
        help="The status is set to 'To Submit', when an allocation request is created." +
        "\nThe status is 'To Approve', when an allocation request is confirmed by user." +
        "\nThe status is 'Refused', when an allocation request is refused by manager." +
        "\nThe status is 'Approved', when an allocation request is approved by manager.")
    date_from = fields.Date('Start Date', index=True, copy=False, default=fields.Date.context_today,
        states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, tracking=True, required=True)
    date_to = fields.Date('End Date', copy=False, tracking=True,
        states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]})
    holiday_status_id = fields.Many2one(
        "hr.leave.type", compute='_compute_holiday_status_id', store=True, string="Time Off Type", required=True, readonly=False,
        states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]},
        domain=_domain_holiday_status_id,
        default=_default_holiday_status_id)
    employee_id = fields.Many2one(
        'hr.employee', compute='_compute_from_employee_ids', store=True, string='Employee', index=True, readonly=False, ondelete="restrict", tracking=True,
        states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate': [('readonly', True)]})
    employee_company_id = fields.Many2one(related='employee_id.company_id', readonly=True, store=True)
    active_employee = fields.Boolean('Active Employee', related='employee_id.active', readonly=True)
    manager_id = fields.Many2one('hr.employee', compute='_compute_manager_id', store=True, string='Manager')
    notes = fields.Text('Reasons', readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
    # duration
    number_of_days = fields.Float(
        'Number of Days', compute='_compute_from_holiday_status_id', store=True, readonly=False, tracking=True, default=1,
        help='Duration in days. Reference field to use when necessary.')
    number_of_days_display = fields.Float(
        'Duration (days)', compute='_compute_number_of_days_display',
        states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]},
        help="If Accrual Allocation: Number of days allocated in addition to the ones you will get via the accrual' system.")
    number_of_hours_display = fields.Float(
        'Duration (hours)', compute='_compute_number_of_hours_display',
        help="If Accrual Allocation: Number of hours allocated in addition to the ones you will get via the accrual' system.")
    duration_display = fields.Char('Allocated (Days/Hours)', compute='_compute_duration_display',
        help="Field allowing to see the allocation duration in days or hours depending on the type_request_unit")
    # details
    parent_id = fields.Many2one('hr.leave.allocation', string='Parent')
    linked_request_ids = fields.One2many('hr.leave.allocation', 'parent_id', string='Linked Requests')
    approver_id = fields.Many2one(
        'hr.employee', string='First Approval', readonly=True, copy=False,
        help='This area is automatically filled by the user who validates the allocation')
    validation_type = fields.Selection(string='Validation Type', related='holiday_status_id.allocation_validation_type', readonly=True)
    can_reset = fields.Boolean('Can reset', compute='_compute_can_reset')
    can_approve = fields.Boolean('Can Approve', compute='_compute_can_approve')
    type_request_unit = fields.Selection(related='holiday_status_id.request_unit', readonly=True)
    # mode
    holiday_type = fields.Selection([
        ('employee', 'By Employee'),
        ('company', 'By Company'),
        ('department', 'By Department'),
        ('category', 'By Employee Tag')],
        string='Allocation Mode', readonly=True, required=True, default='employee',
        states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]},
        help="Allow to create requests in batchs:\n- By Employee: for a specific employee"
             "\n- By Company: all employees of the specified company"
             "\n- By Department: all employees of the specified department"
             "\n- By Employee Tag: all employees of the specific employee group category")
    employee_ids = fields.Many2many(
        'hr.employee', compute='_compute_from_holiday_type', store=True, string='Employees', readonly=False,
        states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate': [('readonly', True)]})
    multi_employee = fields.Boolean(
        compute='_compute_from_employee_ids', store=True,
        help='Holds whether this allocation concerns more than 1 employee')
    mode_company_id = fields.Many2one(
        'res.company', compute='_compute_from_holiday_type', store=True, string='Company Mode', readonly=False,
        states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate': [('readonly', True)]})
    department_id = fields.Many2one(
        'hr.department', compute='_compute_department_id', store=True, string='Department',
        states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
    category_id = fields.Many2one(
        'hr.employee.category', compute='_compute_from_holiday_type', store=True, string='Employee Tag', readonly=False,
        states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate': [('readonly', True)]})
    # accrual configuration
    lastcall = fields.Date("Date of the last accrual allocation", readonly=True, default=fields.Date.context_today)
    nextcall = fields.Date("Date of the next accrual allocation", default=False, readonly=True)
    allocation_type = fields.Selection(
        [
            ('regular', 'Regular Allocation'),
            ('accrual', 'Accrual Allocation')
        ], string="Allocation Type", default="regular", required=True, readonly=True,
        states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
    is_officer = fields.Boolean(compute='_compute_is_officer')
    accrual_plan_id = fields.Many2one('hr.leave.accrual.plan', compute="_compute_from_holiday_status_id", store=True, readonly=False, domain="['|', ('time_off_type_id', '=', False), ('time_off_type_id', '=', holiday_status_id)]", tracking=True)
    max_leaves = fields.Float(compute='_compute_leaves')
    leaves_taken = fields.Float(compute='_compute_leaves')
    taken_leave_ids = fields.One2many('hr.leave', 'holiday_allocation_id', domain="[('state', 'in', ['confirm', 'validate1', 'validate'])]")

    _sql_constraints = [
        ('type_value',
         "CHECK( (holiday_type='employee' AND (employee_id IS NOT NULL OR multi_employee IS TRUE)) or "
         "(holiday_type='category' AND category_id IS NOT NULL) or "
         "(holiday_type='department' AND department_id IS NOT NULL) or "
         "(holiday_type='company' AND mode_company_id IS NOT NULL))",
         "The employee, department, company or employee category of this request is missing. Please make sure that your user login is linked to an employee."),
        ('duration_check', "CHECK( ( number_of_days > 0 AND allocation_type='regular') or (allocation_type != 'regular'))", "The duration must be greater than 0."),
    ]

    # The compute does not get triggered without a depends on record creation
    # aka keep the 'useless' depends
    @api.depends_context('uid')
    @api.depends('allocation_type')
    def _compute_is_officer(self):
        self.is_officer = self.env.user.has_group("hr_holidays.group_hr_holidays_user")

    @api.depends_context('uid')
    def _compute_description(self):
        self.check_access_rights('read')
        self.check_access_rule('read')

        is_officer = self.env.user.has_group('hr_holidays.group_hr_holidays_user')

        for allocation in self:
            if is_officer or allocation.employee_id.user_id == self.env.user or allocation.employee_id.leave_manager_id == self.env.user:
                allocation.name = allocation.sudo().private_name
            else:
                allocation.name = '*****'

    def _inverse_description(self):
        is_officer = self.env.user.has_group('hr_holidays.group_hr_holidays_user')
        for allocation in self:
            if is_officer or allocation.employee_id.user_id == self.env.user or allocation.employee_id.leave_manager_id == self.env.user:
                allocation.sudo().private_name = allocation.name

    def _search_description(self, operator, value):
        is_officer = self.env.user.has_group('hr_holidays.group_hr_holidays_user')
        domain = [('private_name', operator, value)]

        if not is_officer:
            domain = expression.AND([domain, [('employee_id.user_id', '=', self.env.user.id)]])

        allocations = self.sudo().search(domain)
        return [('id', 'in', allocations.ids)]

    @api.depends('name', 'date_from', 'date_to')
    def _compute_description_validity(self):
        for allocation in self:
            if allocation.date_to:
                name_validity = _("%s (from %s to %s)", allocation.name, allocation.date_from.strftime("%b %d %Y"), allocation.date_to.strftime("%b %d %Y"))
            else:
                name_validity = _("%s (from %s to No Limit)", allocation.name, allocation.date_from.strftime("%b %d %Y"))
            allocation.name_validity = name_validity

    @api.depends('employee_id', 'holiday_status_id', 'taken_leave_ids.number_of_days', 'taken_leave_ids.state')
    def _compute_leaves(self):
        for allocation in self:
            allocation.max_leaves = allocation.number_of_hours_display if allocation.type_request_unit == 'hour' else allocation.number_of_days
            allocation.leaves_taken = sum(taken_leave.number_of_hours_display if taken_leave.leave_type_request_unit == 'hour' else taken_leave.number_of_days\
                for taken_leave in allocation.taken_leave_ids\
                if taken_leave.state == 'validate')

    @api.depends('number_of_days')
    def _compute_number_of_days_display(self):
        for allocation in self:
            allocation.number_of_days_display = allocation.number_of_days

    @api.depends('number_of_days', 'employee_id')
    def _compute_number_of_hours_display(self):
        for allocation in self:
            if allocation.parent_id and allocation.parent_id.type_request_unit == "hour":
                allocation.number_of_hours_display = allocation.number_of_days * HOURS_PER_DAY
            elif allocation.number_of_days:
                allocation.number_of_hours_display = allocation.number_of_days * (allocation.employee_id.sudo().resource_id.calendar_id.hours_per_day or HOURS_PER_DAY)
            else:
                allocation.number_of_hours_display = 0.0

    @api.depends('number_of_hours_display', 'number_of_days_display')
    def _compute_duration_display(self):
        for allocation in self:
            allocation.duration_display = '%g %s' % (
                (float_round(allocation.number_of_hours_display, precision_digits=2)
                if allocation.type_request_unit == 'hour'
                else float_round(allocation.number_of_days_display, precision_digits=2)),
                _('hours') if allocation.type_request_unit == 'hour' else _('days'))

    @api.depends('state', 'employee_id', 'department_id')
    def _compute_can_reset(self):
        for allocation in self:
            try:
                allocation._check_approval_update('draft')
            except (AccessError, UserError):
                allocation.can_reset = False
            else:
                allocation.can_reset = True

    @api.depends('state', 'employee_id', 'department_id')
    def _compute_can_approve(self):
        for allocation in self:
            try:
                if allocation.state == 'confirm' and allocation.validation_type != 'no':
                    allocation._check_approval_update('validate')
            except (AccessError, UserError):
                allocation.can_approve = False
            else:
                allocation.can_approve = True

    @api.depends('employee_ids')
    def _compute_from_employee_ids(self):
        for allocation in self:
            if len(allocation.employee_ids) == 1:
                allocation.employee_id = allocation.employee_ids[0]._origin
            else:
                allocation.employee_id = False
            allocation.multi_employee = (len(allocation.employee_ids) > 1)

    @api.depends('holiday_type')
    def _compute_from_holiday_type(self):
        for allocation in self:
            if allocation.holiday_type == 'employee':
                if not allocation.employee_ids:
                    allocation.employee_ids = self.env.user.employee_id
                allocation.mode_company_id = False
                allocation.category_id = False
            if allocation.holiday_type == 'company':
                allocation.employee_ids = False
                if not allocation.mode_company_id:
                    allocation.mode_company_id = self.env.company
                allocation.category_id = False
            elif allocation.holiday_type == 'department':
                allocation.employee_ids = False
                allocation.mode_company_id = False
                allocation.category_id = False
            elif allocation.holiday_type == 'category':
                allocation.employee_ids = False
                allocation.mode_company_id = False
            else:
                allocation.employee_ids = self.env.context.get('default_employee_id') or self.env.user.employee_id

    @api.depends('holiday_type', 'employee_id')
    def _compute_department_id(self):
        for allocation in self:
            if allocation.holiday_type == 'employee':
                allocation.department_id = allocation.employee_id.department_id
            elif allocation.holiday_type == 'department':
                if not allocation.department_id:
                    allocation.department_id = self.env.user.employee_id.department_id
            elif allocation.holiday_type == 'category':
                allocation.department_id = False

    @api.depends('employee_id')
    def _compute_manager_id(self):
        for allocation in self:
            allocation.manager_id = allocation.employee_id and allocation.employee_id.parent_id

    @api.depends('accrual_plan_id')
    def _compute_holiday_status_id(self):
        default_holiday_status_id = None
        for holiday in self:
            if not holiday.holiday_status_id:
                if holiday.accrual_plan_id:
                    holiday.holiday_status_id = holiday.accrual_plan_id.time_off_type_id
                else:
                    if not default_holiday_status_id:  # fetch when we need it
                        default_holiday_status_id = self._default_holiday_status_id()
                    holiday.holiday_status_id = default_holiday_status_id

    @api.depends('holiday_status_id', 'allocation_type', 'number_of_hours_display', 'number_of_days_display', 'date_to')
    def _compute_from_holiday_status_id(self):
        accrual_allocations = self.filtered(lambda alloc: alloc.allocation_type == 'accrual' and not alloc.accrual_plan_id and alloc.holiday_status_id)
        accruals_dict = {}
        if accrual_allocations:
            accruals_read_group = self.env['hr.leave.accrual.plan'].read_group(
                [('time_off_type_id', 'in', accrual_allocations.holiday_status_id.ids)],
                ['time_off_type_id', 'ids:array_agg(id)'],
                ['time_off_type_id'],
            )
            accruals_dict = {res['time_off_type_id'][0]: res['ids'] for res in accruals_read_group}
        for allocation in self:
            allocation.number_of_days = allocation.number_of_days_display
            if allocation.type_request_unit == 'hour':
                allocation.number_of_days = allocation.number_of_hours_display / (allocation.employee_id.sudo().resource_calendar_id.hours_per_day or HOURS_PER_DAY)
            if allocation.accrual_plan_id.time_off_type_id.id not in (False, allocation.holiday_status_id.id):
                allocation.accrual_plan_id = False
            if allocation.allocation_type == 'accrual' and not allocation.accrual_plan_id:
                if allocation.holiday_status_id:
                    allocation.accrual_plan_id = accruals_dict.get(allocation.holiday_status_id.id, [False])[0]

    def _end_of_year_accrual(self):
        # to override in payroll
        today = fields.Date.today()
        for allocation in self:
            current_level = allocation._get_current_accrual_plan_level_id(today)[0]
            if current_level and current_level.action_with_unused_accruals == 'lost':
                # Allocations are lost but number_of_days should not be lower than leaves_taken
                allocation.write({'number_of_days': allocation.leaves_taken, 'lastcall': today, 'nextcall': False})

    def _get_current_accrual_plan_level_id(self, date, level_ids=False):
        """
        Returns a pair (accrual_plan_level, idx) where accrual_plan_level is the level for the given date
         and idx is the index for the plan in the ordered set of levels
        """
        self.ensure_one()
        if not self.accrual_plan_id.level_ids:
            return (False, False)
        # Sort by sequence which should be equivalent to the level
        if not level_ids:
            level_ids = self.accrual_plan_id.level_ids.sorted('sequence')
        current_level = False
        current_level_idx = -1
        for idx, level in enumerate(level_ids):
            if date >= self.date_from + get_timedelta(level.start_count, level.start_type):
                current_level = level
                current_level_idx = idx
        # If transition_mode is set to `immediately` or we are currently on the first level
        # the current_level is simply the first level in the list.
        if current_level_idx <= 0 or self.accrual_plan_id.transition_mode == "immediately":
            return (current_level, current_level_idx)
        # In this case we have to verify that the 'previous level' is not the current one due to `end_of_accrual`
        level_start_date = self.date_from + get_timedelta(current_level.start_count, current_level.start_type)
        previous_level = level_ids[current_level_idx - 1]
        # If the next date from the current level's start date is before the last call of the previous level
        # return the previous level
        if current_level._get_next_date(level_start_date) < previous_level._get_next_date(level_start_date):
            return (previous_level, current_level_idx - 1)
        return (current_level, current_level_idx)

    def _process_accrual_plan_level(self, level, start_period, start_date, end_period, end_date):
        """
        Returns the added days for that level
        """
        self.ensure_one()
        if level.is_based_on_worked_time:
            start_dt = datetime.combine(start_date, datetime.min.time())
            end_dt = datetime.combine(end_date, datetime.max.time())
            worked = self.employee_id._get_work_days_data_batch(start_dt, end_dt, calendar=self.employee_id.resource_calendar_id)\
                [self.employee_id.id]['hours']
            left = self.employee_id.sudo()._get_leave_days_data_batch(start_dt, end_dt,
                domain=[('time_type', '=', 'leave')])[self.employee_id.id]['hours']
            work_entry_prorata = worked / (left + worked) if worked else 0
            added_value = work_entry_prorata * level.added_value
        else:
            added_value = level.added_value
        # Convert time in hours to time in days in case the level is encoded in hours
        if level.added_value_type == 'hours':
            added_value = added_value / (self.employee_id.sudo().resource_id.calendar_id.hours_per_day or HOURS_PER_DAY)
        period_prorata = 1
        if start_period != start_date or end_period != end_date:
            period_days = (end_period - start_period)
            call_days = (end_date - start_date)
            period_prorata = min(1, call_days / period_days) if period_days else 1
        return added_value * period_prorata

    def _process_accrual_plans(self):
        """
        This method is part of the cron's process.
        The goal of this method is to retroactively apply accrual plan levels and progress from nextcall to today
        """
        today = fields.Date.today()
        first_allocation = _("""This allocation have already ran once, any modification won't be effective to the days allocated to the employee. If you need to change the configuration of the allocation, cancel and create a new one.""")
        for allocation in self:
            level_ids = allocation.accrual_plan_id.level_ids.sorted('sequence')
            if not level_ids:
                continue
            if not allocation.nextcall:
                first_level = level_ids[0]
                first_level_start_date = allocation.date_from + get_timedelta(first_level.start_count, first_level.start_type)
                if today < first_level_start_date:
                    # Accrual plan is not configured properly or has not started
                    continue
                allocation.lastcall = max(allocation.lastcall, first_level_start_date)
                allocation.nextcall = first_level._get_next_date(allocation.lastcall)
                allocation._message_log(body=first_allocation)
            days_added_per_level = defaultdict(lambda: 0)
            while allocation.nextcall <= today:
                (current_level, current_level_idx) = allocation._get_current_accrual_plan_level_id(allocation.nextcall)
                nextcall = current_level._get_next_date(allocation.nextcall)
                # Since _get_previous_date returns the given date if it corresponds to a call date
                # this will always return lastcall except possibly on the first call
                # this is used to prorate the first number of days given to the employee
                period_start = current_level._get_previous_date(allocation.lastcall)
                period_end = current_level._get_next_date(allocation.lastcall)
                # Also prorate this accrual in the event that we are passing from one level to another
                if current_level_idx < (len(level_ids) - 1) and allocation.accrual_plan_id.transition_mode == 'immediately':
                    next_level = level_ids[current_level_idx + 1]
                    current_level_last_date = allocation.date_from + get_timedelta(next_level.start_count, next_level.start_type) - relativedelta(days=1)
                    if allocation.nextcall != current_level_last_date:
                        nextcall = min(nextcall, current_level_last_date)
                days_added_per_level[current_level] += allocation._process_accrual_plan_level(
                    current_level, period_start, allocation.lastcall, period_end, allocation.nextcall)
                allocation.lastcall = allocation.nextcall
                allocation.nextcall = nextcall
            if days_added_per_level:
                number_of_days_to_add = 0
                for value in days_added_per_level.values():
                    number_of_days_to_add += value
                # Let's assume the limit of the last level is the correct one
                allocation.write({'number_of_days': min(allocation.number_of_days + number_of_days_to_add, current_level.maximum_leave)})

    @api.model
    def _update_accrual(self):
        """
            Method called by the cron task in order to increment the number_of_days when
            necessary.
        """
        # Get the current date to determine the start and end of the accrual period
        today = datetime.combine(fields.Date.today(), time(0, 0, 0))
        if today.day == 1 and today.month == 1:
            end_of_year_allocations = self.search(
            [('allocation_type', '=', 'accrual'), ('state', '=', 'validate'), ('accrual_plan_id', '!=', False), ('employee_id', '!=', False),
             '|', ('date_to', '=', False), ('date_to', '>', fields.Datetime.now())])
            end_of_year_allocations._end_of_year_accrual()
            end_of_year_allocations.flush()
        allocations = self.search(
        [('allocation_type', '=', 'accrual'), ('state', '=', 'validate'), ('accrual_plan_id', '!=', False), ('employee_id', '!=', False),
            '|', ('date_to', '=', False), ('date_to', '>', fields.Datetime.now()),
            '|', ('nextcall', '=', False), ('nextcall', '<=', today)])
        allocations._process_accrual_plans()

    ####################################################
    # ORM Overrides methods
    ####################################################

    def onchange(self, values, field_name, field_onchange):
        # Try to force the leave_type name_get when creating new records
        # This is called right after pressing create and returns the name_get for
        # most fields in the view.
        if 'employee_id' in field_onchange:
            self = self.with_context(employee_id=int(field_onchange['employee_id']))
        return super().onchange(values, field_name, field_onchange)

    def name_get(self):
        res = []
        for allocation in self:
            if allocation.holiday_type == 'company':
                target = allocation.mode_company_id.name
            elif allocation.holiday_type == 'department':
                target = allocation.department_id.name
            elif allocation.holiday_type == 'category':
                target = allocation.category_id.name
            elif allocation.employee_id:
                target = allocation.employee_id.name
            else:
                target = ', '.join(allocation.employee_ids.sudo().mapped('name'))

            res.append(
                (allocation.id,
                 _("Allocation of %(allocation_name)s : %(duration).2f %(duration_type)s to %(person)s",
                   allocation_name=allocation.holiday_status_id.sudo().name,
                   duration=allocation.number_of_hours_display if allocation.type_request_unit == 'hour' else allocation.number_of_days,
                   duration_type=_('hours') if allocation.type_request_unit == 'hour' else _('days'),
                   person=target
                ))
            )
        return res

    def add_follower(self, employee_id):
        employee = self.env['hr.employee'].browse(employee_id)
        if employee.user_id:
            self.message_subscribe(partner_ids=employee.user_id.partner_id.ids)

    @api.model_create_multi
    def create(self, vals_list):
        """ Override to avoid automatic logging of creation """
        for values in vals_list:
            employee_id = values.get('employee_id', False)
            if not values.get('department_id'):
                values.update({'department_id': self.env['hr.employee'].browse(employee_id).department_id.id})
            # default `lastcall` to `nextcall`
            if 'date_from' in values and 'lastcall' not in values:
                values['lastcall'] = values['date_from']
        holidays = super(HolidaysAllocation, self.with_context(mail_create_nosubscribe=True)).create(vals_list)
        for holiday in holidays:
            partners_to_subscribe = set()
            if holiday.employee_id.user_id:
                partners_to_subscribe.add(holiday.employee_id.user_id.partner_id.id)
            if holiday.validation_type == 'hr':
                partners_to_subscribe.add(holiday.employee_id.parent_id.user_id.partner_id.id)
                partners_to_subscribe.add(holiday.employee_id.leave_manager_id.partner_id.id)
            holiday.message_subscribe(partner_ids=tuple(partners_to_subscribe))
            if not self._context.get('import_file'):
                holiday.activity_update()
            if holiday.validation_type == 'no':
                if holiday.state == 'draft':
                    holiday.action_confirm()
                    holiday.action_validate()
        return holidays

    def write(self, values):
        employee_id = values.get('employee_id', False)
        if values.get('state'):
            self._check_approval_update(values['state'])
        result = super(HolidaysAllocation, self).write(values)
        self.add_follower(employee_id)
        return result

    @api.ondelete(at_uninstall=False)
    def _unlink_if_correct_states(self):
        state_description_values = {elem[0]: elem[1] for elem in self._fields['state']._description_selection(self.env)}
        for holiday in self.filtered(lambda holiday: holiday.state not in ['draft', 'cancel', 'confirm']):
            raise UserError(_('You cannot delete an allocation request which is in %s state.') % (state_description_values.get(holiday.state),))

    def _get_mail_redirect_suggested_company(self):
        return self.holiday_status_id.company_id

    ####################################################
    # Business methods
    ####################################################

    def _prepare_holiday_values(self, employees):
        self.ensure_one()
        return [{
            'name': self.name,
            'holiday_type': 'employee',
            'holiday_status_id': self.holiday_status_id.id,
            'notes': self.notes,
            'number_of_days': self.number_of_days,
            'parent_id': self.id,
            'employee_id': employee.id,
            'employee_ids': [(6, 0, [employee.id])],
            'state': 'confirm',
            'allocation_type': self.allocation_type,
            'date_from': self.date_from,
            'date_to': self.date_to,
            'accrual_plan_id': self.accrual_plan_id.id,
        } for employee in employees]

    def action_draft(self):
        if any(holiday.state not in ['confirm', 'refuse'] for holiday in self):
            raise UserError(_('Allocation request state must be "Refused" or "To Approve" in order to be reset to Draft.'))
        self.write({
            'state': 'draft',
            'approver_id': False,
        })
        linked_requests = self.mapped('linked_request_ids')
        if linked_requests:
            linked_requests.action_draft()
            linked_requests.unlink()
        self.activity_update()
        return True

    def action_confirm(self):
        if self.filtered(lambda holiday: holiday.state != 'draft'):
            raise UserError(_('Allocation request must be in Draft state ("To Submit") in order to confirm it.'))
        res = self.write({'state': 'confirm'})
        self.activity_update()
        return res

    def action_validate(self):
        current_employee = self.env.user.employee_id
        if any(holiday.state != 'confirm' for holiday in self):
            raise UserError(_('Allocation request must be confirmed in order to approve it.'))

        self.write({
            'state': 'validate',
            'approver_id': current_employee.id
        })

        for holiday in self:
            holiday._action_validate_create_childs()
        self.activity_update()
        return True

    def _action_validate_create_childs(self):
        childs = self.env['hr.leave.allocation']
        # In the case we are in holiday_type `employee` and there is only one employee we can keep the same allocation
        # Otherwise we do need to create an allocation for all employees to have a behaviour that is in line
        # with the other holiday_type
        if self.state == 'validate' and (self.holiday_type in ['category', 'department', 'company'] or
            (self.holiday_type == 'employee' and len(self.employee_ids) > 1)):
            if self.holiday_type == 'employee':
                employees = self.employee_ids
            elif self.holiday_type == 'category':
                employees = self.category_id.employee_ids
            elif self.holiday_type == 'department':
                employees = self.department_id.member_ids
            else:
                employees = self.env['hr.employee'].search([('company_id', '=', self.mode_company_id.id)])

            allocation_create_vals = self._prepare_holiday_values(employees)
            childs += self.with_context(
                mail_notify_force_send=False,
                mail_activity_automation_skip=True
            ).create(allocation_create_vals)
            if childs:
                childs.action_validate()
        return childs

    def action_refuse(self):
        current_employee = self.env.user.employee_id
        if any(holiday.state not in ['confirm', 'validate', 'validate1'] for holiday in self):
            raise UserError(_('Allocation request must be confirmed or validated in order to refuse it.'))

        self.write({'state': 'refuse', 'approver_id': current_employee.id})
        # If a category that created several holidays, cancel all related
        linked_requests = self.mapped('linked_request_ids')
        if linked_requests:
            linked_requests.action_refuse()
        self.activity_update()
        return True

    def _check_approval_update(self, state):
        """ Check if target state is achievable. """
        if self.env.is_superuser():
            return
        current_employee = self.env.user.employee_id
        if not current_employee:
            return
        is_officer = self.env.user.has_group('hr_holidays.group_hr_holidays_user')
        is_manager = self.env.user.has_group('hr_holidays.group_hr_holidays_manager')
        for holiday in self:
            val_type = holiday.holiday_status_id.sudo().allocation_validation_type
            if state == 'confirm':
                continue

            if state == 'draft':
                if holiday.employee_id != current_employee and not is_manager:
                    raise UserError(_('Only a time off Manager can reset other people allocation.'))
                continue

            if not is_officer and self.env.user != holiday.employee_id.leave_manager_id and not val_type == 'no':
                raise UserError(_('Only a time off Officer/Responsible or Manager can approve or refuse time off requests.'))

            if is_officer or self.env.user == holiday.employee_id.leave_manager_id:
                # use ir.rule based first access check: department, members, ... (see security.xml)
                holiday.check_access_rule('write')

            if holiday.employee_id == current_employee and not is_manager and not val_type == 'no':
                raise UserError(_('Only a time off Manager can approve its own requests.'))

            if (state == 'validate1' and val_type == 'both') or (state == 'validate' and val_type == 'manager'):
                if self.env.user == holiday.employee_id.leave_manager_id and self.env.user != holiday.employee_id.user_id:
                    continue
                manager = holiday.employee_id.parent_id or holiday.employee_id.department_id.manager_id
                if (manager != current_employee) and not is_manager:
                    raise UserError(_('You must be either %s\'s manager or time off manager to approve this time off') % (holiday.employee_id.name))

            if state == 'validate' and val_type == 'both':
                if not is_officer:
                    raise UserError(_('Only a Time off Approver can apply the second approval on allocation requests.'))

    @api.onchange('allocation_type')
    def _onchange_allocation_type(self):
        if self.allocation_type == 'accrual':
            self.number_of_days = 0.0
        elif not self.number_of_days_display:
            self.number_of_days = 1.0

    # ------------------------------------------------------------
    # Activity methods
    # ------------------------------------------------------------

    def _get_responsible_for_approval(self):
        self.ensure_one()
        responsible = self.env.user

        if self.validation_type == 'manager' or (self.validation_type == 'both' and self.state == 'confirm'):
            if self.employee_id.leave_manager_id:
                responsible = self.employee_id.leave_manager_id
        elif self.validation_type == 'hr' or (self.validation_type == 'both' and self.state == 'validate1'):
            if self.holiday_status_id.responsible_id:
                responsible = self.holiday_status_id.responsible_id

        return responsible

    def activity_update(self):
        to_clean, to_do = self.env['hr.leave.allocation'], self.env['hr.leave.allocation']
        for allocation in self:
            note = _(
                'New Allocation Request created by %(user)s: %(count)s Days of %(allocation_type)s',
                user=allocation.create_uid.name,
                count=allocation.number_of_days,
                allocation_type=allocation.holiday_status_id.name
            )
            if allocation.state == 'draft':
                to_clean |= allocation
            elif allocation.state == 'confirm':
                allocation.activity_schedule(
                    'hr_holidays.mail_act_leave_allocation_approval',
                    note=note,
                    user_id=allocation.sudo()._get_responsible_for_approval().id or self.env.user.id)
            elif allocation.state == 'validate1':
                allocation.activity_feedback(['hr_holidays.mail_act_leave_allocation_approval'])
                allocation.activity_schedule(
                    'hr_holidays.mail_act_leave_allocation_second_approval',
                    note=note,
                    user_id=allocation.sudo()._get_responsible_for_approval().id or self.env.user.id)
            elif allocation.state == 'validate':
                to_do |= allocation
            elif allocation.state == 'refuse':
                to_clean |= allocation
        if to_clean:
            to_clean.activity_unlink(['hr_holidays.mail_act_leave_allocation_approval', 'hr_holidays.mail_act_leave_allocation_second_approval'])
        if to_do:
            to_do.activity_feedback(['hr_holidays.mail_act_leave_allocation_approval', 'hr_holidays.mail_act_leave_allocation_second_approval'])

    ####################################################
    # Messaging methods
    ####################################################

    def _track_subtype(self, init_values):
        if 'state' in init_values and self.state == 'validate':
            allocation_notif_subtype_id = self.holiday_status_id.allocation_notif_subtype_id
            return allocation_notif_subtype_id or self.env.ref('hr_holidays.mt_leave_allocation')
        return super(HolidaysAllocation, self)._track_subtype(init_values)

    def _notify_get_groups(self, msg_vals=None):
        """ Handle HR users and officers recipients that can validate or refuse holidays
        directly from email. """
        groups = super(HolidaysAllocation, self)._notify_get_groups(msg_vals=msg_vals)
        local_msg_vals = dict(msg_vals or {})

        self.ensure_one()
        hr_actions = []
        if self.state == 'confirm':
            app_action = self._notify_get_action_link('controller', controller='/allocation/validate', **local_msg_vals)
            hr_actions += [{'url': app_action, 'title': _('Approve')}]
        if self.state in ['confirm', 'validate', 'validate1']:
            ref_action = self._notify_get_action_link('controller', controller='/allocation/refuse', **local_msg_vals)
            hr_actions += [{'url': ref_action, 'title': _('Refuse')}]

        holiday_user_group_id = self.env.ref('hr_holidays.group_hr_holidays_user').id
        new_group = (
            'group_hr_holidays_user', lambda pdata: pdata['type'] == 'user' and holiday_user_group_id in pdata['groups'], {
                'actions': hr_actions,
            })

        return [new_group] + groups

    def message_subscribe(self, partner_ids=None, subtype_ids=None):
        # due to record rule can not allow to add follower and mention on validated leave so subscribe through sudo
        if self.state in ['validate', 'validate1']:
            self.check_access_rights('read')
            self.check_access_rule('read')
            return super(HolidaysAllocation, self.sudo()).message_subscribe(partner_ids=partner_ids, subtype_ids=subtype_ids)
        return super(HolidaysAllocation, self).message_subscribe(partner_ids=partner_ids, subtype_ids=subtype_ids)
Пример #25
0
class asset_asset(models.Model):
    """
    Assets
    """
    _name = 'asset.asset'
    _description = 'Asset'
    _inherit = ['mail.thread']

    def _read_group_state_ids(self, domain, read_group_order=None, access_rights_uid=None, team='3'):
        access_rights_uid = access_rights_uid or self.uid
        stage_obj = self.env['asset.state']
        order = stage_obj._order
        # lame hack to allow reverting search, should just work in the trivial case
        if read_group_order == 'stage_id desc':
            order = "%s desc" % order
        # write the domain
        # - ('id', 'in', 'ids'): add columns that should be present
        # - OR ('team','=',team): add default columns that belongs team
        search_domain = []
        search_domain += ['|', ('team','=',team)]
        search_domain += [('id', 'in', ids)]
        stage_ids = stage_obj._search(search_domain, order=order, access_rights_uid=access_rights_uid)
        result = stage_obj.name_get(access_rights_uid, stage_ids)
        # restore order of the search
        result.sort(lambda x,y: cmp(stage_ids.index(x[0]), stage_ids.index(y[0])))
        return result, {}    

    def _read_group_finance_state_ids(self, domain, read_group_order=None, access_rights_uid=None):
        return self._read_group_state_ids(domain, read_group_order, access_rights_uid, '0')

    def _read_group_warehouse_state_ids(self, domain, read_group_order=None, access_rights_uid=None):
        return self._read_group_state_ids(domain, read_group_order, access_rights_uid, '1')

    def _read_group_manufacture_state_ids(self, domain, read_group_order=None, access_rights_uid=None):
        return self._read_group_state_ids(domain, read_group_order, access_rights_uid, '2')

    def _read_group_maintenance_state_ids(self, domain, read_group_order=None, access_rights_uid=None):
        return self._read_group_state_ids(domain, read_group_order, access_rights_uid, '3')
        
    def _read_group_accounting_state_ids(self, domain, read_group_order=None, access_rights_uid=None):
        return self._read_group_state_ids(domain, read_group_order, access_rights_uid, '4')

    CRITICALITY_SELECTION = [
        ('0', 'General'),
        ('1', 'Important'),
        ('2', 'Very important'),
        ('3', 'Critical')
    ]

    name = fields.Char('Asset Name', size=64, required=True, translate=True)
    finance_state_id = fields.Many2one('asset.state', 'State', domain=[('team','=','0')])
    warehouse_state_id = fields.Many2one('asset.state', 'State', domain=[('team','=','1')])
    manufacture_state_id = fields.Many2one('asset.state', 'State', domain=[('team','=','2')])
    maintenance_state_id = fields.Many2one('asset.state', 'State', domain=[('team','=','3')])
    accounting_state_id = fields.Many2one('asset.state', 'State', domain=[('team','=','4')])
    maintenance_state_color = fields.Selection(related='maintenance_state_id.state_color', selection=STATE_COLOR_SELECTION, string="Color", readonly=True)
    criticality = fields.Selection(CRITICALITY_SELECTION, 'Criticality')
    property_stock_asset = fields.Many2one(
        'stock.location', "Asset Location",
        company_dependent=True, domain=[('usage', 'like', 'asset')],
        help="This location will be used as the destination location for installed parts during asset life.")
    user_id = fields.Many2one('res.users', 'Assigned to', track_visibility='onchange')
    active = fields.Boolean('Active', default=True)
    asset_number = fields.Char('Asset Number', size=64)
    model = fields.Char('Model', size=64)
    serial = fields.Char('Serial no.', size=64)
    vendor_id = fields.Many2one('res.partner', 'Vendor')
    manufacturer_id = fields.Many2one('res.partner', 'Manufacturer')
    start_date = fields.Date('Start Date')
    purchase_date = fields.Date('Purchase Date')
    warranty_start_date = fields.Date('Warranty Start')
    warranty_end_date = fields.Date('Warranty End')
    image = fields.Binary("Image")
    image_small = fields.Binary("Small-sized image")
    image_medium = fields.Binary("Medium-sized image")
    category_ids = fields.Many2many('asset.category', id1='asset_id', id2='category_id', string='Tags')

    _group_by_full = {
        'finance_state_id': _read_group_finance_state_ids,
        'warehouse_state_id': _read_group_warehouse_state_ids,
        'manufacture_state_id': _read_group_manufacture_state_ids,
        'maintenance_state_id': _read_group_maintenance_state_ids,
        'accounting_state_id': _read_group_accounting_state_ids,
    }

    @api.model
    def create(self, vals):
        if 'image' in vals:
            vals['image_small'] = vals['image_medium'] = vals['image']
        return super(asset_asset, self).create(vals)

    def write(self, vals):
        if 'image' in vals:
            vals['image_small'] = vals['image_medium'] = vals['image']
        return super(asset_asset, self).write(vals)
Пример #26
0
class AccountPayment(models.Model):
    _name = "account.payment"
    _inherit = ['mail.thread', 'account.payment']

    state = fields.Selection(track_visibility='always')
    amount = fields.Monetary(track_visibility='always')
    partner_id = fields.Many2one(track_visibility='always')
    journal_id = fields.Many2one(track_visibility='always')
    destination_journal_id = fields.Many2one(track_visibility='always')
    currency_id = fields.Many2one(track_visibility='always')
    # campo a ser extendido y mostrar un nombre detemrinado en las lineas de
    # pago de un payment group o donde se desee (por ej. con cheque, retención,
    # etc)
    payment_method_description = fields.Char(
        compute='_compute_payment_method_description',
        string='Payment Method',
    )

    @api.multi
    def _compute_payment_method_description(self):
        for rec in self:
            rec.payment_method_description = rec.payment_method_id.display_name

    # nuevo campo funcion para definir dominio de los metodos
    payment_method_ids = fields.Many2many(
        'account.payment.method',
        compute='_compute_payment_methods'
    )
    journal_ids = fields.Many2many(
        'account.journal',
        compute='_compute_journals'
    )
    # journal_at_least_type = fields.Char(
    #     compute='_compute_journal_at_least_type'
    # )
    destination_journal_ids = fields.Many2many(
        'account.journal',
        compute='_compute_destination_journals'
    )

    @api.multi
    @api.depends(
        # 'payment_type',
        'journal_id',
    )
    def _compute_destination_journals(self):
        for rec in self:
            domain = [
                ('type', 'in', ('bank', 'cash')),
                # al final pensamos mejor no agregar esta restricción, por ej,
                # para poder transferir a tarjeta a pagar. Esto solo se usa
                # en transferencias
                # ('at_least_one_inbound', '=', True),
                ('company_id', '=', rec.journal_id.company_id.id),
                ('id', '!=', rec.journal_id.id),
            ]
            rec.destination_journal_ids = rec.journal_ids.search(domain)

    # @api.multi
    # @api.depends(
    #     'payment_type',
    # )
    # def _compute_journal_at_least_type(self):
    #     for rec in self:
    #         if rec.payment_type == 'inbound':
    #             journal_at_least_type = 'at_least_one_inbound'
    #         else:
    #             journal_at_least_type = 'at_least_one_outbound'
    #         rec.journal_at_least_type = journal_at_least_type

    @api.multi
    def get_journals_domain(self):
        """
        We get domain here so it can be inherited
        """
        self.ensure_one()
        domain = [('type', 'in', ('bank', 'cash'))]
        if self.payment_type == 'inbound':
            domain.append(('at_least_one_inbound', '=', True))
        # Al final dejamos que para transferencias se pueda elegir
        # cualquier sin importar si tiene outbound o no
        # else:
        elif self.payment_type == 'outbound':
            domain.append(('at_least_one_outbound', '=', True))
        return domain

    @api.multi
    @api.depends(
        'payment_type',
    )
    def _compute_journals(self):
        for rec in self:
            rec.journal_ids = rec.journal_ids.search(rec.get_journals_domain())

    @api.multi
    @api.depends(
        'journal_id.outbound_payment_method_ids',
        'journal_id.inbound_payment_method_ids',
        'payment_type',
    )
    def _compute_payment_methods(self):
        for rec in self:
            if rec.payment_type in ('outbound', 'transfer'):
                methods = rec.journal_id.outbound_payment_method_ids
            else:
                methods = rec.journal_id.inbound_payment_method_ids
            rec.payment_method_ids = methods

    @api.onchange('payment_type')
    def _onchange_payment_type(self):
        """
        Sobre escribimos y desactivamos la parte del dominio de la funcion
        original ya que se pierde si se vuelve a entrar
        """
        if not self.invoice_ids:
            # Set default partner type for the payment type
            if self.payment_type == 'inbound':
                self.partner_type = 'customer'
            elif self.payment_type == 'outbound':
                self.partner_type = 'supplier'
            else:
                self.partner_type = False
            # limpiamos journal ya que podria no estar disponible para la nueva
            # operacion y ademas para que se limpien los payment methods
            self.journal_id = False
        # # Set payment method domain
        # res = self._onchange_journal()
        # if not res.get('domain', {}):
        #     res['domain'] = {}
        # res['domain']['journal_id'] = self.payment_type == 'inbound' and [
        #     ('at_least_one_inbound', '=', True)] or [
        #     ('at_least_one_outbound', '=', True)]
        # res['domain']['journal_id'].append(('type', 'in', ('bank', 'cash')))
        # return res

    # @api.onchange('partner_type')
    def _onchange_partner_type(self):
        """
        Agregasmos dominio en vista ya que se pierde si se vuelve a entrar
        Anulamos funcion original porque no haria falta
        """
        return True

    def _onchange_amount(self):
        """
        Anulamos este onchange que termina cambiando el domain de journals
        y no es compatible con multicia y se pierde al guardar.
        TODO: ver que odoo con este onchange llama a
        _compute_journal_domain_and_types quien devolveria un journal generico
        cuando el importe sea cero, imagino que para hacer ajustes por
        diferencias de cambio
        """
        return True

    @api.onchange('journal_id')
    def _onchange_journal(self):
        """
        Sobre escribimos y desactivamos la parte del dominio de la funcion
        original ya que se pierde si se vuelve a entrar
        TODO: ver que odoo con este onchange llama a
        _compute_journal_domain_and_types quien devolveria un journal generico
        cuando el importe sea cero, imagino que para hacer ajustes por
        diferencias de cambio
        """
        if self.journal_id:
            self.currency_id = (
                self.journal_id.currency_id or self.company_id.currency_id)
            # Set default payment method
            # (we consider the first to be the default one)
            payment_methods = (
                self.payment_type == 'inbound' and
                self.journal_id.inbound_payment_method_ids or
                self.journal_id.outbound_payment_method_ids)
            # si es una transferencia y no hay payment method de origen,
            # forzamos manual
            if not payment_methods and self.payment_type == 'transfer':
                payment_methods = self.env.ref(
                    'account.account_payment_method_manual_out')
            self.payment_method_id = (
                payment_methods and payment_methods[0] or False)
            # si se eligió de origen el mismo diario de destino, lo resetiamos
            if self.journal_id == self.destination_journal_id:
                self.destination_journal_id = False
        #     # Set payment method domain
        #     # (restrict to methods enabled for the journal and to selected
        #     # payment type)
        #     payment_type = self.payment_type in (
        #         'outbound', 'transfer') and 'outbound' or 'inbound'
        #     return {
        #         'domain': {
        #             'payment_method_id': [
        #                 ('payment_type', '=', payment_type),
        #                 ('id', 'in', payment_methods.ids)]}}
        # return {}

    @api.multi
    @api.depends('invoice_ids', 'payment_type', 'partner_type', 'partner_id')
    def _compute_destination_account_id(self):
        """
        We send force_company on context so payments can be created from parent
        companies. We try to send force_company on self but it doesnt works, it
        only works sending it on partner
        """
        res = super(AccountPayment, self)._compute_destination_account_id()
        for rec in self.filtered(
                lambda x: not x.invoice_ids and x.payment_type != 'transfer'):
            partner = self.partner_id.with_context(
                force_company=self.company_id.id)
            if self.partner_type == 'customer':
                self.destination_account_id = (
                    partner.property_account_receivable_id.id)
            else:
                self.destination_account_id = (
                    partner.property_account_payable_id.id)
        return res
Пример #27
0
class SyncTask(models.Model):
    _name = 'cus_args.sync_task'
    _rec_name = 'sync_model'
    _description = 'Data Sync Task'

    scheme = fields.Selection(
        string="Scheme",
        selection=[
            ('http', 'Http'),
            ('https', 'Https'),
        ],
        required=True,
    )
    base_url = fields.Char(
        string="Source URL",
        required=True,
    )
    data_uri = fields.Char(
        string="URI",
        required=True,
    )
    sync_model = fields.Char(
        string="Model",
        required=True,
    )
    port = fields.Char(
        string="Port",
        required=True,
    )

    @api.multi
    def sync_data(self):
        """同步数据"""
        print(u"开始同步数据!")
        if self.sync_model not in [
                'cus_args.goods_tariff', 'cus_args.goods_declare_element',
                'cus_args.register_company'
        ]:
            self.general_data()
        elif self.sync_model in ['cus_args.goods_tariff']:
            self.goods_tariff_data()
        elif self.sync_model in ['cus_args.goods_declare_element']:
            self.declare_element_data()
        elif self.sync_model in ['cus_args.register_company']:
            self.register_company_data()

        return True

    @api.multi
    def general_data(self):
        records = self.env[self.sync_model].search([])
        if not records:
            codes = {}
        else:
            codes = records.mapped(lambda r: {r.code: r.id})
            codes_dic = {}
            for dic in codes:
                codes_dic.update(dic)
            codes = codes_dic
        print(codes)
        source_url = self.scheme + '://' + self.base_url + ':' + self.port + '/' + self.data_uri
        print(source_url)
        rest_codes = set()
        while (source_url):
            response = requests.get(source_url)
            print('response code: {}'.format(response.status_code))
            response.raise_for_status()
            response = response.json()
            source_url = response.get('next')
            for item in response.get('results'):
                code = item.get('code')
                rest_codes.add(code)
                if code not in codes:
                    self.env[self.sync_model].create({
                        'code':
                        code,
                        'name_cn':
                        item.get('name_cn')
                    })
                else:
                    self.env[self.sync_model].browse(codes[code]).write(
                        {'name_cn': item.get('name_cn')})

        rm_codes = set(codes.keys()) - rest_codes
        if rm_codes:
            rm_obj_ids = [codes[obj_id] for obj_id in rm_codes]
            rm_objs = self.env[self.sync_model].search([('id', 'in',
                                                         rm_obj_ids)])
            rm_objs.unlink()

    @api.multi
    def goods_tariff_data(self):
        records = self.env[self.sync_model].search([])
        if not records:
            codes = {}
        else:
            codes = records.mapped(lambda r: {r.code_ts: r.id})
            codes_dic = {}
            for dic in codes:
                codes_dic.update(dic)
            codes = codes_dic
        print(codes)
        source_url = self.scheme + '://' + self.base_url + ':' + self.port + '/' + self.data_uri
        print(source_url)
        rest_codes = set()
        while (source_url):
            response = requests.get(source_url)
            print('response code: {}'.format(response.status_code))
            response.raise_for_status()
            response = response.json()
            source_url = response.get('next')
            for item in response.get('results'):
                code = item.get('code_ts')
                rest_codes.add(code)
                if code not in codes:
                    self.env[self.sync_model].create({
                        'code_ts':
                        code,
                        'code_t':
                        item.get('code_t'),
                        'code_s':
                        item.get('code_s'),
                        'first_unit_id':
                        self.env['cus_args.unit'].search([
                            ('code', '=', item.get('first_unit'))
                        ])[0].id if item.get('first_unit') else False,
                        'second_unit_id':
                        self.env['cus_args.unit'].search([
                            ('code', '=', item.get('second_unit'))
                        ])[0].id if item.get('second_unit') else False,
                        'name_cn':
                        item.get('name_cn'),
                        'supervision_condition':
                        item.get('supervision_condition')
                    })
                else:
                    self.env[self.sync_model].browse(codes[code]).write({
                        'name_cn':
                        item.get('name_cn'),
                        'code_t':
                        item.get('code_t'),
                        'code_s':
                        item.get('code_s'),
                        'first_unit_id':
                        self.env['cus_args.unit'].search([
                            ('code', '=', item.get('first_unit'))
                        ])[0].id if item.get('first_unit') else False,
                        'second_unit_id':
                        self.env['cus_args.unit'].search([
                            ('code', '=', item.get('second_unit'))
                        ])[0].id if item.get('second_unit') else False,
                        'supervision_condition':
                        item.get('supervision_condition')
                    })

        rm_codes = set(codes.keys()) - rest_codes
        if rm_codes:
            rm_obj_ids = [codes[obj_id] for obj_id in rm_codes]
            rm_objs = self.env[self.sync_model].search([('id', 'in',
                                                         rm_obj_ids)])
            rm_objs.unlink()

    @api.multi
    def declare_element_data(self):
        records = self.env[self.sync_model].search([])
        records.unlink()
        source_url = self.scheme + '://' + self.base_url + ':' + self.port + '/' + self.data_uri
        print(source_url)
        while (source_url):
            response = requests.get(source_url)
            print('response code: {}'.format(response.status_code))
            response.raise_for_status()
            response = response.json()
            source_url = response.get('next')
            for item in response.get('results'):
                self.env[self.sync_model].create({
                    'goods_tariff_hs_code':
                    item['cus_goods_tariff_id'],
                    'name_cn':
                    item.get('name'),
                    'sequence':
                    item.get('sequence')
                })

    @api.multi
    def register_company_data(self):
        records = self.env[self.sync_model].search([])
        if not records:
            codes = {}
        else:
            codes = records.mapped(lambda r: {r.register_code: r.id})
            codes_dic = {}
            for dic in codes:
                codes_dic.update(dic)
            codes = codes_dic
        print(codes)
        source_url = self.scheme + '://' + self.base_url + ':' + self.port + '/' + self.data_uri
        print(source_url)
        rest_codes = set()
        while (source_url):
            response = requests.get(source_url)
            print('response code: {}'.format(response.status_code))
            response.raise_for_status()
            response = response.json()
            source_url = response.get('next')
            for item in response.get('results'):
                code = item.get('register_code')
                rest_codes.add(code)
                if code not in codes:
                    self.env[self.sync_model].create({
                        'register_code':
                        code,
                        'register_name_cn':
                        item.get('register_name_cn'),
                        'unified_social_credit_code':
                        item.get('unified_social_credit_code')
                    })
                else:
                    self.env[self.sync_model].browse(codes[code]).write({
                        'register_name_cn':
                        item.get('register_name_cn'),
                        'unified_social_credit_code':
                        item.get('unified_social_credit_code')
                    })

        rm_codes = set(codes.keys()) - rest_codes
        if rm_codes:
            rm_obj_ids = [codes[obj_id] for obj_id in rm_codes]
            rm_objs = self.env[self.sync_model].search([('id', 'in',
                                                         rm_obj_ids)])
            rm_objs.unlink()
Пример #28
0
class AccountPaymentMethod(models.Model):
    _inherit = "account.payment.method"

    name = fields.Char(translate=True)
Пример #29
0
class urgence(models.Model):
    _name = "purchase.exp.besoin.urgence"
    _description = "urgence"

    name = fields.Char('Libellé', size=64, required=True, readonly=False)
Пример #30
0
class TaxReportWizard(models.TransientModel):
    _name = "tax.report.wizard"

    excel_file = fields.Binary('Sales Report')
    file_name = fields.Char('Excel File', size=64)