Пример #1
0
class import_inv_wizard(models.TransientModel):
    _name="import.inv.wizard"
    _description = "Import Invoice Wizard"        

    import_type = fields.Selection([
        ('csv','CSV File'),
        ('excel','Excel File')
        ],default="csv",string="Import File Type",required=True)
    file = fields.Binary(string="File",required=True)   
    product_by = fields.Selection([
        ('name','Name'),
        ('int_ref','Internal Reference'),
        ('barcode','Barcode')
        ],default="name", string = "Product By", required = True) 
    invoice_type = fields.Selection([
        ('inv','Customer Invoice'),
        ('bill','Vendor Bill'),
        ('ccn','Customer Credit Note'),
        ('vcn','Vendor Credit Note')
        ],default = "inv", string = "Invoicing Type", required = True)
    is_validate = fields.Boolean(string = "Auto Validate?")
    inv_no_type = fields.Selection([
        ('auto','Auto'),
        ('as_per_sheet','As per sheet')
        ],default = "auto",string = "Number",required = True)    

    @api.multi
    def show_success_msg(self,counter, validate_rec, skipped_line_no):
        
        #to close the current active wizard        
        action = self.env.ref('sh_all_in_one_import.sh_import_inv_action').read()[0]
        action = {'type': 'ir.actions.act_window_close'} 
        
        #open the new success message box    
        view = self.env.ref('sh_message.sh_message_wizard')
        view_id = view and view.id or False                                   
        context = dict(self._context or {})
        dic_msg = str(counter) + " Records imported successfully \n"
        dic_msg = dic_msg + str(validate_rec) + " Records Validate"
        if skipped_line_no:
            dic_msg = dic_msg + "\nNote:"
        for k,v in skipped_line_no.items():
            dic_msg = dic_msg + "\nRow No " + k + " " + v + " "
        context['message'] = dic_msg            
        
        return {
            'name': 'Success',
            'type': 'ir.actions.act_window',
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'sh.message.wizard',
            'views': [(view.id, 'form')],
            'view_id': view.id,
            'target': 'new',
            'context': context,
            }     

    
    @api.multi
    def import_inv_apply(self):
        inv_line_obj = self.env['account.invoice.line']
        inv_obj = self.env['account.invoice']
        #perform import lead
        if self and self.file:
            #For CSV
            if self.import_type == 'csv':
                counter = 1
                skipped_line_no = {}
                try:
                    file = str(base64.decodestring(self.file).decode('utf-8'))
                    myreader = csv.reader(file.splitlines())
                    skip_header=True
                    running_inv = None
                    created_inv = False
                    created_inv_list_for_validate = []
                    created_inv_list = []                     
                    
                    for row in myreader:
                        try:
                            if skip_header:
                                skip_header=False
                                counter = counter + 1
                                continue

                            
                            if row[0] not in (None,"") and row[3] not in (None,""): 
                                vals = {}
 
                                if row[0] != running_inv:
                                     
                                    running_inv = row[0]
                                    inv_vals={}
                                     
                                    if row[1] not in (None,""):
                                        partner_obj = self.env["res.partner"]
                                        partner = partner_obj.search([('name','=',row[1] )], limit = 1)
                                         
                                        if partner:
                                            inv_vals.update({'partner_id' : partner.id})
                                        else:
                                            skipped_line_no[str(counter)]= " - Customer/Vendor not found. " 
                                            counter = counter + 1 
                                            continue   
                                    else:
                                        skipped_line_no[str(counter)]= " - Customer/Vendor field is empty. " 
                                        counter = counter + 1 
                                        continue     
                                    
                                    if row[2] not in (None,""):
                                        cd = row[2]                
                                        cd = str(datetime.strptime(cd, '%Y-%m-%d').date())
                                        inv_vals.update({'date_invoice' : cd})
                                        
                                    if self.inv_no_type == 'as_per_sheet':
                                        inv_vals.update({"move_name" : row[0]})                           
                                    
                                    if self.invoice_type == 'inv':
                                        inv_vals.update({"type" : "out_invoice"})
                                    elif self.invoice_type == 'bill': 
                                        inv_vals.update({"type" : "in_invoice"})
                                    elif self.invoice_type == 'ccn': 
                                        inv_vals.update({"type" : "out_refund"}) 
                                    elif self.invoice_type == 'vcn': 
                                        inv_vals.update({"type" : "in_refund"})                                                                                 
                                        
                                    created_inv = inv_obj.create(inv_vals) 
                                    created_inv_list_for_validate.append(created_inv.id)
                                    created_inv_list.append(created_inv.id)                            
                    
                            
                                if created_inv:  
                                    
                                    field_nm = 'name'
                                    if self.product_by == 'name':
                                        field_nm = 'name'
                                    elif self.product_by == 'int_ref':
                                        field_nm = 'default_code'
                                    elif self.product_by == 'barcode':
                                        field_nm = 'barcode'
                                    
                                    search_product = self.env['product.product'].search([(field_nm,'=',row[3] )], limit = 1)                             
                                    if search_product:
                                        vals.update({'product_id' : search_product.id})
                                        
                                        if row[4] != '':
                                            vals.update({'name' : row[4] })
                                        else:
                                           
                                            if created_inv.partner_id:
                                                if created_inv.partner_id.lang:
                                                    product = search_product.with_context(lang=created_inv.partner_id.lang)
                                                else:
                                                    product = search_product
    
                                                name = product.partner_ref
                                            if created_inv.type in ('in_invoice', 'in_refund'):
                                                if product.description_purchase:
                                                    name += '\n' + product.description_purchase
                                            else:
                                                if product.description_sale:
                                                    name += '\n' + product.description_sale                                          
                                            vals.update({'name' : name})
                                                
                                        accounts = search_product.product_tmpl_id.get_product_accounts(created_inv.fiscal_position_id)
                                        account = False
                                        if created_inv.type in ('out_invoice', 'out_refund'):
                                            account =  accounts['income']
                                        else:
                                            account = accounts['expense']        
                                        
                                        if account == False:
                                            skipped_line_no[str(counter)]= " - Account not found. " 
                                            counter = counter + 1
                                            if created_inv.id in created_inv_list_for_validate:
                                                created_inv_list_for_validate.remove(created_inv.id)                                            
                                            continue                                        
                                        else:
                                            vals.update({'account_id' : account.id})
                                                
                                            
                                        if row[5] != '':
                                            vals.update({'quantity' : row[5] })
                                        else:
                                            vals.update({'quantity' : 1 })
                                        
                                        if row[6] in (None,""):
                                            if created_inv.type in ('in_invoice', 'in_refund') and search_product.uom_po_id:
                                                vals.update({'uom_id' : search_product.uom_po_id.id })
                                            elif search_product.uom_id:
                                                vals.update({'uom_id' : search_product.uom_id.id })                                       
                                        else:
                                            search_uom = self.env['uom.uom'].search([('name','=',row[6] )], limit = 1) 
                                            if search_uom:
                                                vals.update({'uom_id' : search_uom.id })
                                            else:
                                                skipped_line_no[str(counter)]= " - Unit of Measure not found. " 
                                                counter = counter + 1
                                                if created_inv.id in created_inv_list_for_validate:
                                                    created_inv_list_for_validate.remove(created_inv.id)                                                
                                                continue
                                        
                                        if row[7] in (None,""):
                                            if created_inv.type in ('in_invoice', 'in_refund'):
                                                vals.update({'price_unit' : search_product.standard_price })
                                            else:
                                                vals.update({'price_unit' : search_product.lst_price })
                                        else:
                                            vals.update({'price_unit' : row[7] })
                                            
                                        if row[8].strip() in (None,""):
                                            if created_inv.type in ('in_invoice', 'in_refund') and search_product.supplier_taxes_id :
                                                vals.update({'invoice_line_tax_ids' : [(6, 0, search_product.supplier_taxes_id.ids)]})
                                            elif created_inv.type in ('out_invoice', 'out_refund') and search_product.taxes_id:
                                                vals.update({'invoice_line_tax_ids' : [(6, 0, search_product.taxes_id.ids)]})
                                                
                                        else:
                                            taxes_list = []
                                            some_taxes_not_found = False
                                            for x in row[8].split(','):
                                                x = x.strip()
                                                if x != '':
                                                    search_tax = self.env['account.tax'].search([('name','=',x)], limit = 1)
                                                    if search_tax:
                                                        taxes_list.append(search_tax.id)
                                                    else:
                                                        some_taxes_not_found = True
                                                        skipped_line_no[str(counter)]= " - Taxes " + x +  " not found. "                                                 
                                                        break  
                                            if some_taxes_not_found:
                                                counter = counter + 1
                                                if created_inv.id in created_inv_list_for_validate:
                                                    created_inv_list_for_validate.remove(created_inv.id)                                                
                                                continue
                                            else:
                                                vals.update({'invoice_line_tax_ids' : [(6, 0, taxes_list)]})
                                            
    
                                    
                                        vals.update({'invoice_id' : created_inv.id})
                                        created_inv_line = inv_line_obj.create(vals)      
                                        counter = counter + 1
                                
                                    else:
                                        skipped_line_no[str(counter)]= " - Product not found. " 
                                        counter = counter + 1 
                                        if created_inv.id in created_inv_list_for_validate:
                                            created_inv_list_for_validate.remove(created_inv.id)
                                        continue                                        
                                             
                                             
                                else:
                                    skipped_line_no[str(counter)]= " - Order not created. " 
                                    counter = counter + 1 
                                    continue
                                
                            
                            else:
                                skipped_line_no[str(counter)] = " - Number or Product field is empty. "  
                                counter = counter + 1    
                        
                        except Exception as e:
                            skipped_line_no[str(counter)] = " - Value is not valid " + ustr(e)   
                            counter = counter + 1 
                            continue          
                             
#                   here call necessary method 
                    if created_inv_list:
                        invoices = inv_obj.search([('id','in',created_inv_list)])
                        if invoices:
                            for invoice in invoices:
                                invoice._onchange_partner_id()    
                                invoice._onchange_invoice_line_ids()                                  
#                     validate invoice
                    if created_inv_list_for_validate and self.is_validate == True:
                        invoices = inv_obj.search([('id','in',created_inv_list_for_validate)])
                        if invoices:
                            for invoice in invoices:
                                invoice.action_invoice_open()
                    else:
                        created_inv_list_for_validate = []
                except Exception as e:
                    raise UserError(_("Sorry, Your csv file does not match with our format" + ustr(e) ))
                 
                if counter > 1:
                    completed_records = len(created_inv_list)
                    validate_rec = len(created_inv_list_for_validate)
                    res = self.show_success_msg(completed_records, validate_rec, skipped_line_no)
                    return res
 
             
            #For Excel
            if self.import_type == 'excel':
                counter = 1
                skipped_line_no = {}   
                active_inv = False                               
                try:
                    wb = xlrd.open_workbook(file_contents=base64.decodestring(self.file))
                    sheet = wb.sheet_by_index(0)     
                    skip_header = True    
                    running_inv = None
                    created_inv = False
                    created_inv_list_for_validate = []
                    created_inv_list = []                        
                    for row in range(sheet.nrows):
                        try:
                            if skip_header:
                                skip_header = False
                                counter = counter + 1
                                continue
                            
                            if sheet.cell(row,0).value not in (None,"") and sheet.cell(row,3).value not in (None,""): 
                                vals = {}
 
                                if sheet.cell(row,0).value != running_inv:
                                     
                                    running_inv = sheet.cell(row,0).value
                                    inv_vals={}
                                     
                                    if sheet.cell(row,1).value not in (None,""):
                                        partner_obj = self.env["res.partner"]
                                        partner = partner_obj.search([('name','=', sheet.cell(row,1).value )], limit = 1)
                                         
                                        if partner:
                                            inv_vals.update({'partner_id' : partner.id})
                                        else:
                                            skipped_line_no[str(counter)]= " - Customer/Vendor not found. " 
                                            counter = counter + 1 
                                            continue   
                                    else:
                                        skipped_line_no[str(counter)]= " - Customer/Vendor field is empty. " 
                                        counter = counter + 1 
                                        continue     
                                    
                                    if sheet.cell(row,2).value not in (None,""):
                                        cd = sheet.cell(row,2).value                
                                        cd = str(datetime.strptime(cd, '%Y-%m-%d').date())
                                        inv_vals.update({'date_invoice' : cd})
                                        
                                    if self.inv_no_type == 'as_per_sheet':
                                        inv_vals.update({"move_name" : sheet.cell(row,0).value})                           
                                    
                                    if self.invoice_type == 'inv':
                                        inv_vals.update({"type" : "out_invoice"})
                                    elif self.invoice_type == 'bill': 
                                        inv_vals.update({"type" : "in_invoice"})
                                    elif self.invoice_type == 'ccn': 
                                        inv_vals.update({"type" : "out_refund"}) 
                                    elif self.invoice_type == 'vcn': 
                                        inv_vals.update({"type" : "in_refund"})                                                                                 
                                        
                                    created_inv = inv_obj.create(inv_vals) 
                                    created_inv_list_for_validate.append(created_inv.id)
                                    created_inv_list.append(created_inv.id)                            
                    
                            
                                if created_inv:  
                                    
                                    field_nm = 'name'
                                    if self.product_by == 'name':
                                        field_nm = 'name'
                                    elif self.product_by == 'int_ref':
                                        field_nm = 'default_code'
                                    elif self.product_by == 'barcode':
                                        field_nm = 'barcode'
                                    
                                    search_product = self.env['product.product'].search([(field_nm,'=', sheet.cell(row,3).value )], limit = 1)                             
                                    if search_product:
                                        vals.update({'product_id' : search_product.id})
                                        
                                        if sheet.cell(row,4).value != '':
                                            vals.update({'name' : sheet.cell(row,4).value })
                                        else:
                                           
                                            if created_inv.partner_id:
                                                if created_inv.partner_id.lang:
                                                    product = search_product.with_context(lang=created_inv.partner_id.lang)
                                                else:
                                                    product = search_product
    
                                                name = product.partner_ref
                                            if created_inv.type in ('in_invoice', 'in_refund'):
                                                if product.description_purchase:
                                                    name += '\n' + product.description_purchase
                                            else:
                                                if product.description_sale:
                                                    name += '\n' + product.description_sale                                          
                                            vals.update({'name' : name})
                                                
                                        accounts = search_product.product_tmpl_id.get_product_accounts(created_inv.fiscal_position_id)
                                        account = False
                                        if created_inv.type in ('out_invoice', 'out_refund'):
                                            account =  accounts['income']
                                        else:
                                            account = accounts['expense']        
                                        
                                        if account == False:
                                            skipped_line_no[str(counter)]= " - Account not found. " 
                                            counter = counter + 1
                                            if created_inv.id in created_inv_list_for_validate:
                                                created_inv_list_for_validate.remove(created_inv.id)                                            
                                            continue                                        
                                        else:
                                            vals.update({'account_id' : account.id})
                                                
                                            
                                        if sheet.cell(row,5).value != '':
                                            vals.update({'quantity' : sheet.cell(row,5).value })
                                        else:
                                            vals.update({'quantity' : 1 })
                                        
                                        if sheet.cell(row,6).value in (None,""):
                                            if created_inv.type in ('in_invoice', 'in_refund') and search_product.uom_po_id:
                                                vals.update({'uom_id' : search_product.uom_po_id.id })
                                            elif search_product.uom_id:
                                                vals.update({'uom_id' : search_product.uom_id.id })                                       
                                        else:
                                            search_uom = self.env['uom.uom'].search([('name','=',sheet.cell(row,6).value )], limit = 1) 
                                            if search_uom:
                                                vals.update({'uom_id' : search_uom.id })
                                            else:
                                                skipped_line_no[str(counter)]= " - Unit of Measure not found. " 
                                                counter = counter + 1
                                                if created_inv.id in created_inv_list_for_validate:
                                                    created_inv_list_for_validate.remove(created_inv.id)                                                
                                                continue
                                        
                                        if sheet.cell(row,7).value in (None,""):
                                            if created_inv.type in ('in_invoice', 'in_refund'):
                                                vals.update({'price_unit' : search_product.standard_price })
                                            else:
                                                vals.update({'price_unit' : search_product.lst_price })
                                        else:
                                            vals.update({'price_unit' : sheet.cell(row,7).value })
                                            
                                        if sheet.cell(row,8).value.strip() in (None,""):
                                            if created_inv.type in ('in_invoice', 'in_refund') and search_product.supplier_taxes_id :
                                                vals.update({'invoice_line_tax_ids' : [(6, 0, search_product.supplier_taxes_id.ids)]})
                                            elif created_inv.type in ('out_invoice', 'out_refund') and search_product.taxes_id:
                                                vals.update({'invoice_line_tax_ids' : [(6, 0, search_product.taxes_id.ids)]})
                                                
                                        else:
                                            taxes_list = []
                                            some_taxes_not_found = False
                                            for x in sheet.cell(row,8).value.split(','):
                                                x = x.strip()
                                                if x != '':
                                                    search_tax = self.env['account.tax'].search([('name','=',x)], limit = 1)
                                                    if search_tax:
                                                        taxes_list.append(search_tax.id)
                                                    else:
                                                        some_taxes_not_found = True
                                                        skipped_line_no[str(counter)]= " - Taxes " + x +  " not found. "                                                 
                                                        break  
                                            if some_taxes_not_found:
                                                counter = counter + 1
                                                if created_inv.id in created_inv_list_for_validate:
                                                    created_inv_list_for_validate.remove(created_inv.id)                                                
                                                continue
                                            else:
                                                vals.update({'invoice_line_tax_ids' : [(6, 0, taxes_list)]})
                                            
    
                                    
                                        vals.update({'invoice_id' : created_inv.id})
                                        created_inv_line = inv_line_obj.create(vals)      
                                        counter = counter + 1
                                
                                    else:
                                        skipped_line_no[str(counter)]= " - Product not found. " 
                                        counter = counter + 1 
                                        if created_inv.id in created_inv_list_for_validate:
                                            created_inv_list_for_validate.remove(created_inv.id)
                                        continue                                        
                                             
                                             
                                else:
                                    skipped_line_no[str(counter)]= " - Order not created. " 
                                    counter = counter + 1 
                                    continue
                                
                            
                            else:
                                skipped_line_no[str(counter)] = " - Number or Product field is empty. "  
                                counter = counter + 1    
                        
                        except Exception as e:
                            skipped_line_no[str(counter)] = " - Value is not valid " + ustr(e)   
                            counter = counter + 1 
                            continue          
                             
#                   here call necessary method 
                    if created_inv_list:
                        invoices = inv_obj.search([('id','in',created_inv_list)])
                        if invoices:
                            for invoice in invoices:
                                invoice._onchange_partner_id()    
                                invoice._onchange_invoice_line_ids()                                  
#                     validate invoice
                    if created_inv_list_for_validate and self.is_validate == True:
                        invoices = inv_obj.search([('id','in',created_inv_list_for_validate)])
                        if invoices:
                            for invoice in invoices:
                                invoice.action_invoice_open()
                    else:
                        created_inv_list_for_validate = []
                except Exception as e:
                    raise UserError(_("Sorry, Your excel file does not match with our format" + ustr(e) ))
                 
                if counter > 1:
                    completed_records = len(created_inv_list)
                    validate_rec = len(created_inv_list_for_validate)
                    res = self.show_success_msg(completed_records, validate_rec, skipped_line_no)
                    return res
Пример #2
0
class Partner(models.Model):
    _inherit = 'res.partner'

    isFCTPartner = fields.Boolean("FCT Partner", default=False)

    pupils = fields.One2many('res.users', 'company', string="Pupils")
Пример #3
0
class AccountInvoice(models.Model):
    _inherit = "account.invoice"

    @api.multi
    @api.depends('invoice_line_ids.price_subtotal',
                 'withholding_tax_line_ids.tax', 'currency_id', 'company_id',
                 'date_invoice')
    def _amount_withholding_tax(self):
        res = {}
        dp_obj = self.env['decimal.precision']
        for invoice in self:
            withholding_tax_amount = 0.0
            for wt_line in invoice.withholding_tax_line_ids:
                withholding_tax_amount += round(
                    wt_line.tax, dp_obj.precision_get('Account'))
            invoice.amount_net_pay = invoice.amount_total - \
                withholding_tax_amount
            invoice.withholding_tax_amount = withholding_tax_amount
        return res

    withholding_tax = fields.Boolean('Withholding Tax')
    withholding_tax_line_ids = fields.One2many(
        'account.invoice.withholding.tax',
        'invoice_id',
        'Withholding Tax',
        readonly=True,
        states={'draft': [('readonly', False)]})
    withholding_tax_amount = fields.Float(compute='_amount_withholding_tax',
                                          digits=dp.get_precision('Account'),
                                          string='Withholding tax',
                                          store=True,
                                          readonly=True)
    amount_net_pay = fields.Float(compute='_amount_withholding_tax',
                                  digits=dp.get_precision('Account'),
                                  string='Net To Pay',
                                  store=True,
                                  readonly=True)

    @api.model
    def create(self, vals):
        invoice = super(AccountInvoice,
                        self.with_context(mail_create_nolog=True)).create(vals)

        if any(line.invoice_line_tax_wt_ids for line in
               invoice.invoice_line_ids) \
                and not invoice.withholding_tax_line_ids:
            invoice.compute_taxes()

        return invoice

    @api.onchange('invoice_line_ids')
    def _onchange_invoice_line_wt_ids(self):
        self.ensure_one()
        wt_taxes_grouped = self.get_wt_taxes_values()
        wt_tax_lines = []
        for tax in wt_taxes_grouped.values():
            wt_tax_lines.append((0, 0, tax))
        self.withholding_tax_line_ids = wt_tax_lines
        if wt_tax_lines:
            self.withholding_tax = True
        else:
            self.withholding_tax = False

    @api.multi
    def action_move_create(self):
        '''
        Split amount withholding tax on account move lines
        '''
        dp_obj = self.env['decimal.precision']
        res = super(AccountInvoice, self).action_move_create()

        for inv in self:
            # Rates
            rate_num = 0
            for move_line in inv.move_id.line_ids:
                if move_line.account_id.internal_type not in [
                        'receivable', 'payable'
                ]:
                    continue
                rate_num += 1
            if rate_num:
                wt_rate = round(inv.withholding_tax_amount / rate_num,
                                dp_obj.precision_get('Account'))
            wt_residual = inv.withholding_tax_amount
            # Re-read move lines to assign the amounts of wt
            i = 0
            for move_line in inv.move_id.line_ids:
                if move_line.account_id.internal_type not in [
                        'receivable', 'payable'
                ]:
                    continue
                i += 1
                if i == rate_num:
                    wt_amount = wt_residual
                else:
                    wt_amount = wt_rate
                wt_residual -= wt_amount
                # update line
                move_line.write({'withholding_tax_amount': wt_amount})
            # Create WT Statement
            self.create_wt_statement()
        return res

    @api.multi
    def get_wt_taxes_values(self):
        tax_grouped = {}
        for invoice in self:
            for line in invoice.invoice_line_ids:
                taxes = []
                for wt_tax in line.invoice_line_tax_wt_ids:
                    res = wt_tax.compute_tax(line.price_subtotal)
                    tax = {
                        'id': wt_tax.id,
                        'sequence': wt_tax.sequence,
                        'base': res['base'],
                        'tax': res['tax'],
                    }
                    taxes.append(tax)

                for tax in taxes:
                    val = {
                        'invoice_id': invoice.id,
                        'withholding_tax_id': tax['id'],
                        'tax': tax['tax'],
                        'base': tax['base'],
                        'sequence': tax['sequence'],
                    }

                    key = self.env['withholding.tax'].browse(
                        tax['id']).get_grouping_key(val)

                    if key not in tax_grouped:
                        tax_grouped[key] = val
                    else:
                        tax_grouped[key]['tax'] += val['tax']
                        tax_grouped[key]['base'] += val['base']
        return tax_grouped

    @api.one
    def create_wt_statement(self):
        """
        Create one statement for each withholding tax
        """
        wt_statement_obj = self.env['withholding.tax.statement']
        for inv_wt in self.withholding_tax_line_ids:
            wt_base_amount = inv_wt.base
            wt_tax_amount = inv_wt.tax
            if self.type in ['in_refund', 'out_refund']:
                wt_base_amount = -1 * wt_base_amount
                wt_tax_amount = -1 * wt_tax_amount
            val = {
                'date': self.move_id.date,
                'move_id': self.move_id.id,
                'invoice_id': self.id,
                'partner_id': self.partner_id.id,
                'withholding_tax_id': inv_wt.withholding_tax_id.id,
                'base': wt_base_amount,
                'tax': wt_tax_amount,
            }
            wt_statement_obj.create(val)
Пример #4
0
class DiscountRestrictCat(models.Model):
    _inherit = 'pos.category'
    # _description = 'Discount Restriction'

    discount_limit_on = fields.Boolean()
    limited_discount = fields.Integer(string="Discount Limit")
Пример #5
0
class ResCompany(models.Model):
    _inherit = "res.company"

    cancel_inventory_move_for_mo = fields.Boolean(
        string="Cancel Inventory Moves?")
    cancel_work_order_for_mo = fields.Boolean(string='Cancel Work Orders?')
class buy_receipt(models.Model):
    _name = "buy.receipt"
    _inherits = {'wh.move': 'buy_move_id'}
    _inherit = ['mail.thread']
    _description = u"采购入库单"
    _order = 'date desc, id desc'

    @api.one
    @api.depends('line_in_ids.subtotal', 'discount_amount',
                 'payment', 'line_out_ids.subtotal')
    def _compute_all_amount(self):
        '''当优惠金额改变时,改变优惠后金额和本次欠款'''
        total = 0
        if self.line_in_ids:
            # 入库时优惠前总金额
            total = sum(line.subtotal for line in self.line_in_ids)
        elif self.line_out_ids:
            # 退货时优惠前总金额
            total = sum(line.subtotal for line in self.line_out_ids)
        self.amount = total - self.discount_amount
        self.debt = self.amount - self.payment

    @api.one
    @api.depends('is_return', 'invoice_id.reconciled', 'invoice_id.amount')
    def _get_buy_money_state(self):
        '''返回付款状态'''
        if not self.is_return:
            if self.invoice_id.reconciled == 0:
                self.money_state = u'未付款'
            elif self.invoice_id.reconciled < self.invoice_id.amount:
                self.money_state = u'部分付款'
            elif self.invoice_id.reconciled == self.invoice_id.amount:
                self.money_state = u'全部付款'
        # 返回退款状态
        if self.is_return:
            if self.invoice_id.reconciled == 0:
                self.return_state = u'未退款'
            elif abs(self.invoice_id.reconciled) < abs(self.invoice_id.amount):
                self.return_state = u'部分退款'
            elif self.invoice_id.reconciled == self.invoice_id.amount:
                self.return_state = u'全部退款'

    buy_move_id = fields.Many2one('wh.move', u'入库单',
                                  required=True, ondelete='cascade',
                                  help=u'入库单号')
    is_return = fields.Boolean(u'是否退货',
                               default=lambda self: self.env.context.get('is_return'),
                               help=u'是否为退货类型')
    order_id = fields.Many2one('buy.order', u'订单号',
                               copy=False, ondelete='cascade',
                               help=u'产生入库单/退货单的购货订单')
    invoice_id = fields.Many2one('money.invoice', u'发票号', copy=False,
                                 ondelete='set null',
                                 help=u'产生的发票号')
    date_due = fields.Date(u'到期日期', copy=False,
                           default=lambda self: fields.Date.context_today(self),
                           help=u'付款截止日期')
    discount_rate = fields.Float(u'优惠率(%)', states=READONLY_STATES,
                                 help=u'整单优惠率')
    discount_amount = fields.Float(u'优惠金额', states=READONLY_STATES,
                                   digits=dp.get_precision('Amount'),
                                   help=u'整单优惠金额,可由优惠率自动计算得出,也可手动输入')
    invoice_by_receipt = fields.Boolean(string=u"按收货结算", default=True,
                                        help=u'如未勾选此项,可在资金行里输入付款金额,订单保存后,采购人员可以单击资金行上的【确认】按钮。')
    amount = fields.Float(u'优惠后金额', compute=_compute_all_amount,
                          store=True, readonly=True,
                          digits=dp.get_precision('Amount'),
                          help=u'总金额减去优惠金额')
    payment = fields.Float(u'本次付款', states=READONLY_STATES,
                           digits=dp.get_precision('Amount'),
                           help=u'本次付款金额')
    bank_account_id = fields.Many2one('bank.account', u'结算账户',
                                      ondelete='restrict',
                                      help=u'用来核算和监督企业与其他单位或个人之间的债权债务的结算情况')
    debt = fields.Float(u'本次欠款', compute=_compute_all_amount,
                        store=True, readonly=True, copy=False,
                        digits=dp.get_precision('Amount'),
                        help=u'本次欠款金额')
    cost_line_ids = fields.One2many('cost.line', 'buy_id', u'采购费用', copy=False,
                                    help=u'采购费用明细行')
    money_state = fields.Char(u'付款状态', compute=_get_buy_money_state,
                              store=True, default=u'未付款',
                              help=u"采购入库单的付款状态",
                              index=True, copy=False)
    return_state = fields.Char(u'退款状态', compute=_get_buy_money_state,
                               store=True, default=u'未退款',
                               help=u"采购退货单的退款状态",
                               index=True, copy=False)
    modifying = fields.Boolean(u'差错修改中', default=False,
                               help=u'是否处于差错修改中')
    voucher_id = fields.Many2one('voucher', u'入库凭证', readonly=True,
                                 help=u'审核时产生的入库凭证')
    origin_id = fields.Many2one('buy.receipt', u'来源单据')
    currency_id = fields.Many2one('res.currency',
                                  u'外币币别',
                                  readonly=True,
                                  help=u'外币币别')

    def _compute_total(self, line_ids):
        return sum(line.subtotal for line in line_ids)

    @api.onchange('discount_rate', 'line_in_ids', 'line_out_ids')
    def onchange_discount_rate(self):
        '''当优惠率或订单行发生变化时,单据优惠金额发生变化'''
        line = self.line_in_ids or self.line_out_ids
        total = self._compute_total(line)
        if self.discount_rate:
            self.discount_amount = total * self.discount_rate * 0.01

    @api.onchange('partner_id')
    def onchange_partner_id(self):
        if self.partner_id:
            for line in self.line_in_ids:
                if line.goods_id.tax_rate and self.partner_id.tax_rate:
                    if line.goods_id.tax_rate >= self.partner_id.tax_rate:
                        line.tax_rate = self.partner_id.tax_rate
                    else:
                        line.tax_rate = line.goods_id.tax_rate
                elif line.goods_id.tax_rate and not self.partner_id.tax_rate:
                    line.tax_rate = line.goods_id.tax_rate
                elif not line.goods_id.tax_rate and self.partner_id.tax_rate:
                    line.tax_rate = self.partner_id.tax_rate
                else:
                    line.tax_rate = self.env.user.company_id.import_tax_rate

    def get_move_origin(self, vals):
        return self._name + (self.env.context.get('is_return') and
                             '.return' or '.buy')

    @api.model
    def create(self, vals):
        '''创建采购入库单时生成有序编号'''
        if not self.env.context.get('is_return'):
            name = self._name
        else:
            name = 'buy.return'
        if vals.get('name', '/') == '/':
            vals['name'] = self.env['ir.sequence'].next_by_code(name) or '/'

        vals.update({
            'origin': self.get_move_origin(vals),
            'finance_category_id': self.env.ref('finance.categ_buy_goods').id,
        })
        return super(buy_receipt, self).create(vals)

    @api.multi
    def unlink(self):
        for receipt in self:
            if receipt.state == 'done':
                raise UserError(u'不能删除已审核的单据')

            receipt.buy_move_id.unlink()

    @api.one
    def _wrong_receipt_done(self):
        if self.state == 'done':
            raise UserError(u'请不要重复审核!')
        batch_one_list_wh = []
        batch_one_list = []
        for line in self.line_in_ids:
            if line.amount < 0:
                raise UserError(u'购货金额不能小于 0!请修改。')
            if line.goods_id.force_batch_one:
                wh_move_lines = self.env['wh.move.line'].search(
                    [('state', '=', 'done'), ('type', '=', 'in'), ('goods_id', '=', line.goods_id.id)])
                for move_line in wh_move_lines:
                    if (move_line.goods_id.id, move_line.lot) not in batch_one_list_wh and move_line.lot:
                        batch_one_list_wh.append((move_line.goods_id.id, move_line.lot))

            if (line.goods_id.id, line.lot) in batch_one_list_wh:
                raise UserError(u'仓库已存在相同序列号的商品!\n商品:%s 序列号:%s' % (line.goods_id.name, line.lot))

        for line in self.line_in_ids:
            if line.goods_qty <= 0 or line.price_taxed < 0:
                raise UserError(u'商品 %s 的数量和含税单价不能小于0!' % line.goods_id.name)
            if line.goods_id.force_batch_one:
                batch_one_list.append((line.goods_id.id, line.lot))

        if len(batch_one_list) > len(set(batch_one_list)):
            raise UserError(u'不能创建相同序列号的商品!\n 序列号list为%s' % str(batch_one_list))

        for line in self.line_out_ids:
            if line.amount < 0:
                raise UserError(u'退货金额不能小于 0!请修改。')
            if line.goods_qty <= 0 or line.price_taxed < 0:
                raise UserError(u'商品 %s 的数量和含税单价不能小于0!' % line.goods_id.name)

        if not self.bank_account_id and self.payment:
            raise UserError(u'付款额不为空时,请选择结算账户!')
        decimal_amount = self.env.ref('core.decimal_amount')
        if float_compare(self.payment, self.amount, precision_digits=decimal_amount.digits) == 1:
            raise UserError(u'本次付款金额不能大于折后金额!\n付款金额:%s 折后金额:%s' % (self.payment, self.amount))
        if float_compare(sum(cost_line.amount for cost_line in self.cost_line_ids),
                         sum(line.share_cost for line in self.line_in_ids),
                         precision_digits=decimal_amount.digits) != 0:
            raise UserError(u'采购费用还未分摊或分摊不正确!\n采购费用:%s 分摊总费用:%s' %
                            (sum(cost_line.amount for cost_line in self.cost_line_ids),
                             sum(line.share_cost for line in self.line_in_ids)))
        return

    @api.one
    def _line_qty_write(self):
        if self.order_id:
            for line in self.line_in_ids:
                line.buy_line_id.quantity_in += line.goods_qty
            for line in self.line_out_ids:
                line.buy_line_id.quantity_in -= line.goods_qty

        return

    def _get_invoice_vals(self, partner_id, category_id, date, amount, tax_amount):
        '''返回创建 money_invoice 时所需数据'''
        return {
            'move_id': self.buy_move_id.id,
            'name': self.name,
            'partner_id': partner_id.id,
            'category_id': category_id.id,
            'date': date,
            'amount': amount,
            'reconciled': 0,
            'to_reconcile': amount,
            'tax_amount': tax_amount,
            'date_due': self.date_due,
            'state': 'draft',
            'currency_id': self.currency_id.id,
            'note': self.note,
        }

    def _receipt_make_invoice(self):
        '''入库单/退货单 生成结算单'''
        invoice_id = False
        if not self.is_return:
            if not self.invoice_by_receipt:
                return False
            amount = self.amount
            tax_amount = sum(line.tax_amount for line in self.line_in_ids)
        else:
            amount = -self.amount
            tax_amount = - sum(line.tax_amount for line in self.line_out_ids)
        categ = self.env.ref('money.core_category_purchase')
        if not float_is_zero(amount, 2):
            invoice_id = self.env['money.invoice'].create(
                self._get_invoice_vals(self.partner_id, categ, self.date, amount, tax_amount)
            )
        return invoice_id

    @api.one
    def _buy_amount_to_invoice(self):
        '''采购费用产生结算单'''
        if sum(cost_line.amount for cost_line in self.cost_line_ids) > 0:
            for line in self.cost_line_ids:
                if not float_is_zero(line.amount, 2):
                    self.env['money.invoice'].create(
                        self._get_invoice_vals(line.partner_id, line.category_id, self.date, line.amount + line.tax,
                                               line.tax)
                    )
        return

    def _make_payment(self, invoice_id, amount, this_reconcile):
        '''根据传入的invoice_id生成付款单'''
        categ = self.env.ref('money.core_category_purchase')
        money_lines = [{'bank_id': self.bank_account_id.id, 'amount': this_reconcile}]
        source_lines = [{'name': invoice_id.id,
                         'category_id': categ.id,
                         'date': invoice_id.date,
                         'amount': amount,
                         'reconciled': 0.0,
                         'to_reconcile': amount,
                         'this_reconcile': this_reconcile}]
        rec = self.with_context(type='pay')
        money_order = rec.env['money.order'].create({
            'partner_id': self.partner_id.id,
            'bank_name':self.partner_id.bank_name,
            'bank_num':self.partner_id.bank_num,
            'date': fields.Date.context_today(self),
            'line_ids':
                [(0, 0, line) for line in money_lines],
            'source_ids':
                [(0, 0, line) for line in source_lines],
            'amount': amount,
            'reconciled': this_reconcile,
            'to_reconcile': amount,
            'state': 'draft',
            'origin_name': self.name,
            'note': self.note,
            'buy_id': self.order_id.id,
        })
        return money_order

    def _create_voucher_line(self, account_id, debit, credit, voucher_id, goods_id, goods_qty, partner_id):
        '''返回voucher line'''
        rate_silent = currency_amount = 0
        currency = self.currency_id != self.env.user.company_id.currency_id and self.currency_id.id or False
        if self.currency_id and self.currency_id != self.env.user.company_id.currency_id:
            rate_silent = self.env['res.currency'].get_rate_silent(self.date, self.currency_id.id)
            currency_amount = debit or credit
            debit = debit * (rate_silent or 1)
            credit = credit * (rate_silent or 1)
        voucher = self.env['voucher.line'].create({
            'name': u'%s %s' % (self.name, self.note or ''),
            'account_id': account_id and account_id.id,
            'partner_id': partner_id and partner_id.id,
            'debit': debit,
            'credit': credit,
            'voucher_id': voucher_id and voucher_id.id,
            'goods_id': goods_id and goods_id.id,
            'goods_qty': goods_qty,
            'currency_id': currency,
            'currency_amount': currency_amount,
            'rate_silent': rate_silent,
        })
        return voucher

    @api.multi
    def create_voucher(self):
        '''
        借: 商品分类对应的会计科目 一般是库存商品
        贷:类型为支出的类别对应的会计科目 一般是材料采购

        当一张入库单有多个商品的时候,按对应科目汇总生成多个借方凭证行。

        采购退货单生成的金额为负
        '''
        self.ensure_one()
        vouch_id = self.env['voucher'].create({'date': self.date})

        sum_amount = 0
        if not self.is_return:
            for line in self.line_in_ids:
                if line.amount:
                    # 借方明细
                    self._create_voucher_line(line.goods_id.category_id.account_id,
                                              line.amount, 0, vouch_id, line.goods_id, line.goods_qty, False)
                sum_amount += line.amount

            if sum_amount:
                # 贷方明细
                self._create_voucher_line(self.buy_move_id.finance_category_id.account_id,
                                          0, sum_amount, vouch_id, False, 0, self.partner_id)
        if self.is_return:
            for line in self.line_out_ids:
                if line.amount:
                    # 借方明细
                    self._create_voucher_line(line.goods_id.category_id.account_id,
                                              -line.amount, 0, vouch_id, line.goods_id, line.goods_qty, False)
                    sum_amount += line.amount

            if sum_amount:
                # 贷方明细
                self._create_voucher_line(self.buy_move_id.finance_category_id.account_id,
                                          0, -sum_amount, vouch_id, False, 0, self.partner_id)

        if len(vouch_id.line_ids) > 0:
            vouch_id.voucher_done()
            return vouch_id
        else:
            vouch_id.unlink()

    @api.one
    def buy_receipt_done(self):
        '''审核采购入库单/退货单,更新本单的付款状态/退款状态,并生成结算单和付款单'''
        # 报错
        self._wrong_receipt_done()
        # 调用wh.move中审核方法,更新审核人和审核状态
        self.buy_move_id.approve_order()

        # 将收货/退货数量写入订单行
        self._line_qty_write()

        # 创建入库的会计凭证
        voucher = self.create_voucher()

        # 入库单/退货单 生成结算单
        invoice_id = self._receipt_make_invoice()
        self.write({
            'voucher_id': voucher and voucher.id,
            'invoice_id': invoice_id and invoice_id.id,
            'state': 'done',    # 为保证审批流程顺畅,否则,未审批就可审核
        })
        # 采购费用产生结算单
        self._buy_amount_to_invoice()
        # 生成付款单
        if self.payment:
            flag = not self.is_return and 1 or -1
            amount = flag * self.amount
            this_reconcile = flag * self.payment
            self._make_payment(invoice_id, amount, this_reconcile)
        # 生成分拆单 FIXME:无法跳转到新生成的分单
        if self.order_id and not self.modifying:
            return self.order_id.buy_generate_receipt()

    @api.one
    def buy_receipt_draft(self):
        '''反审核采购入库单/退货单,更新本单的付款状态/退款状态,并删除生成的结算单、付款单及凭证'''
        # 查找产生的付款单
        source_line = self.env['source.order.line'].search(
            [('name', '=', self.invoice_id.id)])
        for line in source_line:
            line.money_id.money_order_draft()
            line.money_id.unlink()
        # 查找产生的结算单
        invoice_ids = self.env['money.invoice'].search(
            [('name', '=', self.invoice_id.name)])
        invoice_ids.money_invoice_draft()
        invoice_ids.unlink()
        # 如果存在分单,则将差错修改中置为 True,再次审核时不生成分单
        self.write({
            'modifying': False,
            'state': 'draft',
        })
        receipt_ids = self.search(
            [('order_id', '=', self.order_id.id)])
        if len(receipt_ids) > 1:
            self.write({
                'modifying': True,
                'state': 'draft',
            })
        # 修改订单行中已执行数量
        if self.order_id:
            line_ids = not self.is_return and self.line_in_ids or self.line_out_ids
            for line in line_ids:
                line.buy_line_id.quantity_in -= line.goods_qty
        # 调用wh.move中反审核方法,更新审核人和审核状态
        self.buy_move_id.cancel_approved_order()

        # 反审核采购入库单时删除对应的入库凭证
        voucher, self.voucher_id = self.voucher_id, False
        if voucher.state == 'done':
            voucher.voucher_draft()
        voucher.unlink()

    @api.one
    def buy_share_cost(self):
        '''入库单上的采购费用分摊到入库单明细行上'''
        total_amount = 0
        for line in self.line_in_ids:
            total_amount += line.amount
        cost = sum(cost_line.amount for cost_line in self.cost_line_ids)
        for line in self.line_in_ids:
            line.share_cost = cost / total_amount * line.amount
        return True

    @api.multi
    def buy_to_return(self):
        '''采购入库单转化为采购退货单'''
        return_goods = {}

        return_order_draft = self.search([
            ('is_return', '=', True),
            ('origin_id', '=', self.id),
            ('state', '=', 'draft')
        ])
        if return_order_draft:
            raise UserError(u'采购入库单存在草稿状态的退货单!')

        return_order = self.search([
            ('is_return', '=', True),
            ('origin_id', '=', self.id),
            ('state', '=', 'done')
        ])
        for order in return_order:
            for return_line in order.line_out_ids:
                # 用产品、属性、批次做key记录已退货数量
                t_key = (return_line.goods_id.id,return_line.attribute_id.id,return_line.lot_id.lot)
                if return_goods.get(t_key):
                    return_goods[t_key] += return_line.goods_qty
                else:
                    return_goods[t_key] = return_line.goods_qty
        receipt_line = []
        for line in self.line_in_ids:
            qty = line.goods_qty
            l_key = (line.goods_id.id,line.attribute_id.id,line.lot)
            if return_goods.get(l_key):
                qty = qty - return_goods[l_key]
            if qty > 0:
                dic = {
                    'goods_id': line.goods_id.id,
                    'attribute_id': line.attribute_id.id,
                    'uom_id': line.uom_id.id,
                    'warehouse_id': line.warehouse_dest_id.id,
                    'warehouse_dest_id': line.warehouse_id.id,
                    'buy_line_id':line.buy_line_id.id,
                    'goods_qty': qty,
                    'price_taxed': line.price_taxed,
                    'discount_rate': line.discount_rate,
                    'discount_amount': line.discount_amount,
                    'type': 'out'
                }
                if line.goods_id.using_batch:
                    dic.update({'lot_id':line.id})
                receipt_line.append(dic)
        if len(receipt_line) == 0:
            raise UserError(u'该订单已全部退货!')

        vals = {'partner_id': self.partner_id.id,
                'is_return': True,
                'order_id': self.order_id.id,
                'origin_id': self.id,
                'origin': 'buy.receipt.return',
                'warehouse_dest_id': self.warehouse_id.id,
                'warehouse_id': self.warehouse_dest_id.id,
                'bank_account_id': self.bank_account_id.id,
                'date_due': (datetime.datetime.now()).strftime(ISODATEFORMAT),
                'date': (datetime.datetime.now()).strftime(ISODATEFORMAT),
                'line_out_ids': [(0, 0, line) for line in receipt_line],
                'discount_amount': self.discount_amount,
                }
        delivery_return = self.with_context(is_return=True).create(vals)
        view_id = self.env.ref('buy.buy_return_form').id
        name = u'采购退货单'
        return {
            'name': name,
            'view_type': 'form',
            'view_mode': 'form',
            'view_id': False,
            'views': [(view_id, 'form')],
            'res_model': 'buy.receipt',
            'type': 'ir.actions.act_window',
            'res_id': delivery_return.id,
            'target': 'current'
        }
Пример #7
0
class AccountInvoiceLine(models.Model):
    _inherit = 'account.invoice.line'

    is_rounding = fields.Boolean('Rounding Line')
Пример #8
0
class ResConfigSettings(models.TransientModel):
    _inherit = "res.config.settings"

    group_self_borrow = fields.Boolean(string="Self borrow", implied_group="my_library.group_self_borrow")
class ProductCategories(models.Model):
	_inherit = 'product.category'
	_description = 'Magento ProductCategories'

	is_magento = fields.Boolean("Is Magento ? ")
	magento_id = fields.Char("Magento ID ")
Пример #10
0
class Website(models.Model):
    _inherit = "website"

    def _get_default_header_content(self):
        return """
            <p></p>
            <div class="s_rating row te_s_header_offer_text">
            <ul>
                <li>Special Offer on First Purchase</li>
                <li>
                    <section>|</section>
                </li>
                <li>Code : #ASDA44</li>
                <li>
                    <section>|</section>
                </li>
                <li>Get 50% Off</li>
            </ul>
            </div>
            """

    def _get_default_footer_extra_links(self):
        return """
        <section>
        <div class="te_footer_inline_menu">
            <ul class="te_footer_inline_menu_t">
                <li>
                    <section>
                        <a href="#">About Us</a>
                    </section>
                </li>
                <li>
                    <section>
                        <a href="#">Contact Us</a>
                    </section>
                </li>
                <li>
                    <section>
                        <a href="#">Customer Service</a>
                    </section>
                </li>
                <li>
                    <section>
                        <a href="#">Privacy Policy</a>
                    </section>
                </li>
                <li>
                    <section>
                        <a href="#">Accessibility</a>
                    </section>
                </li>
                <li>
                    <section>
                        <a href="#">Store Directory</a>
                    </section>
                </li>
            </ul>
        </section>
        </div>
        """

    def _get_default_footer_content(self):
        return """
            <p></p>
            <div class="row">
                <div class="col-lg-4 col-md-4 col-6">
                    <ul class="te_footer_info_ept">
                        <section>
                            <li>
                                <a href="#">Help</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Gift Cards</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Order Status</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Free Shipping</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Returns Exchanges</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">International</a>
                            </li>
                        </section>
                    </ul>
                </div>
                <div class="col-lg-4 col-md-4 col-6">
                    <ul class="te_footer_info_ept">
                        <section>
                            <li>
                                <a href="#">About Us</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Jobs</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Affiliates</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Meet The Maker</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Contact</a>
                            </li>
                        </section>
                    </ul>
                </div>
                <div class="col-lg-4 col-md-4 col-6">
                    <ul class="te_footer_info_ept">
                        <section>
                            <li>
                                <a href="#">Security</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Privacy</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Text Messaging</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Legal</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Supply Chain</a>
                            </li>
                        </section>
                    </ul>
                </div>
            </div>
        """

    def _get_footer_style_3_content(self):
        return """
                <p></p>
                <section>
                    <div>
                        <h4 class="te_footer_menu_info">Informations</h4>
                    </div>
                </section>
                <div class="row">
                    <div class="col-lg-6 col-md-6 col-6">
                        <ul class="te_footer_info_ept">
                            <section>
                                <li>
                                    <a href="#">Help</a>
                                </li>
                            </section>


                            <section>
                                <li>
                                    <a href="#">Gift Cards</a>
                                </li>
                            </section>

                            <section>
                                <li>
                                    <a href="#">Order Status</a>
                                </li>
                            </section>
                            <section>
                                <li>
                                    <a href="#">Free Shipping</a>
                                </li>
                            </section>
                            <section>
                                <li>
                                    <a href="#">Returns Exchanges</a>
                                </li>
                            </section>
                            <section>
                                <li>
                                    <a href="#">International</a>
                                </li>
                            </section>
                        </ul>
                    </div>
                    <div class="col-lg-6 col-md-6 col-6">
                        <ul class="te_footer_info_ept">

                            <section>
                                <li>
                                    <a href="#">Security</a>
                                </li>
                            </section>
                            <section>
                                <li>
                                    <a href="#">Privacy</a>
                                </li>
                            </section>
                            <section>
                                <li>
                                    <a href="#">Text Messaging</a>
                                </li>
                            </section>
                            <section>
                                <li>
                                    <a href="#">Legal</a>
                                </li>
                            </section>
                            <section>
                                <li>
                                    <a href="#">Supply Chain</a>
                                </li>
                            </section>
                            <section>
                                <li>
                                    <a href="#">Contact</a>
                                </li>
                            </section>
                        </ul>
                    </div>
                </div>"""

    def _get_footer_style_4_content(self):
        return """
         <p></p>
            <div class="row">
                <div class="footer-column-2 col-md-3 col-sm-6">
                    <div class="footer_top_title_div">
                        <h5 class="footer-sub-title">Our Stores</h5>
                        <span>
                            <span class="fa fa-angle-down"></span>
                        </span>
                    </div>
                    <ul class="te_footer_info_ept">
                        <section>
                            <li>
                                <a href="#">New York</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">London SF</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Cockfosters BP</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Los Angeles</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Chicago</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Las Vegas</a>
                            </li>
                        </section>
                    </ul>
                </div>
                <div class="footer-column-2 col-md-3 col-sm-6">
                    <div class="footer_top_title_div">
                        <h5 class="footer-sub-title">Information</h5>
                        <span>
                            <span class="fa fa-angle-down"></span>
                        </span>
                    </div>
                    <ul class="te_footer_info_ept">
                        <section>
                            <li>
                                <a href="#">About Store</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">New Collection</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Woman Dress</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Contact Us</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Latest News</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Our Sitemap</a>
                            </li>
                        </section>
                    </ul>
                </div>
                <div class="footer-column-2 col-md-3 col-sm-6">
                    <div class="footer_top_title_div">
                        <h5 class="footer-sub-title">Useful links</h5>
                        <span>
                            <span class="fa fa-angle-down"></span>
                        </span>
                    </div>
                    <ul class="te_footer_info_ept">
                        <section>
                            <li>
                                <a href="#">Privacy Policy</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Returns</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Terms &amp; Conditions</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Contact Us</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Latest News</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Our Sitemap</a>
                            </li>
                        </section>
                    </ul>
                </div>
                <div class="footer-column-2 col-md-3 col-sm-6">
                    <div class="footer_top_title_div">
                        <h5 class="footer-sub-title">Footer Menu</h5>
                        <span>
                            <span class="fa fa-angle-down"></span>
                        </span>
                    </div>
                    <ul class="te_footer_info_ept">
                        <section>
                            <li>
                                <a href="#">Instagram profile</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">New Collection</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Woman Dress</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Contact Us</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Latest News</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Purchase Theme</a>
                            </li>
                        </section>
                    </ul>
                </div>
            </div>
        """

    def _get_footer_style_5_content(self):
        return """
        <p></p>
            <div class="row">
                <div class="col-sm-6">
                    <div class="te_block_title">My Account</div>
                    <a class="te_collapse_icon collapsed" data-toggle="collapse" data-target="#my_account">
                        <div class="te_block_title_col">My Account</div>
                        <i class="fa fa-plus"></i>
                    </a>
                    <ul class="te_footer_info_ept collapse" id="my_account">
                        <section>
                            <li>
                                <a href="#">About Us</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Contact Us</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">My Account</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Order history</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Advanced search</a>
                            </li>
                        </section>
                    </ul>
                </div>
                <div class="col-sm-6">
                    <div class="te_block_title">Main Features</div>
                    <a class="te_collapse_icon collapsed" data-toggle="collapse" data-target="#feature">
                        <div class="te_block_title_col">Main Features</div>
                        <i class="fa fa-plus"></i>
                    </a>
                    <ul class="te_footer_info_ept collapse" id="feature">
                        <section>
                            <li>
                                <a href="#">Lorem ipsum sit</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Lorem ipsum dolor amet</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Lorem ipsum amet</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Lorem ipsum dolor</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="#">Lorem ipsum sit</a>
                            </li>
                        </section>
                    </ul>
                </div>
            </div>
        """

    def _get_footer_style_6_content(self):
        return """
        <p></p>
        <div class="row">
            <div class="col-sm-6 col-6 te_account_info">
                <div class="te_block_title">My Account</div>
                <ul class="te_footer_info_ept">
                    <section>
                        <li>
                            <a href="#">Help</a>
                        </li>
                    </section>
                    <section>
                        <li>
                            <a href="#">Gift Cards</a>
                        </li>
                    </section>
                    <section>
                        <li>
                            <a href="#">Order Status</a>
                        </li>
                    </section>
                    <section>
                        <li>
                            <a href="#">Free Shipping</a>
                        </li>
                    </section>
                    <section>
                        <li>
                            <a href="#">Returns Exchanges</a>
                        </li>
                    </section>
                    <section>
                        <li>
                            <a href="#">International</a>
                        </li>
                    </section>
                </ul>
            </div>
            <div class="col-sm-6 col-6">
                <div class="te_block_title">Main Features</div>
                <ul class="te_footer_info_ept">
                    <section>
                        <li>
                            <a href="#">About Us</a>
                        </li>
                    </section>
                    <section>
                        <li>
                            <a href="#">Jobs</a>
                        </li>
                    </section>
                    <section>
                        <li>
                            <a href="#">Affiliates</a>
                        </li>
                    </section>
                    <section>
                        <li>
                            <a href="#">Meet The Maker</a>
                        </li>
                    </section>
                    <section>
                        <li>
                            <a href="#">Contact</a>
                        </li>
                    </section>
                </ul>
            </div>
        </div>
        """

    def _get_footer_style_7_content(self):
        return """
            <p></p>
            <div class="row">
                <div class="col-md-6 col-6">
                    <div class="te_block_title">Useful link</div>
                    <ul class="te_footer_info_ept">
                        <section>
                            <li>
                                <i class="fa fa-long-arrow-right"></i>
                                <a href="#">Help</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <i class="fa fa-long-arrow-right"></i>
                                <a href="#">Gift Cards</a>
                            </li>
                        </section>
            
                        <section>
                            <li>
                                <i class="fa fa-long-arrow-right"></i>
                                <a href="#">Order Status</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <i class="fa fa-long-arrow-right"></i>
                                <a href="#">Free Shipping</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <i class="fa fa-long-arrow-right"></i>
                                <a href="#">Returns Exchanges</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <i class="fa fa-long-arrow-right"></i>
                                <a href="#">International</a>
                            </li>
                        </section>
                    </ul>
                </div>
                <div class="col-md-6 col-6">
                    <div class="te_block_title">Take Action</div>
                    <ul class="te_footer_info_ept">
                        <section>
                            <li>
                                <i class="fa fa-long-arrow-right"></i>
                                <a href="#">Security</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <i class="fa fa-long-arrow-right"></i>
                                <a href="#">Privacy</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <i class="fa fa-long-arrow-right"></i>
                                <a href="#">Text Messaging</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <i class="fa fa-long-arrow-right"></i>
                                <a href="#">Legal</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <i class="fa fa-long-arrow-right"></i>
                                <a href="#">Supply Chain</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <i class="fa fa-long-arrow-right"></i>
                                <a href="#">Contact</a>
                            </li>
                        </section>
                    </ul>
                </div>
            </div>
        """

    def _get_default_header_extra_links(self):
        return """
            <p></p>
            <div class="te_header_static_menu">
                <ul>
                    <li>
                        <a href="#">Custom menu</a>
                    </li>
                    <li>
                        <a href="#">Information</a>
                    </li>
                    <li>
                        <a href="#">About us</a>
                    </li>
                    <li>
                        <a href="#">Our story</a>
                    </li>
                </ul>
            </div>
        """

    def _get_default_vertical_menu(self):
        return """
            <section>
                <div class="te_sidenav_menu">
                    <ul>
                        <section>
                            <li>
                                <a href="/shop">About Shop</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="/contactus">Help Center</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="/aboutus">Portfolio</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="/blog">Blog</a>
                            </li>
                        </section>
                        <section>
                            <li>
                                <a href="/shop">New Look</a>
                            </li>
                        </section>
                    </ul>
                </div>
                <div class="te_sidenav_content">
                    <section>
                        <p>Pellentesque mollis nec orci id tincidunt. Sed mollis risus eu nisi aliquet, sit amet
                            fermentum.
                        </p>
                    </section>
                </div>
            </section>
        """

    def _get_default_facebook(self):
        return """
            <span class="fa fa-facebook"/>
        """

    def _get_default_twitter(self):
        return """
            <span class="fa fa-twitter"/>
        """

    def _get_default_linkedin(self):
        return """    
            <span class="fa fa-linkedin"/>
        """

    def _get_default_youtube(self):
        return """    
            <span class="fa fa-youtube-play"/>
        """

    def _get_default_github(self):
        return """    
            <span class="fa fa-github"/>
        """

    def _get_default_instagram(self):
        return """    
            <span class="fa fa-instagram"/>
        """

    facebook_sharing = fields.Boolean(string='Facebook')
    twitter_sharing = fields.Boolean(string='Twitter')
    linkedin_sharing = fields.Boolean(string='Linkedin')
    mail_sharing = fields.Boolean(string='Mail')
    is_load_more = fields.Boolean(string='Load More',
                                  help="Load moer will be enabled",
                                  readonly=False)
    load_more_image = fields.Binary(
        'Load More Image',
        help="Display this image while load more applies.",
        readonly=False)
    button_or_scroll = fields.Selection(
        [('automatic', 'Automatic- on page scroll'),
         ('button', 'Button- on click button')],
        string="Loading type for products",
        required=True,
        default='automatic',
        readonly=False)
    prev_button_label = fields.Char(string='Label for the Prev Button',
                                    readonly=False,
                                    default="Load prev",
                                    translate=True)
    next_button_label = fields.Char(string='Label for the Next Button',
                                    readonly=False,
                                    default="Load next",
                                    translate=True)
    is_lazy_load = fields.Boolean(string='Lazyload',
                                  help="Lazy load will be enabled",
                                  readonly=False)
    lazy_load_image = fields.Binary(
        'Lazyload Image',
        help="Display this image while lazy load applies.",
        readonly=False)
    banner_video_url = fields.Char(string='Video URL',
                                   help='URL of a video for banner.',
                                   readonly=False)
    number_of_product_line = fields.Selection(
        [('1', '1'), ('2', '2'), ('3', '3')],
        string="Number of lines for product name",
        default='1',
        readonly=False,
        help="Number of lines to show in product name for shop.")
    website_company_info = fields.Text(
        string="Company Information",
        translate=True,
        default="We are a team of passionate people whose goal is to improve "
        "everyone's life through disruptive products. We build great products to solve your business problems."
    )
    website_footer_extra_links = fields.Html(
        string="Footer Content",
        translate=True,
        default=_get_default_footer_extra_links)
    website_header_offer_ept = fields.Html(
        string="Clarico Header Offer Content",
        translate=True,
        sanitize=False,
        default=_get_default_header_content)
    footer_style_1_content_ept = fields.Html(
        string="Clarico Footer Style 1 Content",
        translate=True,
        sanitize=False,
        default=_get_default_footer_content)
    footer_style_3_content_ept = fields.Html(
        string="Clarico Footer Style 3 Content",
        translate=True,
        sanitize=False,
        default=_get_footer_style_3_content)
    footer_style_4_content_ept = fields.Html(
        string="Clarico Footer Style 4 Content",
        translate=True,
        sanitize=False,
        default=_get_footer_style_4_content)
    footer_style_5_content_ept = fields.Html(
        string="Clarico Footer Style 5 Content",
        translate=True,
        sanitize=False,
        default=_get_footer_style_5_content)
    footer_style_6_content_ept = fields.Html(
        string="Clarico Footer Style 6 Content",
        translate=True,
        sanitize=False,
        default=_get_footer_style_6_content)
    footer_style_7_content_ept = fields.Html(
        string="Clarico Footer Style 7 Content",
        translate=True,
        sanitize=False,
        default=_get_footer_style_7_content)
    website_header_extra_links = fields.Html(
        string="Clarico Header Extra Content",
        translate=True,
        sanitize=False,
        default=_get_default_header_extra_links)
    website_vertical_menu_ept = fields.Html(string="Vertical Menu Content",
                                            translate=True,
                                            sanitize=False,
                                            default=_get_default_vertical_menu)
    is_auto_play = fields.Boolean(string='Slider Auto Play',
                                  default=True,
                                  readonly=False)

    # @api.depends('banner_video_url')
    # def _compute_embed_code(self):
    #     for image in self:
    #         image.embed_code = get_video_embed_code(image.video_url)

    def getDatabase(self):
        """
                To display database in login popup
                :return: List of databases
                """
        values = request.params.copy()
        try:
            values['databases'] = http.db_list()
        except odoo.exceptions.AccessDenied:
            values['databases'] = None
        return values['databases']

    def category_check(self):
        """
        To display main parent product.public.category website specific
        :return:
        """
        return self.env['product.public.category'].sudo().search([
            ('parent_id', '=', False), ('website_id', 'in', (False, self.id))
        ])

    def get_default_company_address(self):
        """
        To get company default address
        :return:
        """
        street = ''
        street2 = ''
        city = ''
        zip = ''
        state = ''
        country = ''

        getCurrentCompany = request.env['website'].get_current_website(
        ).company_id

        values = {
            'street': getCurrentCompany.street,
            'street2': getCurrentCompany.street2,
            'city': getCurrentCompany.city,
            'zip': getCurrentCompany.zip,
            'state_id': getCurrentCompany.state_id.name,
            'country_id': getCurrentCompany.country_id.name
        }

        if getCurrentCompany.street:
            street = str(values['street'])
        if getCurrentCompany.street2:
            street2 = str(values['street2'])
        if getCurrentCompany.city:
            city = str(values['city'])
        if getCurrentCompany.zip:
            zip = values['zip']
        if getCurrentCompany.state_id.name:
            state = str(values['state_id'])
        if getCurrentCompany.country_id.name:
            country = str(values['country_id'])

        return street + ' ' + street2 + ' ' + city + ' ' + zip + ' ' + state + ' ' + country

    def get_product_categs_path(self, id):
        """
        To render full path for breadcrumbs based on argument
        :param id: product.public.category
        :return: list of category path and website url
        """
        categ_set = []
        if id:
            while id:
                categ = self.env['product.public.category'].sudo().search([
                    ('id', '=', id)
                ])
                categ_set.append(categ.id)
                if categ and categ.parent_id:
                    id = categ.parent_id.id
                else:
                    break

        # For Reverse order
        categ_set = categ_set[::-1]

        values = {
            'categ_set':
            categ_set,
            'web_url':
            self.env['ir.config_parameter'].sudo().get_param('web.base.url')
        }
        return values

    def get_min_max_prices(self,
                           search=False,
                           category=False,
                           attributes=False):
        """
        Get minimum price and maximum price according to Price list as well as discount for Shop page
        :return: min and max price value
        """
        range_list = []
        cust_min_val = request.httprequest.values.get('min_price', False)
        cust_max_val = request.httprequest.values.get('max_price', False)

        domain = WebsiteSaleWishlist._get_search_domain(
            self, search=search, category=category, attrib_values=attributes)

        if attributes:
            ids = []
            for value in attributes:
                if value[0] == 0:
                    ids.append(value[1])
                    domain += [('product_brand_ept_id.id', 'in', ids)]
        products = self.env['product.template'].search(domain)
        prices_list = []
        if products:
            pricelist = self.get_current_pricelist()
            for prod in products:
                context = dict(self.env.context,
                               quantity=1,
                               pricelist=pricelist.id if pricelist else False)
                product_template = prod.with_context(context)

                list_price = product_template.price_compute('list_price')[
                    product_template.id]
                price = product_template.price if pricelist else list_price
                if price:
                    prices_list.append(price)

        if not prices_list: return False

        if not cust_min_val and not cust_max_val:
            range_list.append(min(prices_list))
            range_list.append(max(prices_list))
            range_list.append(round(min(prices_list), 2))
            range_list.append(round(max(prices_list), 2))
        else:
            range_list.append(cust_min_val)
            range_list.append(cust_max_val)
            range_list.append(round(min(prices_list), 2))
            range_list.append(round(max(prices_list), 2))
        return range_list

    def get_brand(self, products=False):
        """
        This function is used to search the list of brand data
        :return: List of brand
        """

        if products:
            shop_brands = self.env['product.brand.ept'].sudo().search([
                ('product_ids', 'in', products.ids),
                ('products_count', '>', 0),
                ('website_id', 'in', (False, self.get_current_website().id))
            ])
        else:
            shop_brands = self.env['product.brand.ept'].sudo().search([
                ('website_published', '=', True), ('products_count', '>', 0),
                ('website_id', 'in', (False, self.get_current_website().id))
            ])
        return shop_brands

    def image_resize(self, img, width, height):
        """
        This function is used for resize the image with specific height and width
        and return the resizable image.
        :param img: image url
        :param width: image width
        :param height: image height
        :return: resizable image url
        """
        return image_process(img, size=(width, height))

    def get_carousel_category_list(self):
        """
        This method is used for return the list of category
        which has selected the allow category in carousel option from admin
        :return: list of category.
        """
        domain = [('website_id', 'in', (False, self.get_current_website().id)),
                  ('allow_in_category_carousel', '=', True)]
        category = self.env['product.public.category'].sudo().search(domain)
        return category

    def checkQuickFilter(self, currentWebsite, filterWebsiteArray):
        if currentWebsite in filterWebsiteArray or len(
                filterWebsiteArray) == 0:
            return True
        else:
            return False

    def list_providers_ept(self):
        """
        This method is used for return the encoded url for the auth providers
        :return: link for the auth providers.
        """
        try:
            providers = request.env['auth.oauth.provider'].sudo().search_read([
                ('enabled', '=', True)
            ])
        except Exception:
            providers = []
        for provider in providers:
            return_url = request.httprequest.url_root + 'auth_oauth/signin'
            state = OAuthLogin.get_state(self, provider)
            params = dict(
                response_type='token',
                client_id=provider['client_id'],
                redirect_uri=return_url,
                scope=provider['scope'],
                state=json.dumps(state),
            )
        return werkzeug.url_encode(params)
Пример #11
0
class OdooCMSExamValuation(models.Model):
    _name = 'odoocms.exam.valuation'
    _description = 'CMS Exam Valuation'

    name = fields.Char(string='Name', default='New')
    exam_id = fields.Many2one('odoocms.exam',
                              string='Exam',
                              required=True,
                              domain=[('state', '=', 'ongoing')])
    class_id = fields.Many2one('odoocms.class', string='Class', required=True)
    teacher_id = fields.Many2one('odoocms.faculty.staff', string='Evaluator')
    mark = fields.Float(string='Max Mark', required=True)
    pass_mark = fields.Float(string='Pass Mark', required=True)
    state = fields.Selection([('draft', 'Draft'), ('completed', 'Completed'),
                              ('cancel', 'Canceled')],
                             default='draft')
    valuation_line = fields.One2many('odoocms.exam.valuation.line',
                                     'valuation_id',
                                     string='Students')
    #subject_id = fields.Many2one('education.subject', string='Subject', required=True)
    mark_sheet_created = fields.Boolean(string='Mark sheet Created')
    date = fields.Date(string='Date', default=fields.Date.today)
    academic_semester_id = fields.Many2one(
        'odoocms.academic.semester',
        string='Academic Term',
        related='class_id.academic_semester_id',
        store=True)
    company_id = fields.Many2one(
        'res.company',
        string='Company',
        default=lambda self: self.env['res.company']._company_default_get())

    @api.onchange('class_id')
    def onchange_class_id(self):
        domain = []
        if self.class_id:
            domain = [('class_id', '=', self.class_id.id)]
        return {'domain': {'division_id': domain}}

    @api.onchange('pass_mark')
    def onchange_pass_mark(self):
        if self.pass_mark > self.mark:
            raise UserError(_('Pass mark must be less than Max Mark'))
        for records in self.valuation_line:
            if records.mark_scored >= self.pass_mark:
                records.pass_or_fail = True
            else:
                records.pass_or_fail = False

    #@api.onchange('exam_id', 'subject_id')
    #def onchange_exam_id(self):
    #    if self.exam_id:
    #        if self.exam_id.class_id:
    #            self.class_id = self.exam_id.class_id
    #        else:
    #            self.class_id = ''
    #
    #        self.mark = ''
    #
    #        #if self.subject_id:
    #        #    for sub in self.exam_id.subject_line:
    #        #        if sub.subject_id.id == self.subject_id.id:
    #        #            if sub.mark:
    #        #                self.mark = sub.mark
    #    domain = []
    #    subjects = self.exam_id.exam_lines
    #    for items in subjects:
    #        domain.append(items.subject_id.id)
    #    return {'domain': {'subject_id': [('id', 'in', domain)]}}

    @api.multi
    def create_mark_sheet(self):
        valuation_line_obj = self.env['odoocms.exam.valuation.line']
        students = self.class_id.student_ids
        if len(students) < 1:
            raise UserError(_('There are no students in this Class Section'))
        for student in students:
            data = {
                'student_id': student.id,
                'student_name': student.name,
                'valuation_id': self.id,
            }
            valuation_line_obj.create(data)
        self.mark_sheet_created = True

    @api.model
    def create(self, vals):
        res = super(OdooCMSExamValuation, self).create(vals)
        valuation_obj = self.env['odoocms.exam.valuation']
        #search_valuation = valuation_obj.search(
        #    [('exam_id', '=', res.exam_id.id), ('class_id', '=', res.class_id.id),
        #     ('subject_id', '=', res.subject_id.id), ('state', '!=', 'cancel')])
        search_valuation = valuation_obj.search([
            ('exam_id', '=', res.exam_id.id),
            ('class_id', '=', res.class_id.id), ('state', '!=', 'cancel')
        ])
        if len(search_valuation) > 1:
            #raise UserError(
            #    _('Valuation Sheet for \n Subject --> %s \nDivision --> %s \nExam --> %s \n is already created') % (
            #        res.subject_id.name, res.division_id.name, res.exam_id.name))
            raise UserError(
                _('Valuation Sheet for \n Class --> %s \nExam --> %s \n is already created'
                  ) % (res.class_id.name, res.exam_id.name))
        return res

    @api.multi
    def valuation_completed(self):
        self.name = str(self.exam_id.exam_type.name) + '-' + str(
            self.exam_id.date_start)[0:10] + ' (' + str(
                self.class_id.name) + ')'
        result_obj = self.env['odoocms.exam.results']
        result_line_obj = self.env['odoocms.results.subject.line']
        for students in self.valuation_line:
            search_result = result_obj.search([
                ('exam_id', '=', self.exam_id.id),
                ('class_id', '=', self.class_id.id),
                ('student_id', '=', students.student_id.id)
            ])
            if len(search_result) < 1:
                result_data = {
                    'name': self.name,
                    'exam_id': self.exam_id.id,
                    'class_id': self.class_id.id,
                    'student_id': students.student_id.id,
                    'student_name': students.student_id.name,
                }
                result = result_obj.create(result_data)
                result_line_data = {
                    'name': self.name,
                    #'subject_id': self.subject_id.id,
                    'max_mark': self.mark,
                    'pass_mark': self.pass_mark,
                    'mark_scored': students.mark_scored,
                    'pass_or_fail': students.pass_or_fail,
                    'result_id': result.id,
                    'exam_id': self.exam_id.id,
                    'class_id': self.class_id.id,
                    'student_id': students.student_id.id,
                    'student_name': students.student_id.name,
                }
                result_line_obj.create(result_line_data)
            else:
                result_line_data = {
                    #'subject_id': self.subject_id.id,
                    'max_mark': self.mark,
                    'pass_mark': self.pass_mark,
                    'mark_scored': students.mark_scored,
                    'pass_or_fail': students.pass_or_fail,
                    'result_id': search_result.id,
                    'exam_id': self.exam_id.id,
                    'class_id': self.class_id.id,
                    'student_id': students.student_id.id,
                    'student_name': students.student_id.name,
                }
                result_line_obj.create(result_line_data)
        self.state = 'completed'

    @api.multi
    def set_to_draft(self):
        result_line_obj = self.env['odoocms.results.subject.line']
        result_obj = self.env['odoocms.exam.results']
        for students in self.valuation_line:
            search_result = result_obj.search([
                ('exam_id', '=', self.exam_id.id),
                ('class_id', '=', self.class_id.id),
                ('student_id', '=', students.student_id.id)
            ])
            #search_result_line = result_line_obj.search(
            #    [('result_id', '=', search_result.id), ('subject_id', '=', self.subject_id.id)])
            search_result_line = result_line_obj.search([('result_id', '=',
                                                          search_result.id)])
            search_result_line.unlink()
        self.state = 'draft'

    @api.multi
    def valuation_canceled(self):
        self.state = 'cancel'
Пример #12
0
class HrEmployeeBase(models.AbstractModel):
    _name = "hr.employee.base"
    _inherit = "hr.employee.base"

    x_employee_language = fields.Selection(
        [
            ("english", "English"),
            ("french", "French"),
            ("bilingual", "Bilingual")
        ],
        string = "Language"
    )

    x_employee_job_type = fields.Char("Job type", groups="hr.group_hr_user,hr.group_hr_reporter")

    department_id_domain = fields.Char(
        compute = "_compute_department_id_domain",
        readonly = True,
        store = False 
    )

    parent_id_domain = fields.Char(
        compute = "_compute_parent_id_domain",
        readonly = True,
        store = False
    )

    @api.depends("department_id")
    def _compute_department_id_domain(self):
         # get the current user groups
        current_user = self.env.user 
        current_user_groups = list(map(lambda x: x.name, current_user.groups_id))

        # if Senior Management or Coordinator is in the user groups, return the restricted domain

        for rec in self: 
            if("Senior Management" in current_user_groups or "Coordinator" in current_user_groups or "Reporter" in current_user_groups):
                rec.department_id_domain = json.dumps(
                    ['|', ('id', 'child_of', [employee.department_id.id for employee in current_user.employee_ids]),
                          ('id','child_of',[department.id for department in current_user.x_department_coordinators_ids])
                    ]
                )
            else:
                rec.department_id_domain = json.dumps([("active", "=", True)])
    
    @api.depends("parent_id")
    def _compute_parent_id_domain(self):
        # get the current user groups
        current_user = self.env.user 
        current_user_groups = list(map(lambda x: x.name, current_user.groups_id))

        # if Senior Management or Coordinator is in the user groups, return the restricted domain

        for rec in self: 
            if("Senior Management" in current_user_groups or "Coordinator" in current_user_groups or "Reporter" in current_user_groups):
                rec.parent_id_domain = json.dumps(
                    ['|', ('id', 'child_of', [employee.id for employee in current_user.employee_ids]),
                          ('department_id', 'child_of', [department.id for department in current_user.x_department_coordinators_ids])
                    ]
                )
            else:
                rec.parent_id_domain = json.dumps([("active", "=", True)])
    
    x_employee_status = fields.Selection(
        [
            ("contractor", "Contractor"),
            ("casual", "Casual"),
            ("indeterminate", "Indeterminate"),
            ("term", "Term"),
            ("assignment", "Assignment"),
            ("student", "Student")
        ],
        string = "Employment status"
    )

    x_employee_access_gov_office = fields.Boolean("Access to a government office", groups="hr.group_hr_user,hr.group_hr_reporter")

    x_employee_in_ad = fields.Boolean("Employee in ESDC Directory", groups="hr.group_hr_user,hr.group_hr_reporter")

    x_employee_device_type = fields.Selection(
        [
            ("laptop", "Laptop"),
            ("desktop", "Desktop"),
            ("tablet", "Tablet")
        ],
        string = "Device type"
    )

    x_employee_asset_number = fields.Char(
        "Asset number"
    )
    
    x_employee_office_floor = fields.Char(
        "Office floor"
    )
    
    x_employee_office_cubicle = fields.Char(
        "Office cubicle"
    )
    x_employee_is_remote = fields.Boolean(
        "Remote employee"
    )

    x_employee_second_monitor = fields.Boolean(
        "Second monitor availability"
    )

    x_employee_mobile_hotspot = fields.Boolean(
        "Mobile hotspot availability"
    )

    x_employee_headset = fields.Boolean(
        "Headset availability"
    )

    classification_id = fields.Many2one(
        "hr.classification",
        ondelete = "set null"
    )

    region_id = fields.Many2one(
        "hr.region",
        ondelete = "set null"
    )

    branch_id = fields.Many2one(
        "hr.branch",
        ondelete = "set null"
    )

    x_employee_remote_access_network = fields.Boolean(
        "Remote access to network"
    )

    x_employee_remote_access_tool = fields.Selection(
        [
            ('both', "Both"),
            ("vpn", "VPN"),
            ("appgate", "AppGate")
        ],
        string = "Remote connection tool"
    )
    address_id = fields.Many2one('res.partner', 'Work Address', domain="['&', '|', ('company_id', '=', False), ('company_id', '=', company_id), ('is_company', '=', True)]")
Пример #13
0
class IrActionsReport(models.Model):
    _name = 'ir.actions.report'
    _description = 'Report Action'
    _inherit = 'ir.actions.actions'
    _table = 'ir_act_report_xml'
    _sequence = 'ir_actions_id_seq'
    _order = 'name'

    name = fields.Char(translate=True)
    type = fields.Char(default='ir.actions.report')
    binding_type = fields.Selection(default='report')
    model = fields.Char(required=True, string='Model Name')
    model_id = fields.Many2one('ir.model',
                               string='Model',
                               compute='_compute_model_id',
                               search='_search_model_id')

    report_type = fields.Selection(
        [
            ('qweb-html', 'HTML'),
            ('qweb-pdf', 'PDF'),
            ('qweb-text', 'Text'),
        ],
        required=True,
        default='qweb-pdf',
        help=
        'The type of the report that will be rendered, each one having its own'
        ' rendering method. HTML means the report will be opened directly in your'
        ' browser PDF means the report will be rendered using Wkhtmltopdf and'
        ' downloaded by the user.')
    report_name = fields.Char(
        string='Template Name',
        required=True,
        help=
        "For QWeb reports, name of the template used in the rendering. The method 'render_html' of the model 'report.template_name' will be called (if any) to give the html. For RML reports, this is the LocalService name."
    )
    report_file = fields.Char(
        string='Report File',
        required=False,
        readonly=False,
        store=True,
        help=
        "The path to the main report file (depending on Report Type) or empty if the content is in another field"
    )
    groups_id = fields.Many2many('res.groups',
                                 'res_groups_report_rel',
                                 'uid',
                                 'gid',
                                 string='Groups')
    multi = fields.Boolean(
        string='On Multiple Doc.',
        help=
        "If set to true, the action will not be displayed on the right toolbar of a form view."
    )

    paperformat_id = fields.Many2one('report.paperformat', 'Paper Format')
    print_report_name = fields.Char(
        'Printed Report Name',
        translate=True,
        help=
        "This is the filename of the report going to download. Keep empty to not change the report filename. You can use a python expression with the 'object' and 'time' variables."
    )
    attachment_use = fields.Boolean(
        string='Reload from Attachment',
        help=
        'If you check this, then the second time the user prints with same attachment name, it returns the previous report.'
    )
    attachment = fields.Char(
        string='Save as Attachment Prefix',
        help=
        'This is the filename of the attachment used to store the printing result. Keep empty to not save the printed reports. You can use a python expression with the object and time variables.'
    )

    @api.depends('model')
    def _compute_model_id(self):
        for action in self:
            action.model_id = self.env['ir.model']._get(action.model).id

    def _search_model_id(self, operator, value):
        ir_model_ids = None
        if isinstance(value, str):
            names = self.env['ir.model'].name_search(value, operator=operator)
            ir_model_ids = [n[0] for n in names]

        elif isinstance(value, Iterable):
            ir_model_ids = value

        elif isinstance(value, int) and not isinstance(value, bool):
            ir_model_ids = [value]

        if ir_model_ids:
            operator = 'not in' if operator in NEGATIVE_TERM_OPERATORS else 'in'
            ir_model = self.env['ir.model'].browse(ir_model_ids)
            return [('model', operator, ir_model.mapped('model'))]
        elif isinstance(value, bool) or value is None:
            return [('model', operator, value)]
        else:
            return FALSE_DOMAIN

    def associated_view(self):
        """Used in the ir.actions.report form view in order to search naively after the view(s)
        used in the rendering.
        """
        self.ensure_one()
        action_ref = self.env.ref('base.action_ui_view')
        if not action_ref or len(self.report_name.split('.')) < 2:
            return False
        action_data = action_ref.read()[0]
        action_data['domain'] = [('name', 'ilike',
                                  self.report_name.split('.')[1]),
                                 ('type', '=', 'qweb')]
        return action_data

    def create_action(self):
        """ Create a contextual action for each report. """
        for report in self:
            model = self.env['ir.model']._get(report.model)
            report.write({
                'binding_model_id': model.id,
                'binding_type': 'report'
            })
        return True

    def unlink_action(self):
        """ Remove the contextual actions created for the reports. """
        self.check_access_rights('write', raise_exception=True)
        self.filtered('binding_model_id').write({'binding_model_id': False})
        return True

    #--------------------------------------------------------------------------
    # Main report methods
    #--------------------------------------------------------------------------
    def _retrieve_stream_from_attachment(self, attachment):
        #This import is needed to make sure a PDF stream can be saved in Image
        from PIL import PdfImagePlugin
        if attachment.mimetype.startswith('image'):
            stream = io.BytesIO(base64.b64decode(attachment.datas))
            img = Image.open(stream)
            img.convert("RGB").save(stream, format="pdf")
            return stream
        return io.BytesIO(base64.decodestring(attachment.datas))

    def retrieve_attachment(self, record):
        '''Retrieve an attachment for a specific record.

        :param record: The record owning of the attachment.
        :param attachment_name: The optional name of the attachment.
        :return: A recordset of length <=1 or None
        '''
        attachment_name = safe_eval(self.attachment, {
            'object': record,
            'time': time
        }) if self.attachment else ''
        if not attachment_name:
            return None
        return self.env['ir.attachment'].search(
            [('name', '=', attachment_name), ('res_model', '=', self.model),
             ('res_id', '=', record.id)],
            limit=1)

    def postprocess_pdf_report(self, record, buffer):
        '''Hook to handle post processing during the pdf report generation.
        The basic behavior consists to create a new attachment containing the pdf
        base64 encoded.

        :param record_id: The record that will own the attachment.
        :param pdf_content: The optional name content of the file to avoid reading both times.
        :return: A modified buffer if the previous one has been modified, None otherwise.
        '''
        attachment_name = safe_eval(self.attachment, {
            'object': record,
            'time': time
        })
        if not attachment_name:
            return None
        attachment_vals = {
            'name': attachment_name,
            'datas': base64.encodestring(buffer.getvalue()),
            'res_model': self.model,
            'res_id': record.id,
            'type': 'binary',
        }
        try:
            self.env['ir.attachment'].create(attachment_vals)
        except AccessError:
            _logger.info("Cannot save PDF report %r as attachment",
                         attachment_vals['name'])
        else:
            _logger.info('The PDF document %s is now saved in the database',
                         attachment_vals['name'])
        return buffer

    @api.model
    def get_wkhtmltopdf_state(self):
        '''Get the current state of wkhtmltopdf: install, ok, upgrade, workers or broken.
        * install: Starting state.
        * upgrade: The binary is an older version (< 0.12.0).
        * ok: A binary was found with a recent version (>= 0.12.0).
        * workers: Not enough workers found to perform the pdf rendering process (< 2 workers).
        * broken: A binary was found but not responding.

        :return: wkhtmltopdf_state
        '''
        return wkhtmltopdf_state

    @api.model
    def get_paperformat(self):
        return self.paperformat_id or self.env.company.paperformat_id

    @api.model
    def _build_wkhtmltopdf_args(self,
                                paperformat_id,
                                landscape,
                                specific_paperformat_args=None,
                                set_viewport_size=False):
        '''Build arguments understandable by wkhtmltopdf bin.

        :param paperformat_id: A report.paperformat record.
        :param landscape: Force the report orientation to be landscape.
        :param specific_paperformat_args: A dictionary containing prioritized wkhtmltopdf arguments.
        :param set_viewport_size: Enable a viewport sized '1024x1280' or '1280x1024' depending of landscape arg.
        :return: A list of string representing the wkhtmltopdf process command args.
        '''
        if landscape is None and specific_paperformat_args and specific_paperformat_args.get(
                'data-report-landscape'):
            landscape = specific_paperformat_args.get('data-report-landscape')

        command_args = ['--disable-local-file-access']
        if set_viewport_size:
            command_args.extend(
                ['--viewport-size', landscape and '1024x1280' or '1280x1024'])

        # Passing the cookie to wkhtmltopdf in order to resolve internal links.
        try:
            if request:
                command_args.extend(
                    ['--cookie', 'session_id', request.session.sid])
        except AttributeError:
            pass

        # Less verbose error messages
        command_args.extend(['--quiet'])

        # Build paperformat args
        if paperformat_id:
            if paperformat_id.format and paperformat_id.format != 'custom':
                command_args.extend(['--page-size', paperformat_id.format])

            if paperformat_id.page_height and paperformat_id.page_width and paperformat_id.format == 'custom':
                command_args.extend(
                    ['--page-width',
                     str(paperformat_id.page_width) + 'mm'])
                command_args.extend(
                    ['--page-height',
                     str(paperformat_id.page_height) + 'mm'])

            if specific_paperformat_args and specific_paperformat_args.get(
                    'data-report-margin-top'):
                command_args.extend([
                    '--margin-top',
                    str(specific_paperformat_args['data-report-margin-top'])
                ])
            else:
                command_args.extend(
                    ['--margin-top',
                     str(paperformat_id.margin_top)])

            dpi = None
            if specific_paperformat_args and specific_paperformat_args.get(
                    'data-report-dpi'):
                dpi = int(specific_paperformat_args['data-report-dpi'])
            elif paperformat_id.dpi:
                if os.name == 'nt' and int(paperformat_id.dpi) <= 95:
                    _logger.info(
                        "Generating PDF on Windows platform require DPI >= 96. Using 96 instead."
                    )
                    dpi = 96
                else:
                    dpi = paperformat_id.dpi
            if dpi:
                command_args.extend(['--dpi', str(dpi)])
                if wkhtmltopdf_dpi_zoom_ratio:
                    command_args.extend(['--zoom', str(96.0 / dpi)])

            if specific_paperformat_args and specific_paperformat_args.get(
                    'data-report-header-spacing'):
                command_args.extend([
                    '--header-spacing',
                    str(specific_paperformat_args['data-report-header-spacing']
                        )
                ])
            elif paperformat_id.header_spacing:
                command_args.extend(
                    ['--header-spacing',
                     str(paperformat_id.header_spacing)])

            command_args.extend(
                ['--margin-left',
                 str(paperformat_id.margin_left)])
            command_args.extend(
                ['--margin-bottom',
                 str(paperformat_id.margin_bottom)])
            command_args.extend(
                ['--margin-right',
                 str(paperformat_id.margin_right)])
            if not landscape and paperformat_id.orientation:
                command_args.extend(
                    ['--orientation',
                     str(paperformat_id.orientation)])
            if paperformat_id.header_line:
                command_args.extend(['--header-line'])

        if landscape:
            command_args.extend(['--orientation', 'landscape'])

        return command_args

    def _prepare_html(self, html):
        '''Divide and recreate the header/footer html by merging all found in html.
        The bodies are extracted and added to a list. Then, extract the specific_paperformat_args.
        The idea is to put all headers/footers together. Then, we will use a javascript trick
        (see minimal_layout template) to set the right header/footer during the processing of wkhtmltopdf.
        This allows the computation of multiple reports in a single call to wkhtmltopdf.

        :param html: The html rendered by render_qweb_html.
        :type: bodies: list of string representing each one a html body.
        :type header: string representing the html header.
        :type footer: string representing the html footer.
        :type specific_paperformat_args: dictionary of prioritized paperformat values.
        :return: bodies, header, footer, specific_paperformat_args
        '''
        IrConfig = self.env['ir.config_parameter'].sudo()
        base_url = IrConfig.get_param('report.url') or IrConfig.get_param(
            'web.base.url')

        # Return empty dictionary if 'web.minimal_layout' not found.
        layout = self.env.ref('web.minimal_layout', False)
        if not layout:
            return {}
        layout = self.env['ir.ui.view'].browse(
            self.env['ir.ui.view'].get_view_id('web.minimal_layout'))

        root = lxml.html.fromstring(html)
        match_klass = "//div[contains(concat(' ', normalize-space(@class), ' '), ' {} ')]"

        header_node = etree.Element('div', id='minimal_layout_report_headers')
        footer_node = etree.Element('div', id='minimal_layout_report_footers')
        bodies = []
        res_ids = []

        body_parent = root.xpath('//main')[0]
        # Retrieve headers
        for node in root.xpath(match_klass.format('header')):
            body_parent = node.getparent()
            node.getparent().remove(node)
            header_node.append(node)

        # Retrieve footers
        for node in root.xpath(match_klass.format('footer')):
            body_parent = node.getparent()
            node.getparent().remove(node)
            footer_node.append(node)

        # Retrieve bodies
        for node in root.xpath(match_klass.format('article')):
            layout_with_lang = layout
            # set context language to body language
            if node.get('data-oe-lang'):
                layout_with_lang = layout_with_lang.with_context(
                    lang=node.get('data-oe-lang'))
            body = layout_with_lang.render(
                dict(subst=False,
                     body=lxml.html.tostring(node),
                     base_url=base_url))
            bodies.append(body)
            if node.get('data-oe-model') == self.model:
                res_ids.append(int(node.get('data-oe-id', 0)))
            else:
                res_ids.append(None)

        if not bodies:
            body = bytearray().join(
                [lxml.html.tostring(c) for c in body_parent.getchildren()])
            bodies.append(body)

        # Get paperformat arguments set in the root html tag. They are prioritized over
        # paperformat-record arguments.
        specific_paperformat_args = {}
        for attribute in root.items():
            if attribute[0].startswith('data-report-'):
                specific_paperformat_args[attribute[0]] = attribute[1]

        header = layout.render(
            dict(subst=True,
                 body=lxml.html.tostring(header_node),
                 base_url=base_url))
        footer = layout.render(
            dict(subst=True,
                 body=lxml.html.tostring(footer_node),
                 base_url=base_url))

        return bodies, res_ids, header, footer, specific_paperformat_args

    @api.model
    def _run_wkhtmltopdf(self,
                         bodies,
                         header=None,
                         footer=None,
                         landscape=False,
                         specific_paperformat_args=None,
                         set_viewport_size=False):
        '''Execute wkhtmltopdf as a subprocess in order to convert html given in input into a pdf
        document.

        :param bodies: The html bodies of the report, one per page.
        :param header: The html header of the report containing all headers.
        :param footer: The html footer of the report containing all footers.
        :param landscape: Force the pdf to be rendered under a landscape format.
        :param specific_paperformat_args: dict of prioritized paperformat arguments.
        :param set_viewport_size: Enable a viewport sized '1024x1280' or '1280x1024' depending of landscape arg.
        :return: Content of the pdf as a string
        '''
        paperformat_id = self.get_paperformat()

        # Build the base command args for wkhtmltopdf bin
        command_args = self._build_wkhtmltopdf_args(
            paperformat_id,
            landscape,
            specific_paperformat_args=specific_paperformat_args,
            set_viewport_size=set_viewport_size)

        files_command_args = []
        temporary_files = []
        if header:
            head_file_fd, head_file_path = tempfile.mkstemp(
                suffix='.html', prefix='report.header.tmp.')
            with closing(os.fdopen(head_file_fd, 'wb')) as head_file:
                head_file.write(header)
            temporary_files.append(head_file_path)
            files_command_args.extend(['--header-html', head_file_path])
        if footer:
            foot_file_fd, foot_file_path = tempfile.mkstemp(
                suffix='.html', prefix='report.footer.tmp.')
            with closing(os.fdopen(foot_file_fd, 'wb')) as foot_file:
                foot_file.write(footer)
            temporary_files.append(foot_file_path)
            files_command_args.extend(['--footer-html', foot_file_path])

        paths = []
        for i, body in enumerate(bodies):
            prefix = '%s%d.' % ('report.body.tmp.', i)
            body_file_fd, body_file_path = tempfile.mkstemp(suffix='.html',
                                                            prefix=prefix)
            with closing(os.fdopen(body_file_fd, 'wb')) as body_file:
                body_file.write(body)
            paths.append(body_file_path)
            temporary_files.append(body_file_path)

        pdf_report_fd, pdf_report_path = tempfile.mkstemp(suffix='.pdf',
                                                          prefix='report.tmp.')
        os.close(pdf_report_fd)
        temporary_files.append(pdf_report_path)

        try:
            wkhtmltopdf = [
                _get_wkhtmltopdf_bin()
            ] + command_args + files_command_args + paths + [pdf_report_path]
            process = subprocess.Popen(wkhtmltopdf,
                                       stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE)
            out, err = process.communicate()

            if process.returncode not in [0, 1]:
                if process.returncode == -11:
                    message = _(
                        'Wkhtmltopdf failed (error code: %s). Memory limit too low or maximum file number of subprocess reached. Message : %s'
                    )
                else:
                    message = _(
                        'Wkhtmltopdf failed (error code: %s). Message: %s')
                raise UserError(message %
                                (str(process.returncode), err[-1000:]))
            else:
                if err:
                    _logger.warning('wkhtmltopdf: %s' % err)
        except:
            raise

        with open(pdf_report_path, 'rb') as pdf_document:
            pdf_content = pdf_document.read()

        # Manual cleanup of the temporary files
        for temporary_file in temporary_files:
            try:
                os.unlink(temporary_file)
            except (OSError, IOError):
                _logger.error('Error when trying to remove file %s' %
                              temporary_file)

        return pdf_content

    @api.model
    def _get_report_from_name(self, report_name):
        """Get the first record of ir.actions.report having the ``report_name`` as value for
        the field report_name.
        """
        report_obj = self.env['ir.actions.report']
        conditions = [('report_name', '=', report_name)]
        context = self.env['res.users'].context_get()
        return report_obj.with_context(context).search(conditions, limit=1)

    @api.model
    def barcode(self,
                barcode_type,
                value,
                width=600,
                height=100,
                humanreadable=0,
                quiet=1):
        if barcode_type == 'UPCA' and len(value) in (11, 12, 13):
            barcode_type = 'EAN13'
            if len(value) in (11, 12):
                value = '0%s' % value
        try:
            width, height, humanreadable, quiet = int(width), int(
                height), bool(int(humanreadable)), bool(int(quiet))
            barcode = createBarcodeDrawing(barcode_type,
                                           value=value,
                                           format='png',
                                           width=width,
                                           height=height,
                                           humanReadable=humanreadable,
                                           quiet=quiet)
            return barcode.asString('png')
        except (ValueError, AttributeError):
            if barcode_type == 'Code128':
                raise ValueError("Cannot convert into barcode.")
            else:
                return self.barcode('Code128',
                                    value,
                                    width=width,
                                    height=height,
                                    humanreadable=humanreadable,
                                    quiet=quiet)

    def render_template(self, template, values=None):
        """Allow to render a QWeb template python-side. This function returns the 'ir.ui.view'
        render but embellish it with some variables/methods used in reports.
        :param values: additionnal methods/variables used in the rendering
        :returns: html representation of the template
        """
        if values is None:
            values = {}

        context = dict(self.env.context, inherit_branding=False)

        # Browse the user instead of using the sudo self.env.user
        user = self.env['res.users'].browse(self.env.uid)
        website = None
        if request and hasattr(request, 'website'):
            if request.website is not None:
                website = request.website
                context = dict(context,
                               translatable=context.get('lang') !=
                               request.env['ir.http']._get_default_lang().code)

        view_obj = self.env['ir.ui.view'].with_context(context)
        values.update(
            time=time,
            context_timestamp=lambda t: fields.Datetime.context_timestamp(
                self.with_context(tz=user.tz), t),
            user=user,
            res_company=user.company_id,
            website=website,
            web_base_url=self.env['ir.config_parameter'].sudo().get_param(
                'web.base.url', default=''),
        )
        return view_obj.render_template(template, values)

    def _post_pdf(self, save_in_attachment, pdf_content=None, res_ids=None):
        '''Merge the existing attachments by adding one by one the content of the attachments
        and then, we add the pdf_content if exists. Create the attachments for each record individually
        if required.

        :param save_in_attachment: The retrieved attachments as map record.id -> attachment_id.
        :param pdf_content: The pdf content newly generated by wkhtmltopdf.
        :param res_ids: the ids of record to allow postprocessing.
        :return: The pdf content of the merged pdf.
        '''
        def close_streams(streams):
            for stream in streams:
                try:
                    stream.close()
                except Exception:
                    pass

        # Check special case having only one record with existing attachment.
        # In that case, return directly the attachment content.
        # In that way, we also ensure the embedded files are well preserved.
        if len(save_in_attachment) == 1 and not pdf_content:
            return list(save_in_attachment.values())[0].getvalue()

        # Create a list of streams representing all sub-reports part of the final result
        # in order to append the existing attachments and the potentially modified sub-reports
        # by the postprocess_pdf_report calls.
        streams = []

        # In wkhtmltopdf has been called, we need to split the pdf in order to call the postprocess method.
        if pdf_content:
            pdf_content_stream = io.BytesIO(pdf_content)
            # Build a record_map mapping id -> record
            record_map = {
                r.id: r
                for r in self.env[self.model].browse(
                    [res_id for res_id in res_ids if res_id])
            }

            # If no value in attachment or no record specified, only append the whole pdf.
            if not record_map or not self.attachment:
                streams.append(pdf_content_stream)
            else:
                if len(res_ids) == 1:
                    # Only one record, so postprocess directly and append the whole pdf.
                    if res_ids[0] in record_map and not res_ids[
                            0] in save_in_attachment:
                        new_stream = self.postprocess_pdf_report(
                            record_map[res_ids[0]], pdf_content_stream)
                        # If the buffer has been modified, mark the old buffer to be closed as well.
                        if new_stream and new_stream != pdf_content_stream:
                            close_streams([pdf_content_stream])
                            pdf_content_stream = new_stream
                    streams.append(pdf_content_stream)
                else:
                    # In case of multiple docs, we need to split the pdf according the records.
                    # To do so, we split the pdf based on top outlines computed by wkhtmltopdf.
                    # An outline is a <h?> html tag found on the document. To retrieve this table,
                    # we look on the pdf structure using pypdf to compute the outlines_pages from
                    # the top level heading in /Outlines.
                    reader = PdfFileReader(pdf_content_stream)
                    root = reader.trailer['/Root']
                    if '/Outlines' in root and '/First' in root['/Outlines']:
                        outlines_pages = []
                        node = root['/Outlines']['/First']
                        while True:
                            outlines_pages.append(
                                root['/Dests'][node['/Dest']][0])
                            if '/Next' not in node:
                                break
                            node = node['/Next']
                        outlines_pages = sorted(set(outlines_pages))
                        # There should be only one top-level heading by document
                        assert len(outlines_pages) == len(res_ids)
                        # There should be a top-level heading on first page
                        assert outlines_pages[0] == 0
                        for i, num in enumerate(outlines_pages):
                            to = outlines_pages[i + 1] if i + 1 < len(
                                outlines_pages) else reader.numPages
                            attachment_writer = PdfFileWriter()
                            for j in range(num, to):
                                attachment_writer.addPage(reader.getPage(j))
                            stream = io.BytesIO()
                            attachment_writer.write(stream)
                            if res_ids[i] and res_ids[
                                    i] not in save_in_attachment:
                                new_stream = self.postprocess_pdf_report(
                                    record_map[res_ids[i]], stream)
                                # If the buffer has been modified, mark the old buffer to be closed as well.
                                if new_stream and new_stream != stream:
                                    close_streams([stream])
                                    stream = new_stream
                            streams.append(stream)
                        close_streams([pdf_content_stream])
                    else:
                        # If no outlines available, do not save each record
                        streams.append(pdf_content_stream)

        # If attachment_use is checked, the records already having an existing attachment
        # are not been rendered by wkhtmltopdf. So, create a new stream for each of them.
        if self.attachment_use:
            for stream in save_in_attachment.values():
                streams.append(stream)

        # Build the final pdf.
        # If only one stream left, no need to merge them (and then, preserve embedded files).
        if len(streams) == 1:
            result = streams[0].getvalue()
        else:
            result = self._merge_pdfs(streams)

        # We have to close the streams after PdfFileWriter's call to write()
        close_streams(streams)
        return result

    def _merge_pdfs(self, streams):
        writer = PdfFileWriter()
        for stream in streams:
            reader = PdfFileReader(stream)
            writer.appendPagesFromReader(reader)
        result_stream = io.BytesIO()
        streams.append(result_stream)
        writer.write(result_stream)
        return result_stream.getvalue()

    def render_qweb_pdf(self, res_ids=None, data=None):
        if not data:
            data = {}
        data.setdefault('report_type', 'pdf')

        # In case of test environment without enough workers to perform calls to wkhtmltopdf,
        # fallback to render_html.
        if (tools.config['test_enable'] or tools.config['test_file']
            ) and not self.env.context.get('force_report_rendering'):
            return self.render_qweb_html(res_ids, data=data)

        # As the assets are generated during the same transaction as the rendering of the
        # templates calling them, there is a scenario where the assets are unreachable: when
        # you make a request to read the assets while the transaction creating them is not done.
        # Indeed, when you make an asset request, the controller has to read the `ir.attachment`
        # table.
        # This scenario happens when you want to print a PDF report for the first time, as the
        # assets are not in cache and must be generated. To workaround this issue, we manually
        # commit the writes in the `ir.attachment` table. It is done thanks to a key in the context.
        context = dict(self.env.context)
        if not config['test_enable']:
            context['commit_assetsbundle'] = True

        # Disable the debug mode in the PDF rendering in order to not split the assets bundle
        # into separated files to load. This is done because of an issue in wkhtmltopdf
        # failing to load the CSS/Javascript resources in time.
        # Without this, the header/footer of the reports randomly disapear
        # because the resources files are not loaded in time.
        # https://github.com/wkhtmltopdf/wkhtmltopdf/issues/2083
        context['debug'] = False

        # The test cursor prevents the use of another environnment while the current
        # transaction is not finished, leading to a deadlock when the report requests
        # an asset bundle during the execution of test scenarios. In this case, return
        # the html version.
        if isinstance(self.env.cr, TestCursor):
            return self.with_context(context).render_qweb_html(res_ids,
                                                               data=data)[0]

        save_in_attachment = OrderedDict()
        if res_ids:
            # Dispatch the records by ones having an attachment and ones requesting a call to
            # wkhtmltopdf.
            Model = self.env[self.model]
            record_ids = Model.browse(res_ids)
            wk_record_ids = Model
            if self.attachment:
                for record_id in record_ids:
                    attachment = self.retrieve_attachment(record_id)
                    if attachment:
                        save_in_attachment[
                            record_id.
                            id] = self._retrieve_stream_from_attachment(
                                attachment)
                    if not self.attachment_use or not attachment:
                        wk_record_ids += record_id
            else:
                wk_record_ids = record_ids
            res_ids = wk_record_ids.ids

        # A call to wkhtmltopdf is mandatory in 2 cases:
        # - The report is not linked to a record.
        # - The report is not fully present in attachments.
        if save_in_attachment and not res_ids:
            _logger.info('The PDF report has been generated from attachments.')
            return self._post_pdf(save_in_attachment), 'pdf'

        if self.get_wkhtmltopdf_state() == 'install':
            # wkhtmltopdf is not installed
            # the call should be catched before (cf /report/check_wkhtmltopdf) but
            # if get_pdf is called manually (email template), the check could be
            # bypassed
            raise UserError(
                _("Unable to find Wkhtmltopdf on this system. The PDF can not be created."
                  ))

        html = self.with_context(context).render_qweb_html(res_ids,
                                                           data=data)[0]

        # Ensure the current document is utf-8 encoded.
        html = html.decode('utf-8')

        bodies, html_ids, header, footer, specific_paperformat_args = self.with_context(
            context)._prepare_html(html)

        if self.attachment and set(res_ids) != set(html_ids):
            raise UserError(
                _("The report's template '%s' is wrong, please contact your administrator. \n\n"
                  "Can not separate file to save as attachment because the report's template does not contains the attributes 'data-oe-model' and 'data-oe-id' on the div with 'article' classname."
                  ) % self.name)

        pdf_content = self._run_wkhtmltopdf(
            bodies,
            header=header,
            footer=footer,
            landscape=context.get('landscape'),
            specific_paperformat_args=specific_paperformat_args,
            set_viewport_size=context.get('set_viewport_size'),
        )
        if res_ids:
            _logger.info(
                'The PDF report has been generated for model: %s, records %s.'
                % (self.model, str(res_ids)))
            return self._post_pdf(save_in_attachment,
                                  pdf_content=pdf_content,
                                  res_ids=html_ids), 'pdf'
        return pdf_content, 'pdf'

    @api.model
    def render_qweb_text(self, docids, data=None):
        if not data:
            data = {}
        data.setdefault('report_type', 'text')
        data = self._get_rendering_context(docids, data)
        return self.render_template(self.report_name, data), 'text'

    @api.model
    def render_qweb_html(self, docids, data=None):
        """This method generates and returns html version of a report.
        """
        if not data:
            data = {}
        data.setdefault('report_type', 'html')
        data = self._get_rendering_context(docids, data)
        return self.render_template(self.report_name, data), 'html'

    @api.model
    def _get_rendering_context_model(self):
        report_model_name = 'report.%s' % self.report_name
        return self.env.get(report_model_name)

    @api.model
    def _get_rendering_context(self, docids, data):
        # If the report is using a custom model to render its html, we must use it.
        # Otherwise, fallback on the generic html rendering.
        report_model = self._get_rendering_context_model()

        data = data and dict(data) or {}

        if report_model is not None:
            data.update(report_model._get_report_values(docids, data=data))
        else:
            docs = self.env[self.model].browse(docids)
            data.update({
                'doc_ids': docids,
                'doc_model': self.model,
                'docs': docs,
            })
        return data

    def render(self, res_ids, data=None):
        report_type = self.report_type.lower().replace('-', '_')
        render_func = getattr(self, 'render_' + report_type, None)
        if not render_func:
            return None
        return render_func(res_ids, data=data)

    def report_action(self, docids, data=None, config=True):
        """Return an action of type ir.actions.report.

        :param docids: id/ids/browserecord of the records to print (if not used, pass an empty list)
        :param report_name: Name of the template to generate an action for
        """
        context = self.env.context
        if docids:
            if isinstance(docids, models.Model):
                active_ids = docids.ids
            elif isinstance(docids, int):
                active_ids = [docids]
            elif isinstance(docids, list):
                active_ids = docids
            context = dict(self.env.context, active_ids=active_ids)

        report_action = {
            'context': context,
            'data': data,
            'type': 'ir.actions.report',
            'report_name': self.report_name,
            'report_type': self.report_type,
            'report_file': self.report_file,
            'name': self.name,
        }

        discard_logo_check = self.env.context.get('discard_logo_check')
        if self.env.is_admin(
        ) and not self.env.company.external_report_layout_id and config and not discard_logo_check:
            action = self.env.ref(
                'base.action_base_document_layout_configurator').read()[0]
            ctx = action.get('context')
            py_ctx = json.loads(ctx) if ctx else {}
            report_action['close_on_report_download'] = True
            py_ctx['report_action'] = report_action
            action['context'] = py_ctx
            return action

        return report_action
Пример #14
0
class Applicant(models.Model):
    _name = "hr.applicant"
    _description = "Applicant"
    _order = "priority desc, id desc"
    _inherit = ['mail.thread.cc', 'mail.activity.mixin', 'utm.mixin']

    def _default_stage_id(self):
        if self._context.get('default_job_id'):
            return self.env['hr.recruitment.stage'].search(
                [
                    '|', ('job_ids', '=', False),
                    ('job_ids', '=', self._context['default_job_id']),
                    ('fold', '=', False)
                ],
                order='sequence asc',
                limit=1).id
        return False

    def _default_company_id(self):
        company_id = False
        if self._context.get('default_department_id'):
            department = self.env['hr.department'].browse(
                self._context['default_department_id'])
            company_id = department.company_id.id
        if not company_id:
            company_id = self.env.company
        return company_id

    name = fields.Char("Subject / Application Name", required=True)
    active = fields.Boolean(
        "Active",
        default=True,
        help=
        "If the active field is set to false, it will allow you to hide the case without removing it."
    )
    description = fields.Text("Description")
    email_from = fields.Char("Email", size=128, help="Applicant email")
    probability = fields.Float("Probability")
    partner_id = fields.Many2one('res.partner', "Contact", copy=False)
    create_date = fields.Datetime("Creation Date", readonly=True, index=True)
    stage_id = fields.Many2one(
        'hr.recruitment.stage',
        'Stage',
        ondelete='restrict',
        tracking=True,
        domain="['|', ('job_ids', '=', False), ('job_ids', '=', job_id)]",
        copy=False,
        index=True,
        group_expand='_read_group_stage_ids',
        default=_default_stage_id)
    last_stage_id = fields.Many2one(
        'hr.recruitment.stage',
        "Last Stage",
        help=
        "Stage of the applicant before being in the current stage. Used for lost cases analysis."
    )
    categ_ids = fields.Many2many('hr.applicant.category', string="Tags")
    company_id = fields.Many2one('res.company',
                                 "Company",
                                 default=_default_company_id)
    user_id = fields.Many2one('res.users',
                              "Responsible",
                              tracking=True,
                              default=lambda self: self.env.uid)
    date_closed = fields.Datetime("Closed", readonly=True, index=True)
    date_open = fields.Datetime("Assigned", readonly=True, index=True)
    date_last_stage_update = fields.Datetime("Last Stage Update",
                                             index=True,
                                             default=fields.Datetime.now)
    priority = fields.Selection(AVAILABLE_PRIORITIES,
                                "Appreciation",
                                default='0')
    job_id = fields.Many2one('hr.job', "Applied Job")
    salary_proposed_extra = fields.Char(
        "Proposed Salary Extra",
        help="Salary Proposed by the Organisation, extra advantages")
    salary_expected_extra = fields.Char(
        "Expected Salary Extra",
        help="Salary Expected by Applicant, extra advantages")
    salary_proposed = fields.Float("Proposed Salary",
                                   group_operator="avg",
                                   help="Salary Proposed by the Organisation")
    salary_expected = fields.Float("Expected Salary",
                                   group_operator="avg",
                                   help="Salary Expected by Applicant")
    availability = fields.Date(
        "Availability",
        help=
        "The date at which the applicant will be available to start working")
    partner_name = fields.Char("Applicant's Name")
    partner_phone = fields.Char("Phone", size=32)
    partner_mobile = fields.Char("Mobile", size=32)
    type_id = fields.Many2one('hr.recruitment.degree', "Degree")
    department_id = fields.Many2one('hr.department', "Department")
    reference = fields.Char("Referred By")
    day_open = fields.Float(compute='_compute_day', string="Days to Open")
    day_close = fields.Float(compute='_compute_day', string="Days to Close")
    delay_close = fields.Float(compute="_compute_day",
                               string='Delay to Close',
                               readonly=True,
                               group_operator="avg",
                               help="Number of days to close",
                               store=True)
    color = fields.Integer("Color Index", default=0)
    emp_id = fields.Many2one('hr.employee',
                             string="Employee",
                             help="Employee linked to the applicant.")
    user_email = fields.Char(related='user_id.email',
                             type="char",
                             string="User Email",
                             readonly=True)
    attachment_number = fields.Integer(compute='_get_attachment_number',
                                       string="Number of Attachments")
    employee_name = fields.Char(related='emp_id.name',
                                string="Employee Name",
                                readonly=False,
                                tracking=False)
    attachment_ids = fields.One2many('ir.attachment',
                                     'res_id',
                                     domain=[('res_model', '=', 'hr.applicant')
                                             ],
                                     string='Attachments')
    kanban_state = fields.Selection([('normal', 'Grey'), ('done', 'Green'),
                                     ('blocked', 'Red')],
                                    string='Kanban State',
                                    copy=False,
                                    default='normal',
                                    required=True)
    legend_blocked = fields.Char(related='stage_id.legend_blocked',
                                 string='Kanban Blocked',
                                 readonly=False)
    legend_done = fields.Char(related='stage_id.legend_done',
                              string='Kanban Valid',
                              readonly=False)
    legend_normal = fields.Char(related='stage_id.legend_normal',
                                string='Kanban Ongoing',
                                readonly=False)
    application_count = fields.Integer(compute='_compute_application_count',
                                       help='Applications with the same email')

    @api.depends('date_open', 'date_closed')
    def _compute_day(self):
        for applicant in self:
            if applicant.date_open:
                date_create = applicant.create_date
                date_open = applicant.date_open
                applicant.day_open = (
                    date_open - date_create).total_seconds() / (24.0 * 3600)
            if applicant.date_closed:
                date_create = applicant.create_date
                date_closed = applicant.date_closed
                applicant.day_close = (
                    date_closed - date_create).total_seconds() / (24.0 * 3600)
                applicant.delay_close = applicant.day_close - applicant.day_open

    @api.depends('email_from')
    def _compute_application_count(self):
        application_data = self.env['hr.applicant'].read_group(
            [('email_from', 'in', list(set(self.mapped('email_from'))))],
            ['email_from'], ['email_from'])
        application_data_mapped = dict(
            (data['email_from'], data['email_from_count'])
            for data in application_data)
        for applicant in self.filtered(lambda applicant: applicant.email_from):
            applicant.application_count = application_data_mapped.get(
                applicant.email_from, 1) - 1

    def _get_attachment_number(self):
        read_group_res = self.env['ir.attachment'].read_group(
            [('res_model', '=', 'hr.applicant'),
             ('res_id', 'in', self.ids)], ['res_id'], ['res_id'])
        attach_data = dict(
            (res['res_id'], res['res_id_count']) for res in read_group_res)
        for record in self:
            record.attachment_number = attach_data.get(record.id, 0)

    @api.model
    def _read_group_stage_ids(self, stages, domain, order):
        # retrieve job_id from the context and write the domain: ids + contextual columns (job or default)
        job_id = self._context.get('default_job_id')
        search_domain = [('job_ids', '=', False)]
        if job_id:
            search_domain = ['|', ('job_ids', '=', job_id)] + search_domain
        if stages:
            search_domain = ['|', ('id', 'in', stages.ids)] + search_domain

        stage_ids = stages._search(search_domain,
                                   order=order,
                                   access_rights_uid=SUPERUSER_ID)
        return stages.browse(stage_ids)

    @api.onchange('job_id')
    def onchange_job_id(self):
        vals = self._onchange_job_id_internal(self.job_id.id)
        self.department_id = vals['value']['department_id']
        self.user_id = vals['value']['user_id']
        self.stage_id = vals['value']['stage_id']

    def _onchange_job_id_internal(self, job_id):
        department_id = False
        user_id = False
        stage_id = self.stage_id.id or self._context.get('default_stage_id')
        if job_id:
            job = self.env['hr.job'].browse(job_id)
            department_id = job.department_id.id
            user_id = job.user_id.id
            if not stage_id:
                stage_ids = self.env['hr.recruitment.stage'].search(
                    [
                        '|', ('job_ids', '=', False), ('job_ids', '=', job.id),
                        ('fold', '=', False)
                    ],
                    order='sequence asc',
                    limit=1).ids
                stage_id = stage_ids[0] if stage_ids else False

        return {
            'value': {
                'department_id': department_id,
                'user_id': user_id,
                'stage_id': stage_id
            }
        }

    @api.onchange('email_from')
    def onchange_email_from(self):
        if self.partner_id and self.email_from and not self.partner_id.email:
            self.partner_id.email = self.email_from

    @api.onchange('partner_phone')
    def onchange_partner_phone(self):
        if self.partner_id and self.partner_phone and not self.partner_id.phone:
            self.partner_id.phone = self.partner_phone

    @api.onchange('partner_mobile')
    def onchange_partner_mobile(self):
        if self.partner_id and self.partner_mobile and not self.partner_id.mobile:
            self.partner_id.mobile = self.partner_mobile

    @api.onchange('partner_id')
    def onchange_partner_id(self):
        self.partner_phone = self.partner_id.phone
        self.partner_mobile = self.partner_id.mobile
        self.email_from = self.partner_id.email

    @api.onchange('stage_id')
    def onchange_stage_id(self):
        vals = self._onchange_stage_id_internal(self.stage_id.id)
        if vals['value'].get('date_closed'):
            self.date_closed = vals['value']['date_closed']

    def _onchange_stage_id_internal(self, stage_id):
        if not stage_id:
            return {'value': {}}
        stage = self.env['hr.recruitment.stage'].browse(stage_id)
        if stage.fold:
            return {'value': {'date_closed': fields.datetime.now()}}
        return {'value': {'date_closed': False}}

    @api.model
    def create(self, vals):
        if vals.get('department_id'
                    ) and not self._context.get('default_department_id'):
            self = self.with_context(
                default_department_id=vals.get('department_id'))
        if vals.get('job_id') or self._context.get('default_job_id'):
            job_id = vals.get('job_id') or self._context.get('default_job_id')
            for key, value in self._onchange_job_id_internal(
                    job_id)['value'].items():
                if key not in vals:
                    vals[key] = value
        if vals.get('user_id'):
            vals['date_open'] = fields.Datetime.now()
        if 'stage_id' in vals:
            vals.update(
                self._onchange_stage_id_internal(
                    vals.get('stage_id'))['value'])
        return super(Applicant, self).create(vals)

    def write(self, vals):
        # user_id change: update date_open
        if vals.get('user_id'):
            vals['date_open'] = fields.Datetime.now()
        # stage_id: track last stage before update
        if 'stage_id' in vals:
            vals['date_last_stage_update'] = fields.Datetime.now()
            vals.update(
                self._onchange_stage_id_internal(
                    vals.get('stage_id'))['value'])
            if 'kanban_state' not in vals:
                vals['kanban_state'] = 'normal'
            for applicant in self:
                vals['last_stage_id'] = applicant.stage_id.id
                res = super(Applicant, self).write(vals)
        else:
            res = super(Applicant, self).write(vals)
        return res

    @api.model
    def get_empty_list_help(self, help):
        return super(
            Applicant,
            self.with_context(
                empty_list_help_model='hr.job',
                empty_list_help_id=self.env.context.get('default_job_id'),
                empty_list_help_document_name=_(
                    "job applicant"))).get_empty_list_help(help)

    def action_get_created_employee(self):
        self.ensure_one()
        action = self.env['ir.actions.act_window'].for_xml_id(
            'hr', 'open_view_employee_list')
        action['res_id'] = self.mapped('emp_id').ids[0]
        return action

    def action_makeMeeting(self):
        """ This opens Meeting's calendar view to schedule meeting on current applicant
            @return: Dictionary value for created Meeting view
        """
        self.ensure_one()
        partners = self.partner_id | self.user_id.partner_id | self.department_id.manager_id.user_id.partner_id

        category = self.env.ref('hr_recruitment.categ_meet_interview')
        res = self.env['ir.actions.act_window'].for_xml_id(
            'calendar', 'action_calendar_event')
        res['context'] = {
            'search_default_partner_ids': self.partner_id.name,
            'default_partner_ids': partners.ids,
            'default_user_id': self.env.uid,
            'default_name': self.name,
            'default_categ_ids': category and [category.id] or False,
        }
        return res

    def action_get_attachment_tree_view(self):
        attachment_action = self.env.ref('base.action_attachment')
        action = attachment_action.read()[0]
        action['context'] = {
            'default_res_model': self._name,
            'default_res_id': self.ids[0]
        }
        action['domain'] = str(
            ['&', ('res_model', '=', self._name), ('res_id', 'in', self.ids)])
        action['search_view_id'] = (self.env.ref(
            'hr_recruitment.ir_attachment_view_search_inherit_hr_recruitment').
                                    id, )
        return action

    def action_applications_email(self):
        return {
            'type': 'ir.actions.act_window',
            'name': _('Applications'),
            'res_model': self._name,
            'view_mode': 'kanban,tree,form,pivot,graph,calendar,activity',
            'domain': [('email_from', 'in', self.mapped('email_from'))],
        }

    def _track_template(self, changes):
        res = super(Applicant, self)._track_template(changes)
        applicant = self[0]
        if 'stage_id' in changes and applicant.stage_id.template_id:
            res['stage_id'] = (applicant.stage_id.template_id, {
                'auto_delete_message':
                True,
                'subtype_id':
                self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note'),
                'email_layout_xmlid':
                'mail.mail_notification_light'
            })
        return res

    def _creation_subtype(self):
        return self.env.ref('hr_recruitment.mt_applicant_new')

    def _track_subtype(self, init_values):
        record = self[0]
        if 'emp_id' in init_values and record.emp_id and record.emp_id.active:
            return self.env.ref('hr_recruitment.mt_applicant_hired')
        elif 'stage_id' in init_values and record.stage_id:
            return self.env.ref('hr_recruitment.mt_applicant_stage_changed')
        return super(Applicant, self)._track_subtype(init_values)

    def _notify_get_reply_to(self,
                             default=None,
                             records=None,
                             company=None,
                             doc_names=None):
        """ Override to set alias of applicants to their job definition if any. """
        aliases = self.mapped('job_id')._notify_get_reply_to(default=default,
                                                             records=None,
                                                             company=company,
                                                             doc_names=None)
        res = {app.id: aliases.get(app.job_id.id) for app in self}
        leftover = self.filtered(lambda rec: not rec.job_id)
        if leftover:
            res.update(
                super(Applicant,
                      leftover)._notify_get_reply_to(default=default,
                                                     records=None,
                                                     company=company,
                                                     doc_names=doc_names))
        return res

    def _message_get_suggested_recipients(self):
        recipients = super(Applicant, self)._message_get_suggested_recipients()
        for applicant in self:
            if applicant.partner_id:
                applicant._message_add_suggested_recipient(
                    recipients,
                    partner=applicant.partner_id,
                    reason=_('Contact'))
            elif applicant.email_from:
                email_from = applicant.email_from
                if applicant.partner_name:
                    email_from = '%s<%s>' % (applicant.partner_name,
                                             email_from)
                applicant._message_add_suggested_recipient(
                    recipients, email=email_from, reason=_('Contact Email'))
        return recipients

    @api.model
    def message_new(self, msg, custom_values=None):
        """ Overrides mail_thread message_new that is called by the mailgateway
            through message_process.
            This override updates the document according to the email.
        """
        # remove default author when going through the mail gateway. Indeed we
        # do not want to explicitly set user_id to False; however we do not
        # want the gateway user to be responsible if no other responsible is
        # found.
        self = self.with_context(default_user_id=False)
        val = msg.get('from').split('<')[0]
        defaults = {
            'name': msg.get('subject') or _("No Subject"),
            'partner_name': val,
            'email_from': msg.get('from'),
            'partner_id': msg.get('author_id', False),
        }
        if msg.get('priority'):
            defaults['priority'] = msg.get('priority')
        if custom_values:
            defaults.update(custom_values)
        return super(Applicant, self).message_new(msg, custom_values=defaults)

    def _message_post_after_hook(self, message, msg_vals):
        if self.email_from and not self.partner_id:
            # we consider that posting a message with a specified recipient (not a follower, a specific one)
            # on a document without customer means that it was created through the chatter using
            # suggested recipients. This heuristic allows to avoid ugly hacks in JS.
            new_partner = message.partner_ids.filtered(
                lambda partner: partner.email == self.email_from)
            if new_partner:
                self.search([('partner_id', '=', False),
                             ('email_from', '=', new_partner.email),
                             ('stage_id.fold', '=', False)
                             ]).write({'partner_id': new_partner.id})
        return super(Applicant,
                     self)._message_post_after_hook(message, msg_vals)

    def create_employee_from_applicant(self):
        """ Create an hr.employee from the hr.applicants """
        employee = False
        for applicant in self:
            contact_name = False
            if applicant.partner_id:
                address_id = applicant.partner_id.address_get(['contact'
                                                               ])['contact']
                contact_name = applicant.partner_id.display_name
            else:
                if not applicant.partner_name:
                    raise UserError(
                        _('You must define a Contact Name for this applicant.')
                    )
                new_partner_id = self.env['res.partner'].create({
                    'is_company':
                    False,
                    'name':
                    applicant.partner_name,
                    'email':
                    applicant.email_from,
                    'phone':
                    applicant.partner_phone,
                    'mobile':
                    applicant.partner_mobile
                })
                address_id = new_partner_id.address_get(['contact'])['contact']
            if applicant.partner_name or contact_name:
                employee = self.env['hr.employee'].create({
                    'name':
                    applicant.partner_name or contact_name,
                    'job_id':
                    applicant.job_id.id or False,
                    'job_title':
                    applicant.job_id.name,
                    'address_home_id':
                    address_id,
                    'department_id':
                    applicant.department_id.id or False,
                    'address_id':
                    applicant.company_id and applicant.company_id.partner_id
                    and applicant.company_id.partner_id.id or False,
                    'work_email':
                    applicant.department_id
                    and applicant.department_id.company_id
                    and applicant.department_id.company_id.email or False,
                    'work_phone':
                    applicant.department_id
                    and applicant.department_id.company_id
                    and applicant.department_id.company_id.phone or False
                })
                applicant.write({'emp_id': employee.id})
                if applicant.job_id:
                    applicant.job_id.write({
                        'no_of_hired_employee':
                        applicant.job_id.no_of_hired_employee + 1
                    })
                    applicant.job_id.message_post(
                        body=_('New Employee %s Hired') %
                        applicant.partner_name
                        if applicant.partner_name else applicant.name,
                        subtype="hr_recruitment.mt_job_applicant_hired")
                applicant.message_post_with_view(
                    'hr_recruitment.applicant_hired_template',
                    values={'applicant': applicant},
                    subtype_id=self.env.ref(
                        "hr_recruitment.mt_applicant_hired").id)

        employee_action = self.env.ref('hr.open_view_employee_list')
        dict_act_window = employee_action.read([])[0]
        dict_act_window['context'] = {'form_view_initial_mode': 'edit'}
        dict_act_window['res_id'] = employee.id
        return dict_act_window

    def archive_applicant(self):
        self.write({'active': False})

    def reset_applicant(self):
        """ Reinsert the applicant into the recruitment pipe in the first stage"""
        default_stage_id = self._default_stage_id()
        self.write({'active': True, 'stage_id': default_stage_id})
class TmsExpenseLine(models.Model):
    _name = 'tms.expense.line'
    _description = 'Expense Line'

    loan_id = fields.Many2one('tms.expense.loan', string='Loan')
    travel_id = fields.Many2one(
        'tms.travel',
        string='Travel')
    expense_id = fields.Many2one(
        'tms.expense',
        string='Expense',
        readonly=True)
    product_qty = fields.Float(
        string='Qty', default=1.0)
    unit_price = fields.Float()
    price_subtotal = fields.Float(
        compute='_compute_price_subtotal',
        string='Subtotal',)
    product_uom_id = fields.Many2one(
        'product.uom',
        string='Unit of Measure')
    line_type = fields.Selection(
        [('real_expense', 'Real Expense'),
         ('made_up_expense', 'Made-up Expense'),
         ('salary', 'Salary'),
         ('fuel', 'Fuel'),
         ('fuel_cash', 'Fuel in Cash'),
         ('refund', 'Refund'),
         ('salary_retention', 'Salary Retention'),
         ('salary_discount', 'Salary Discount'),
         ('other_income', 'Other Income'),
         ('tollstations', 'Toll Stations'),
         ('loan', 'Loan')],
        compute='_compute_line_type',
        store=True, readonly=True)
    name = fields.Char(
        'Description',
        required=True)
    sequence = fields.Integer(
        help="Gives the sequence order when displaying a list of "
        "sales order lines.",
        default=10)
    price_total = fields.Float(
        string='Total',
        compute='_compute_price_total',
    )
    tax_amount = fields.Float(
        compute='_compute_tax_amount',)
    special_tax_amount = fields.Float(
        string='Special Tax'
    )
    tax_ids = fields.Many2many(
        'account.tax',
        string='Taxes',
        domain=[('type_tax_use', '=', 'purchase')])
    notes = fields.Text()
    employee_id = fields.Many2one(
        'hr.employee',
        string='Driver')
    date = fields.Date()
    state = fields.Char(readonly=True)
    control = fields.Boolean()
    automatic = fields.Boolean(
        help="Check this if you want to create Advances and/or "
        "Fuel Vouchers for this line automatically")
    is_invoice = fields.Boolean(string='Is Invoice?')
    partner_id = fields.Many2one(
        'res.partner', string='Supplier',
        domain=[('company_type', '=', 'company')])
    invoice_date = fields.Date('Date')
    invoice_number = fields.Char()
    invoice_id = fields.Many2one(
        'account.invoice',
        string='Supplier Invoice')
    product_id = fields.Many2one(
        'product.product',
        string='Product',
        required=True,)
    route_id = fields.Many2one(
        'tms.route', related='travel_id.route_id',
        string='Route', readonly=True)

    @api.onchange('product_id')
    def _onchange_product_id(self):
        if self.line_type not in [
                'salary', 'salary_retention', 'salary_discount']:
            self.tax_ids = self.product_id.supplier_taxes_id
        self.line_type = self.product_id.tms_product_category
        self.product_uom_id = self.product_id.uom_id.id
        self.name = self.product_id.name

    @api.depends('product_id')
    def _compute_line_type(self):
        for rec in self:
            rec.line_type = rec.product_id.tms_product_category

    @api.depends('tax_ids', 'product_qty', 'unit_price')
    def _compute_tax_amount(self):
        for rec in self:
            taxes = rec.tax_ids.compute_all(
                rec.unit_price, rec.expense_id.currency_id,
                rec.product_qty,
                rec.expense_id.employee_id.address_home_id)
            if taxes['taxes']:
                for tax in taxes['taxes']:
                    rec.tax_amount += tax['amount']
            else:
                rec.tax_amount = 0.0

    @api.depends('product_qty', 'unit_price', 'line_type')
    def _compute_price_subtotal(self):
        for rec in self:
            if rec.line_type in [
                    'salary_retention', 'salary_discount', 'loan']:
                rec.price_subtotal = rec.product_qty * rec.unit_price * -1
            elif rec.line_type == 'fuel':
                rec.price_subtotal = rec.unit_price
            else:
                rec.price_subtotal = rec.product_qty * rec.unit_price

    @api.depends('price_subtotal', 'tax_ids')
    def _compute_price_total(self):
        for rec in self:
            if rec.line_type == 'fuel':
                rec.price_total = rec.unit_price
            elif rec.line_type in [
                    'salary_retention', 'salary_discount', 'loan']:
                rec.price_total = rec.price_subtotal - rec.tax_amount
            else:
                rec.price_total = rec.price_subtotal + rec.tax_amount

    @api.model
    def create(self, values):
        expense_line = super(TmsExpenseLine, self).create(values)
        if expense_line.line_type in (
                'salary_discount', 'salary_retention', 'loan'):
            if expense_line.price_total > 0:
                raise ValidationError(_('This line type needs a '
                                        'negative value to continue!'))
        return expense_line
class HrExpenseRegisterPaymentWizard(models.TransientModel):

    _name = "hr.expense.register.payment.wizard"
    _description = "Hr Expense Register Payment wizard"

    @api.model
    def _default_partner_id(self):
        context = dict(self._context or {})
        active_ids = context.get('active_ids', [])
        expense_sheet = self.env['hr.expense.sheet'].browse(active_ids)
        return expense_sheet.address_id.id or expense_sheet.employee_id.id and expense_sheet.employee_id.address_home_id.id

    partner_id = fields.Many2one('res.partner',
                                 string='Partner',
                                 required=True,
                                 default=_default_partner_id)
    journal_id = fields.Many2one('account.journal',
                                 string='Payment Method',
                                 required=True,
                                 domain=[('type', 'in', ('bank', 'cash'))])
    company_id = fields.Many2one('res.company',
                                 related='journal_id.company_id',
                                 string='Company',
                                 readonly=True,
                                 required=True)
    payment_method_id = fields.Many2one('account.payment.method',
                                        string='Payment Type',
                                        required=True)
    amount = fields.Monetary(string='Payment Amount', required=True)
    currency_id = fields.Many2one(
        'res.currency',
        string='Currency',
        required=True,
        default=lambda self: self.env.user.company_id.currency_id)
    payment_date = fields.Date(string='Payment Date',
                               default=fields.Date.context_today,
                               required=True)
    communication = fields.Char(string='Memo')
    hide_payment_method = fields.Boolean(
        compute='_compute_hide_payment_method',
        help=
        "Technical field used to hide the payment method if the selected journal has only one available which is 'manual'"
    )

    @api.one
    @api.constrains('amount')
    def _check_amount(self):
        if not self.amount > 0.0:
            raise ValidationError(
                _('The payment amount must be strictly positive.'))

    @api.one
    @api.depends('journal_id')
    def _compute_hide_payment_method(self):
        if not self.journal_id:
            self.hide_payment_method = True
            return
        journal_payment_methods = self.journal_id.outbound_payment_method_ids
        self.hide_payment_method = len(
            journal_payment_methods
        ) == 1 and journal_payment_methods[0].code == 'manual'

    @api.onchange('journal_id')
    def _onchange_journal(self):
        if self.journal_id:
            # Set default payment method (we consider the first to be the default one)
            payment_methods = self.journal_id.outbound_payment_method_ids
            self.payment_method_id = payment_methods and payment_methods[
                0] or False
            # Set payment method domain (restrict to methods enabled for the journal and to selected payment type)
            return {
                'domain': {
                    'payment_method_id': [('payment_type', '=', 'outbound'),
                                          ('id', 'in', payment_methods.ids)]
                }
            }
        return {}

    def get_payment_vals(self):
        """ Hook for extension """
        return {
            'partner_type': 'supplier',
            'payment_type': 'outbound',
            'partner_id': self.partner_id.id,
            'journal_id': self.journal_id.id,
            'company_id': self.company_id.id,
            'payment_method_id': self.payment_method_id.id,
            'amount': self.amount,
            'currency_id': self.currency_id.id,
            'payment_date': self.payment_date,
            'communication': self.communication
        }

    @api.multi
    def expense_post_payment(self):
        self.ensure_one()
        context = dict(self._context or {})
        active_ids = context.get('active_ids', [])
        expense_sheet = self.env['hr.expense.sheet'].browse(active_ids)

        # Create payment and post it
        payment = self.env['account.payment'].create(self.get_payment_vals())
        payment.post()

        # Log the payment in the chatter
        #body = (_("A payment of %s %s with the reference <a href='/mail/view?'%s>%s</a> related to your expense %s has been made.") % (payment.amount, payment.currency_id.symbol, url_encode({'model': 'account.payment', 'res_id': payment.id}), payment.name, expense_sheet.name))
        body = (_(
            "A payment of %s %s with the reference <a href=\"/mail/view?%s\">%s</a> related to your expense %s has been made."
        ) % (payment.amount, payment.currency_id.symbol,
             url_encode({
                 'model': 'account.payment',
                 'res_id': payment.id
             }), payment.name, expense_sheet.name))
        expense_sheet.message_post(body=body)

        # Reconcile the payment and the expense, i.e. lookup on the payable account move lines
        account_move_lines_to_reconcile = self.env['account.move.line']
        for line in payment.move_line_ids + expense_sheet.account_move_id.line_ids:
            if line.account_id.internal_type == 'payable':
                account_move_lines_to_reconcile |= line
        # DO NOT FORWARD-PORT! ONLY FOR v10
        if len(expense_sheet.expense_line_ids) > 1:
            return payment.open_payment_matching_screen()
        else:
            account_move_lines_to_reconcile.reconcile()

        return {'type': 'ir.actions.act_window_close'}
Пример #17
0
class AccountStandardLedger(models.TransientModel):
    _name = 'account.report.standard.ledger'
    _description = 'Account Standard Ledger'

    def _get_periode_date(self):
        lang_code = self.env.user.lang or 'en_US'
        date_format = self.env['res.lang']._lang_get(lang_code).date_format

        today_year = fields.datetime.now().year

        company = self.env.user.company_id
        last_day = company.fiscalyear_last_day or 31
        last_month = company.fiscalyear_last_month or 12

        periode_obj = self.env['account.report.standard.ledger.periode']
        periode_obj.search([]).unlink()
        periode_ids = periode_obj
        for year in range(today_year, today_year - 4, -1):
            date_from = datetime(year - 1, last_month, last_day) + timedelta(days=1)
            date_to = datetime(year, last_month, last_day)
            user_periode = "%s - %s" % (date_from.strftime(date_format),
                                        date_to.strftime(date_format),
                                        )
            vals = {
                'name': user_periode,
                'date_from': date_from.strftime(DEFAULT_SERVER_DATE_FORMAT),
                'date_to': date_to.strftime(DEFAULT_SERVER_DATE_FORMAT), }
            periode_ids += periode_obj.create(vals)
        return False

    name = fields.Char(default='Standard Report')
    type_ledger = fields.Selection([('general', 'General Ledger'), ('partner', 'Partner Ledger'), ('journal', 'Journal Ledger'), ('open', 'Open Ledger'), ('aged', 'Aged Balance'), ('analytic', 'Analytic Ledger')], string='Type', default='general', required=True,
                                   help=' * General Ledger : Journal entries group by account\n'
                                   ' * Partner Leger : Journal entries group by partner, with only payable/recevable accounts\n'
                                   ' * Journal Ledger : Journal entries group by journal, without initial balance\n'
                                   ' * Open Ledger : Openning journal at Start date\n')
    summary = fields.Boolean('Trial Balance', default=False,
                             help=' * Check : generate a trial balance.\n'
                             ' * Uncheck : detail report.\n')
    amount_currency = fields.Boolean("With Currency", help="It adds the currency column on report if the currency differs from the company currency.")
    reconciled = fields.Boolean('With Reconciled Entries', default=True,
                                help='Only for entrie with a payable/receivable account.\n'
                                ' * Check this box to see un-reconcillied and reconciled entries with payable.\n'
                                ' * Uncheck to see only un-reconcillied entries. Can be use only with parnter ledger.\n')
    partner_select_ids = fields.Many2many(comodel_name='res.partner', string='Partners', domain=['|', ('is_company', '=', True), ('parent_id', '=', False)], help='If empty, get all partners')
    account_methode = fields.Selection([('include', 'Include'), ('exclude', 'Exclude')], string="Methode")
    account_in_ex_clude = fields.Many2many(comodel_name='account.account', string='Accounts', help='If empty, get all accounts')
    analytic_account_select_ids = fields.Many2many(comodel_name='account.analytic.account', string='Analytic Accounts')
    init_balance_history = fields.Boolean('Initial balance with history.', default=True,
                                          help=' * Check this box if you need to report all the debit and the credit sum before the Start Date.\n'
                                          ' * Uncheck this box to report only the balance before the Start Date\n')
    company_id = fields.Many2one('res.company', string='Company', readonly=True, default=lambda self: self.env.user.company_id)
    company_currency_id = fields.Many2one('res.currency', related='company_id.currency_id', string="Company Currency", readonly=True,
                                          help='Utility field to express amount currency', store=True)
    journal_ids = fields.Many2many('account.journal', string='Journals', required=True, default=lambda self: self.env['account.journal'].search([('company_id', '=', self.env.user.company_id.id)]),
                                   help='Select journal, for the Open Ledger you need to set all journals.')
    date_from = fields.Date(string='Start Date', help='Use to compute initial balance.')
    date_to = fields.Date(string='End Date', help='Use to compute the entrie matched with futur.')
    target_move = fields.Selection([('posted', 'All Posted Entries'),
                                    ('all', 'All Entries'),
                                    ], string='Target Moves', required=True, default='posted')
    periode_date = fields.Many2one('account.report.standard.ledger.periode', 'Periode', default=_get_periode_date, help="Auto complete Start and End date.")
    month_selec = fields.Selection([(1, '01 Junary'), (2, '02 Febuary'), (3, '03 March'), (4, '04 April'), (5, '05 May'), (6, '06 June'),
                                    (7, '07 Jully'), (8, '08 August'), (9, '09 September'), (10, '10 October'), (11, '11 November'), (12, '12 December')],
                                   string='Month')
    result_selection = fields.Selection([('customer', 'Customers'),
                                         ('supplier', 'Suppliers'),
                                         ('customer_supplier', 'Customers and Suppliers')
                                         ], string="Partner's", required=True, default='supplier')
    report_name = fields.Char('Report Name')
    compact_account = fields.Boolean('Compacte account.', default=False)
    report_id = fields.Many2one('account.report.standard.ledger.report')
    account_ids = fields.Many2many('account.account', relation='table_standard_report_accounts')
    partner_ids = fields.Many2many('res.partner', relation='table_standard_report_partner')
    type = fields.Selection([('account', 'Account'), ('partner', 'Partner'), ('journal', 'Journal'), ('analytic', 'Analytic')])

    @api.onchange('account_in_ex_clude')
    def on_change_summary(self):
        if self.account_in_ex_clude:
            self.account_methode = 'include'
        else:
            self.account_methode = False

    @api.onchange('type_ledger')
    def on_change_type_ledger(self):
        if self.type_ledger in ('partner', 'journal', 'open', 'aged'):
            self.compact_account = False
        if self.type_ledger == 'aged':
            self.date_from = False
            self.reconciled = False
        else:
            self.on_change_periode_date()
            self.on_change_month_selec()
        if self.type_ledger not in ('partner', 'aged',):
            self.reconciled = True
            return {'domain': {'account_in_ex_clude': []}}
        self.account_in_ex_clude = False
        if self.result_selection == 'suplier':
            return {'domain': {'account_in_ex_clude': [('type_third_parties', '=', 'supplier')]}}
        if self.result_selection == 'customer':
            return {'domain': {'account_in_ex_clude': [('type_third_parties', '=', 'customer')]}}
        return {'domain': {'account_in_ex_clude': [('type_third_parties', 'in', ('supplier', 'customer'))]}}

    @api.onchange('periode_date')
    def on_change_periode_date(self):
        if self.periode_date:
            self.date_from = self.periode_date.date_from
            self.date_to = self.periode_date.date_to
            if self.month_selec:
                self.on_change_month_selec()

    @api.onchange('month_selec')
    def on_change_month_selec(self):
        if self.periode_date and self.month_selec:
            date_from = datetime.strptime(self.periode_date.date_from, DEFAULT_SERVER_DATETIME_FORMAT)
            date_from = datetime(date_from.year, self.month_selec, 1)
            date_to = datetime(date_from.year, self.month_selec, calendar.monthrange(date_from.year, self.month_selec)[1])
            self.date_from = date_from.strftime(DEFAULT_SERVER_DATE_FORMAT)
            self.date_to = date_to.strftime(DEFAULT_SERVER_DATE_FORMAT)
        elif self.periode_date and not self.month_selec:
            self.on_change_periode_date()

    def action_view_lines(self):
        self.ensure_one()
        self._compute_data()
        return {
            'name': self.report_id.name,
            'view_type': 'form',
            'view_mode': 'tree,form',
            'views': [(self.env.ref('account_standard_report.view_aged_tree').id if self.type_ledger == 'aged' else False, 'tree'), (False, 'form')],
            'res_model': 'account.report.standard.ledger.line',
            'type': 'ir.actions.act_window',
            'domain': "[('report_id','=',%s),('type','not in',('5_super_total','4_total'))]" % (self.report_id.id),
            'context': {'search_default_%s' % self.type_ledger: 1},
            'target': 'current',
        }

    def print_pdf_report(self):
        self.ensure_one()
        self._compute_data()
        return self.env.ref('account_standard_report.action_standard_report').report_action(self)

    def print_excel_report(self):
        self.ensure_one()
        self._compute_data()
        return self.env.ref('account_standard_report.action_standard_excel').report_action(self)

    def _pre_compute(self):
        lang_code = self.env.context.get('lang') or 'en_US'
        date_format = self.env['res.lang']._lang_get(lang_code).date_format
        time_format = self.env['res.lang']._lang_get(lang_code).time_format

        vals = {'report_name': self._get_name_report(),
                'name': self._get_name_report(),
                'print_time': '%s' % fields.Datetime.context_timestamp(self.with_context(tz=self.env.user.tz), datetime.now()).strftime(('%s %s') % (date_format, time_format)),
                'date_to': self.date_to if self.date_to else "2099-01-01",
                'date_from': self.date_from if self.date_from else "1970-01-01",
                }
        self.report_id = self.env['account.report.standard.ledger.report'].create(vals)
        self.account_ids = self._search_account()
        self.partner_ids = self._search_partner()
        self.analytic_account_ids = self._search_analytic_account()

        if self.type_ledger in ('general', 'open'):
            self.type = 'account'
        elif self.type_ledger in ('partner', 'aged'):
            self.type = 'partner'
        elif self.type_ledger == 'analytic':
            self.type = 'analytic'
        else:
            self.type = 'journal'

        if self.type_ledger in ('partner', 'journal', 'open', 'aged', 'analytic'):
            self.compact_account = False
        if self.type_ledger not in ('partner', 'aged',):
            self.reconciled = True
            self.partner_select_ids = False

    def _compute_data(self):
        if not self.user_has_groups('account.group_account_user'):
            raise UserError(_('Your are not an accountant !'))
        self.env['account.move.line'].check_access_rights('read')
        self._pre_compute()

        self._sql_report_object()
        if self.type == 'account':
            self._sql_unaffected_earnings()
        if self.type in ('account, partner'):
            if self.type_ledger != 'aged':
                self._sql_init_balance()
        self._sql_lines()
        if self.compact_account and self.type_ledger == 'general':
            self._sql_lines_compacted()
        self._sql_total()
        self._sql_super_total()
        self.refresh()

        # complet total line
        line_obj = self.env['account.report.standard.ledger.line']
        self.report_id.line_total_ids = line_obj.search([('report_id', '=', self.report_id.id), ('type', '=', '4_total')])
        self.report_id.line_super_total_id = line_obj.search([('report_id', '=', self.report_id.id), ('type', '=', '5_super_total')], limit=1)
        self._format_total()

    def _sql_report_object(self):
        query = """INSERT INTO  account_report_standard_ledger_report_object
            (report_id, create_uid, create_date, object_id, name, account_id, partner_id, journal_id, analytic_account_id)
            SELECT DISTINCT
                %s AS report_id,
                %s AS create_uid,
                NOW() AS create_date,
                CASE
                    WHEN %s = 'account' THEN aml.account_id
                    WHEN %s = 'partner' THEN aml.partner_id
                    WHEN %s = 'analytic' THEN aml.analytic_account_id
                    ELSE aml.journal_id
                END AS object_id,
                CASE
                    WHEN %s = 'account' THEN acc.code || ' ' || acc.name
                    WHEN %s = 'partner' THEN CASE WHEN rep.ref IS NULL THEN rep.name ELSE rep.ref || ' ' || rep.name END
                    WHEN %s = 'analytic' THEN CASE WHEN an_acc.code IS NULL THEN an_acc.name ELSE an_acc.code || ' ' || an_acc.code END
                    ELSE acj.code || ' ' || acj.name
                END AS name,
                CASE WHEN %s = 'account' THEN aml.account_id ELSE NULL END AS account_id,
                CASE WHEN %s = 'partner' THEN aml.partner_id ELSE NULL END AS partner_id,
                CASE WHEN %s = 'journal' THEN aml.journal_id ELSE NULL END AS journal_id,
                CASE WHEN %s = 'analytic' THEN aml.analytic_account_id ELSE NULL END AS analytic_account_id
            FROM
                account_move_line aml
                LEFT JOIN account_account acc ON (acc.id = aml.account_id)
                LEFT JOIN res_partner rep ON (rep.id = aml.partner_id)
                LEFT JOIN account_journal acj ON (acj.id = aml.journal_id)
                LEFT JOIN account_analytic_account an_acc ON (acj.id = aml.analytic_account_id)
            WHERE
                aml.company_id = %s
                AND aml.journal_id IN %s
                AND aml.account_id IN %s
                AND (%s IN ('account','journal','analytic') OR aml.partner_id IN %s)
                AND (%s != 'analytic' OR aml.analytic_account_id IN %s)
            ORDER BY
                name
            """

        params = [
            # SELECT
            self.report_id.id,
            self.env.uid,
            self.type, self.type, self.type,
            self.type, self.type, self.type,
            self.type, self.type, self.type, self.type,
            # WHERE
            self.company_id.id,
            tuple(self.journal_ids.ids) if self.journal_ids else (None,),
            tuple(self.account_ids.ids) if self.account_ids else (None,),
            self.type,
            tuple(self.partner_ids.ids) if self.partner_ids else (None,),
            self.type,
            tuple(self.analytic_account_ids.ids) if self.analytic_account_ids else (None,),
        ]

        self.env.cr.execute(query, tuple(params))

    def _sql_unaffected_earnings(self):
        company = self.company_id
        unaffected_earnings_account = self.env['account.account'].search([('company_id', '=', company.id), ('user_type_id', '=', self.env.ref('account.data_unaffected_earnings').id)], limit=1)
        if unaffected_earnings_account not in self.account_ids:
            return

        report_object_id = self.report_id.report_object_ids.filtered(lambda x: x.object_id == unaffected_earnings_account.id)
        if not report_object_id:
            report_object_id = self.report_id.report_object_ids.create({'report_id': self.report_id.id,
                                                                        'object_id': unaffected_earnings_account.id,
                                                                        'name': '%s %s' % (unaffected_earnings_account.code, unaffected_earnings_account.name),
                                                                        'account_id': unaffected_earnings_account.id})
        query = """
        INSERT INTO account_report_standard_ledger_line
            (report_id, create_uid, create_date, account_id, type, type_view, date, debit, credit, balance, cumul_balance, company_currency_id, reconciled, report_object_id)
        SELECT
            %s AS report_id,
            %s AS create_uid,
            NOW() AS create_date,
            %s AS account_id,
            '0_init' AS type,
            'init' AS type_view,
            %s AS date,
            CASE WHEN %s THEN COALESCE(SUM(aml.debit), 0) ELSE CASE WHEN COALESCE(SUM(aml.balance), 0) <= 0 THEN 0 ELSE COALESCE(SUM(aml.balance), 0) END END AS debit,
            CASE WHEN %s THEN COALESCE(SUM(aml.credit), 0) ELSE CASE WHEN COALESCE(SUM(aml.balance), 0) >= 0 THEN 0 ELSE COALESCE(-SUM(aml.balance), 0) END END AS credit,
            COALESCE(SUM(aml.balance), 0) AS balance,
            COALESCE(SUM(aml.balance), 0) AS cumul_balance,
            %s AS company_currency_id,
            FALSE as reconciled,
            %s AS report_object_id
        FROM
            account_move_line aml
            LEFT JOIN account_account acc ON (aml.account_id = acc.id)
            LEFT JOIN account_account_type acc_type ON (acc.user_type_id = acc_type.id)
            LEFT JOIN account_move m ON (aml.move_id = m.id)
        WHERE
            m.state IN %s
            AND aml.company_id = %s
            AND aml.date < %s
            AND acc_type.include_initial_balance = FALSE
        HAVING
            CASE
                WHEN %s = FALSE THEN ABS(SUM(aml.balance)) > %s
                ELSE ABS(SUM(aml.debit)) > %s OR ABS(SUM(aml.debit)) > %s OR ABS(SUM(aml.balance)) > %s
            END
        """

        date_from_fiscal = self.company_id.compute_fiscalyear_dates(datetime.strptime(self.report_id.date_from, DEFAULT_SERVER_DATE_FORMAT))['date_from']

        params = [
            # SELECT
            self.report_id.id,
            self.env.uid,
            unaffected_earnings_account.id,
            date_from_fiscal,
            self.init_balance_history,
            self.init_balance_history,
            self.company_currency_id.id,
            report_object_id.id,
            # WHERE
            ('posted',) if self.target_move == 'posted' else ('posted', 'draft',),
            company.id,
            self.report_id.date_from,
            # HAVING
            self.init_balance_history,
            self.company_currency_id.rounding, self.company_currency_id.rounding, self.company_currency_id.rounding, self.company_currency_id.rounding,
        ]

        self.env.cr.execute(query, tuple(params))

    def _sql_init_balance(self):
        company = self.company_id
        # initial balance partner
        query = """
        INSERT INTO account_report_standard_ledger_line
            (report_id, create_uid, create_date, account_id, partner_id, group_by_key, type, type_view, date, debit, credit, balance, cumul_balance, company_currency_id, reconciled, report_object_id)

        WITH matching_in_futur_before_init (id) AS
        (
        SELECT DISTINCT
            afr.id as id
        FROM
            account_full_reconcile afr
        INNER JOIN account_move_line aml ON aml.full_reconcile_id=afr.id
        WHERE
            aml.company_id = %s
            AND aml.date >= %s
        )
        SELECT
            %s AS report_id,
            %s AS create_uid,
            NOW() AS create_date,
            MIN(aml.account_id),
            CASE WHEN %s = 'partner' THEN MIN(aml.partner_id) ELSE NULL END,
            (CASE
                WHEN %s = 'account' THEN '-' || aml.account_id
                ELSE aml.partner_id || '-' || aml.account_id
            END) AS group_by_key,
            '0_init' AS type,
            'init' AS type_view,
            %s AS date,
            CASE WHEN %s THEN COALESCE(SUM(aml.debit), 0) ELSE CASE WHEN COALESCE(SUM(aml.balance), 0) <= 0 THEN 0 ELSE COALESCE(SUM(aml.balance), 0) END END AS debit,
            CASE WHEN %s THEN COALESCE(SUM(aml.credit), 0) ELSE CASE WHEN COALESCE(SUM(aml.balance), 0) >= 0 THEN 0 ELSE COALESCE(-SUM(aml.balance), 0) END END AS credit,
            COALESCE(SUM(aml.balance), 0) AS balance,
            COALESCE(SUM(aml.balance), 0) AS cumul_balance,
            %s AS company_currency_id,
            FALSE as reconciled,
            MIN(ro.id) AS report_object_id
        FROM
            account_report_standard_ledger_report_object ro
            INNER JOIN account_move_line aml ON (CASE WHEN %s = 'account' THEN aml.account_id = ro.object_id ELSE aml.partner_id = ro.object_id END)
            LEFT JOIN account_account acc ON (aml.account_id = acc.id)
            LEFT JOIN account_account_type acc_type ON (acc.user_type_id = acc_type.id)
            LEFT JOIN account_move m ON (aml.move_id = m.id)
            LEFT JOIN matching_in_futur_before_init mif ON (aml.full_reconcile_id = mif.id)
       	WHERE
            m.state IN %s
            AND ro.report_id = %s
            AND aml.company_id = %s
            AND aml.date < %s
            AND acc_type.include_initial_balance = TRUE
            AND aml.journal_id IN %s
            AND aml.account_id IN %s
            AND (%s IN ('account', 'journal') OR aml.partner_id IN %s)
            AND ((%s AND acc.compacted = TRUE) OR acc.type_third_parties = 'no' OR (aml.full_reconcile_id IS NOT NULL AND mif.id IS NULL))
        GROUP BY
            group_by_key
        HAVING
            CASE
                WHEN %s = FALSE THEN ABS(SUM(aml.balance)) > %s
                ELSE ABS(SUM(aml.debit)) > %s OR ABS(SUM(aml.debit)) > %s OR ABS(SUM(aml.balance)) > %s
            END
        """

        params = [
            # matching_in_futur
            company.id,
            self.report_id.date_from,

            # init_account_table
            # SELECT
            self.report_id.id,
            self.env.uid,
            self.type, self.type,
            self.report_id.date_from,
            self.init_balance_history,
            self.init_balance_history,
            self.company_currency_id.id,
            # FROM
            self.type,
            # WHERE
            ('posted',) if self.target_move == 'posted' else ('posted', 'draft',),
            self.report_id.id,
            company.id,
            self.report_id.date_from,
            tuple(self.journal_ids.ids) if self.journal_ids else (None,),
            tuple(self.account_ids.ids) if self.account_ids else (None,),
            self.type,
            tuple(self.partner_ids.ids) if self.partner_ids else (None,),
            self.compact_account,

            # HAVING
            self.init_balance_history,
            self.company_currency_id.rounding, self.company_currency_id.rounding, self.company_currency_id.rounding, self.company_currency_id.rounding,
        ]

        self.env.cr.execute(query, tuple(params))

    def _sql_lines(self):
        # lines_table
        query = """
        INSERT INTO account_report_standard_ledger_line
            (report_id, create_uid, create_date, account_id, analytic_account_id, type, type_view, journal_id, partner_id, move_id, move_line_id, date, date_maturity, debit, credit, balance, full_reconcile_id, reconciled, report_object_id, cumul_balance, current, age_30_days, age_60_days, age_90_days, age_120_days, older, company_currency_id, amount_currency, currency_id)

        WITH matching_in_futur_before_init (id) AS
        (
            SELECT DISTINCT
                afr.id AS id
            FROM
                account_full_reconcile afr
            INNER JOIN account_move_line aml ON aml.full_reconcile_id=afr.id
            WHERE
                aml.company_id = %s
                AND aml.date >= %s
        ),

        matching_in_futur_after_date_to (id) AS
        (
            SELECT DISTINCT
                afr.id AS id
            FROM
                account_full_reconcile afr
                INNER JOIN account_move_line aml ON aml.full_reconcile_id = afr.id
            WHERE
                aml.company_id = %s
                AND aml.date > %s
        ),

        initial_balance (id, balance) AS
        (
            SELECT
                MIN(report_object_id) AS id,
                COALESCE(SUM(balance), 0) AS balance
            FROM
                account_report_standard_ledger_line
            WHERE
                report_id = %s
                AND type = '0_init'
            GROUP BY
                report_object_id
        ),

        date_range AS
            (
                SELECT
                    %s AS date_current,
                    DATE %s - INTEGER '30' AS date_less_30_days,
                    DATE %s - INTEGER '60' AS date_less_60_days,
                    DATE %s - INTEGER '90' AS date_less_90_days,
                    DATE %s - INTEGER '120' AS date_less_120_days,
                    DATE %s - INTEGER '150' AS date_older
            )

        SELECT
            %s AS report_id,
            %s AS create_uid,
            NOW() AS create_date,
            aml.account_id,
            aml.analytic_account_id,
            CASE WHEN aml.date >= %s THEN '2_line' ELSE '1_init_line' END AS type,
            CASE WHEN aml.date >= %s THEN 'normal' ELSE 'init' END AS type_view,
            aml.journal_id,
            aml.partner_id,
            aml.move_id,
            aml.id,
            aml.date,
            aml.date_maturity,
            aml.debit,
            aml.credit,
            aml.balance,
            aml.full_reconcile_id,
            CASE WHEN aml.full_reconcile_id is NOT NULL AND NOT mifad.id IS NOT NULL THEN TRUE ELSE FALSE END AS reconciled,
            ro.id AS report_object_id,
            CASE
                WHEN %s = 'account' THEN COALESCE(init.balance, 0) + (SUM(aml.balance) OVER (PARTITION BY aml.account_id ORDER BY aml.account_id, aml.date, aml.id))
                WHEN %s = 'partner' THEN COALESCE(init.balance, 0) + (SUM(aml.balance) OVER (PARTITION BY aml.partner_id ORDER BY aml.partner_id, aml.date, aml.id))
                ELSE SUM(aml.balance) OVER (PARTITION BY aml.journal_id ORDER BY aml.journal_id, aml.date, aml.id)
            END AS cumul_balance,
            CASE WHEN aml.date_maturity > date_range.date_less_30_days THEN aml.balance END AS current,
            CASE WHEN aml.date_maturity > date_range.date_less_60_days AND aml.date_maturity <= date_range.date_less_30_days THEN aml.balance END AS age_30_days,
            CASE WHEN aml.date_maturity > date_range.date_less_90_days AND aml.date_maturity <= date_range.date_less_60_days THEN aml.balance END AS age_60_days,
            CASE WHEN aml.date_maturity > date_range.date_less_120_days AND aml.date_maturity <= date_range.date_less_90_days THEN aml.balance END AS age_90_days,
            CASE WHEN aml.date_maturity > date_range.date_older AND aml.date_maturity <= date_range.date_less_120_days THEN aml.balance END AS age_120_days,
            CASE WHEN aml.date_maturity <= date_range.date_older THEN aml.balance END AS older,
            %s AS company_currency_id,
            aml.amount_currency AS amount_currency,
            aml.currency_id AS currency_id
        FROM
            date_range,
            account_report_standard_ledger_report_object ro
            INNER JOIN account_move_line aml ON (
                CASE
                    WHEN %s = 'account' THEN aml.account_id = ro.object_id
                    WHEN %s = 'partner' THEN aml.partner_id = ro.object_id
                    WHEN %s = 'analytic' THEN aml.analytic_account_id = ro.object_id
                    ELSE aml.journal_id = ro.object_id
                END)
            LEFT JOIN account_journal j ON (aml.journal_id = j.id)
            LEFT JOIN account_account acc ON (aml.account_id = acc.id)
            LEFT JOIN account_account_type acc_type ON (acc.user_type_id = acc_type.id)
            LEFT JOIN account_move m ON (aml.move_id = m.id)
            LEFT JOIN matching_in_futur_before_init mif ON (aml.full_reconcile_id = mif.id)
            LEFT JOIN matching_in_futur_after_date_to mifad ON (aml.full_reconcile_id = mifad.id)
            LEFT JOIN initial_balance init ON (ro.id = init.id)
        WHERE
            m.state IN %s
            AND ro.report_id = %s
            AND aml.company_id = %s
            AND (CASE
                    WHEN %s = 'journal' THEN aml.date >= %s
                    WHEN aml.date >= %s THEN %s != 'open'
                    ELSE acc.type_third_parties IN ('supplier', 'customer') AND (aml.full_reconcile_id IS NULL OR mif.id IS NOT NULL)
                END)
            AND aml.date <= %s
            AND aml.journal_id IN %s
            AND aml.account_id IN %s
            AND (%s IN ('account','journal','analytic') OR aml.partner_id IN %s)
            AND (%s != 'analytic' OR aml.analytic_account_id IN %s)
            AND NOT (%s AND acc.compacted = TRUE)
            AND (%s OR NOT (aml.full_reconcile_id is NOT NULL AND NOT mifad.id IS NOT NULL))
        ORDER BY
            aml.date, aml.id
        """
        params = [
            # matching_in_futur init
            self.company_id.id,

            self.report_id.date_from,

            # matching_in_futur date_to
            self.company_id.id,
            self.report_id.date_to,

            # initial_balance
            self.report_id.id,

            # date_range
            self.report_id.date_to, self.report_id.date_to, self.report_id.date_to, self.report_id.date_to, self.report_id.date_to, self.report_id.date_to,

            # lines_table
            # SELECT
            self.report_id.id,
            self.env.uid,
            self.report_id.date_from,
            self.report_id.date_from,
            self.type, self.type,
            self.company_currency_id.id,

            # FROM
            self.type, self.type, self.type,

            # WHERE
            ('posted',) if self.target_move == 'posted' else ('posted', 'draft',),
            self.report_id.id,
            self.company_id.id,

            self.type, self.report_id.date_from,
            self.report_id.date_from, self.type_ledger,
            self.report_id.date_to,
            tuple(self.journal_ids.ids) if self.journal_ids else (None,),
            tuple(self.account_ids.ids) if self.account_ids else (None,),
            self.type,
            tuple(self.partner_ids.ids) if self.partner_ids else (None,),
            self.type,
            tuple(self.analytic_account_ids.ids) if self.analytic_account_ids else (None,),
            self.compact_account,
            self.reconciled,

        ]

        self.env.cr.execute(query, tuple(params))

    def _sql_lines_compacted(self):
        query = """
        INSERT INTO account_report_standard_ledger_line
            (report_id, create_uid, create_date, account_id, type, type_view, date, debit, credit, balance, cumul_balance, company_currency_id, report_object_id)

        WITH initial_balance (id, balance) AS
        (
        SELECT
            MIN(report_object_id) AS id,
            COALESCE(SUM(balance), 0) AS balance
        FROM
            account_report_standard_ledger_line
        WHERE
            report_id = %s
            AND type = '0_init'
        GROUP BY
            report_object_id
        )

        SELECT
            %s AS report_id,
            %s AS create_uid,
            NOW() AS create_date,
            MIN(aml.account_id) AS account_id,
            '3_compact' AS type,
            'normal' AS type_view,
            %s AS date,
            COALESCE(SUM(aml.debit), 0) AS debit,
            COALESCE(SUM(aml.credit), 0) AS credit,
            COALESCE(SUM(aml.balance), 0) AS balance,
            COALESCE(MIN(init.balance), 0) + COALESCE(SUM(aml.balance), 0) AS cumul_balance,
            %s AS company_currency_id,
            MIN(ro.id) AS report_object_id
        FROM
            account_report_standard_ledger_report_object ro
            INNER JOIN account_move_line aml ON (aml.account_id = ro.object_id)
            LEFT JOIN account_journal j ON (aml.journal_id = j.id)
            LEFT JOIN account_account acc ON (aml.account_id = acc.id)
            LEFT JOIN account_account_type acc_type ON (acc.user_type_id = acc_type.id)
            LEFT JOIN account_move m ON (aml.move_id = m.id)
            LEFT JOIN initial_balance init ON (ro.id = init.id)
        WHERE
            m.state IN %s
            AND ro.report_id = %s
            AND aml.company_id = %s
            AND aml.date >= %s
            AND aml.date <= %s
            AND aml.journal_id IN %s
            AND aml.account_id IN %s
            AND (%s AND acc.compacted = TRUE)
        GROUP BY
            aml.account_id
        """

        params = [
            # initial_balance
            self.report_id.id,

            # SELECT
            self.report_id.id,
            self.env.uid,
            self.report_id.date_from,
            self.company_currency_id.id,
            # FROM

            # WHERE
            ('posted',) if self.target_move == 'posted' else ('posted', 'draft',),
            self.report_id.id,
            self.company_id.id,
            self.report_id.date_from,
            self.report_id.date_to,
            tuple(self.journal_ids.ids) if self.journal_ids else (None,),
            tuple(self.account_ids.ids) if self.account_ids else (None,),
            self.compact_account,
        ]

        self.env.cr.execute(query, tuple(params))

    def _sql_total(self):
        query = """
        INSERT INTO account_report_standard_ledger_line
            (report_id, create_uid, create_date, account_id, partner_id, journal_id, analytic_account_id, type, type_view, date, debit, credit, balance, cumul_balance, report_object_id, current, age_30_days, age_60_days, age_90_days, age_120_days, older, company_currency_id)
        SELECT
            %s AS report_id,
            %s AS create_uid,
            NOW() AS create_date,
            CASE WHEN %s = 'account' THEN MIN(account_id) ELSE NULL END AS account_id,
            CASE WHEN %s = 'partner' THEN MIN(partner_id) ELSE NULL END AS partner_id,
            CASE WHEN %s = 'journal' THEN MIN(journal_id) ELSE NULL END AS journal_id,
            CASE WHEN %s = 'analytic' THEN MIN(analytic_account_id) ELSE NULL END AS analytic_account_id,
            '4_total' AS type,
            'total' AS type_view,
            %s AS date,
            COALESCE(SUM(debit), 0) AS debit,
            COALESCE(SUM(credit), 0) AS credit,
            COALESCE(SUM(balance), 0) AS balance,
            COALESCE(SUM(balance), 0) AS cumul_balance,
            MIN(report_object_id) AS report_object_id,
            COALESCE(SUM(current), 0) AS current,
            COALESCE(SUM(age_30_days), 0) AS age_30_days,
            COALESCE(SUM(age_60_days), 0) AS age_60_days,
            COALESCE(SUM(age_90_days), 0) AS age_90_days,
            COALESCE(SUM(age_120_days), 0) AS age_120_days,
            COALESCE(SUM(older), 0) AS older,
            %s AS company_currency_id
        FROM
            account_report_standard_ledger_line
        WHERE
            report_id = %s
            AND report_object_id IS NOT NULL
        GROUP BY
            report_object_id
        ORDER BY
            report_object_id
        """
        params = [
            # SELECT
            self.report_id.id,
            self.env.uid,
            self.type, self.type, self.type, self.type,
            self.report_id.date_from,
            self.company_currency_id.id,

            # WHERE
            self.report_id.id,

        ]
        self.env.cr.execute(query, tuple(params))

    def _sql_super_total(self):
        query = """
        INSERT INTO account_report_standard_ledger_line
            (report_id, create_uid, create_date, type, type_view, date, debit, credit, balance, cumul_balance, current, age_30_days, age_60_days, age_90_days, age_120_days, older, company_currency_id)
        SELECT
            %s AS report_id,
            %s AS create_uid,
            NOW() AS create_date,
            '5_super_total' AS type,
            'total' AS type_view,
            %s AS date,
            COALESCE(SUM(debit), 0) AS debit,
            COALESCE(SUM(credit), 0) AS credit,
            COALESCE(SUM(balance), 0) AS balance,
            COALESCE(SUM(balance), 0) AS cumul_balance,
            COALESCE(SUM(current), 0) AS current,
            COALESCE(SUM(age_30_days), 0) AS age_30_days,
            COALESCE(SUM(age_60_days), 0) AS age_60_days,
            COALESCE(SUM(age_90_days), 0) AS age_90_days,
            COALESCE(SUM(age_120_days), 0) AS age_120_days,
            COALESCE(SUM(older), 0) AS older,
            %s AS company_currency_id
        FROM
            account_report_standard_ledger_line
        WHERE
            report_id = %s
            AND type = '4_total'
        """
        params = [
            # SELECT
            self.report_id.id,
            self.env.uid,
            self.report_id.date_from,
            self.company_currency_id.id,
            self.report_id.id,
        ]
        self.env.cr.execute(query, tuple(params))

    def _search_account(self):
        type_ledger = self.type_ledger
        domain = [('deprecated', '=', False), ('company_id', '=', self.company_id.id)]
        if type_ledger in ('partner', 'aged',):
            result_selection = self.result_selection
            if result_selection == 'supplier':
                acc_type = ('supplier',)
            elif result_selection == 'customer':
                acc_type = ('customer',)
            else:
                acc_type = ('supplier', 'customer',)
            domain.append(('type_third_parties', 'in', acc_type))

        account_in_ex_clude = self.account_in_ex_clude.ids
        acc_methode = self.account_methode
        if account_in_ex_clude:
            if acc_methode == 'include':
                domain.append(('id', 'in', account_in_ex_clude))
            elif acc_methode == 'exclude':
                domain.append(('id', 'not in', account_in_ex_clude))
        return self.env['account.account'].search(domain)

    def _search_analytic_account(self):
        if self.type_ledger == 'analytic':
            if self.analytic_account_select_ids:
                return self.analytic_account_select_ids
            else:
                return self.env['account.analytic.account'].search([])
        return False

    def _search_partner(self):
        if self.type_ledger in ('partner', 'aged'):
            if self.partner_select_ids:
                return self.partner_select_ids
            return self.env['res.partner'].search([])
        return False

    def _get_name_report(self):
        report_name = D_LEDGER[self.type_ledger]['name']
        if self.summary:
            report_name += _(' Balance')
        return report_name

    def _sql_get_line_for_report(self, type_l, report_object=None):
        self.env['account.move.line'].check_access_rights('read')
        query = """SELECT
                    raml.report_object_id AS report_object_id,
                    raml.type_view AS type_view,
                    CASE
                        WHEN %s = 'account' THEN acc.code
                        WHEN %s = 'journal' THEN acj.code
                        WHEN %s = 'analytic' THEN an_acc.code
                        ELSE rep.ref
                    END AS code,
                    CASE
                        WHEN %s = 'account' THEN acc.name
                        WHEN %s = 'journal' THEN acj.name
                        WHEN %s = 'analytic' THEN an_acc.name
                        ELSE rep.name
                    END AS name,
                    acj.code AS j_code,
                    acc.code AS a_code,
                    acc.name AS a_name,
                    an_acc.code AS an_code,
                    an_acc.name AS an_name,
                    raml.current AS current,
                    raml.age_30_days AS age_30_days,
                    raml.age_60_days AS age_60_days,
                    raml.age_90_days AS age_90_days,
                    raml.age_120_days AS age_120_days,
                    raml.older AS older,
                    raml.credit AS credit,
                    raml.debit AS debit,
                    raml.cumul_balance AS cumul_balance,
                    raml.balance AS balance,
                    aml.name AS displayed_name,
                    ml.name AS move_name,
                    aml.ref AS displayed_ref,
                    rep.name AS partner_name,
                    raml.date AS date,
                    raml.date_maturity AS date_maturity,
                    raml.amount_currency AS amount_currency,
                    cr.excel_format AS currency,
                    CASE
                        WHEN raml.full_reconcile_id IS NOT NULL THEN (CASE WHEN raml.reconciled = TRUE THEN afr.name ELSE '*' END)
                        ELSE ''
                    END AS matching_number
                FROM
                    account_report_standard_ledger_line raml
                    LEFT JOIN account_account acc ON (acc.id = raml.account_id)
                    LEFT JOIN account_journal acj ON (acj.id = raml.journal_id)
                    LEFT JOIN res_partner rep ON (rep.id = raml.partner_id)
                    LEFT JOIN account_move ml ON (ml.id = raml.move_id)
                    LEFT JOIN account_move_line aml ON (aml.id = raml.move_line_id)
                    LEFT JOIN account_full_reconcile afr ON (raml.full_reconcile_id = afr.id)
                    LEFT JOIN account_analytic_account an_acc ON (raml.analytic_account_id = an_acc.id)
                    LEFT JOIN res_currency cr ON (raml.currency_id = cr.id)
                WHERE
                    raml.report_id = %s
                    AND (%s OR raml.report_object_id = %s)
                    AND raml.type IN %s
                ORDER BY
                    raml.id
                """
        params = [
            self.type, self.type, self.type, self.type, self.type, self.type,
            self.report_id.id,
            True if report_object is None else False,
            report_object,
            type_l
        ]

        self.env.cr.execute(query, tuple(params))
        return self.env.cr.dictfetchall()

    def _format_total(self):
        if not self.company_currency_id:
            return
        lines = self.report_id.line_total_ids + self.report_id.line_super_total_id
        for line in lines:
            line.write({
                'debit': self.company_currency_id.round(line.debit) + 0.0,
                'credit': self.company_currency_id.round(line.credit) + 0.0,
                'balance': self.company_currency_id.round(line.balance) + 0.0,
                'current': self.company_currency_id.round(line.current) + 0.0,
                'age_30_days': self.company_currency_id.round(line.age_30_days) + 0.0,
                'age_60_days': self.company_currency_id.round(line.age_60_days) + 0.0,
                'age_90_days': self.company_currency_id.round(line.age_90_days) + 0.0,
                'age_120_days': self.company_currency_id.round(line.age_120_days) + 0.0,
                'older': self.company_currency_id.round(line.older) + 0.0,
            })
Пример #18
0
class PurchaseOrder(models.Model):
    _inherit = "purchase.order"

    manual_currency_rate_active = fields.Boolean('Apply Manual Exchange')
    manual_currency_rate = fields.Float('Manual Rate', digits=(12, 6))
Пример #19
0
class product_images(models.Model):

    "Products Image gallery"
    _name = "product.images"
    _description = __doc__
    _table = "product_images"

    @api.model
    def unlink(self):
        local_media_repository = self.env[
            'res.company'].get_local_media_repository()
        if local_media_repository:
            for image in self:
                path = os.path.join(local_media_repository,
                                    image.product_id.default_code, image.name)
                if os.path.isfile(path):
                    os.remove(path)
        return super(product_images, self).unlink()

    @api.model
    def create(self, vals):
        if vals.get('name', False) and not vals.get('extention', False):
            vals['name'], vals['extention'] = os.path.splitext(vals['name'])
        return super(product_images, self).create(vals)

    @api.multi
    def write(self, vals):
        if vals.get('name', False) and not vals.get('extention', False):
            vals['name'], vals['extention'] = os.path.splitext(vals['name'])
        if vals.get('name', False) or vals.get('extention', False):
            local_media_repository = self.env[
                'res.company'].get_local_media_repository()
            if local_media_repository:
                #                 old_images = self.browse(cr, uid, ids, context=context)
                res = []
                for old_image in self:
                    if vals.get('name', False) and (
                            old_image.name != vals['name']) or vals.get(
                                'extention', False) and (old_image.extention !=
                                                         vals['extention']):
                        old_path = os.path.join(
                            local_media_repository,
                            old_image.product_id.default_code,
                            '%s%s' % (old_image.name, old_image.extention))
                        res.append(
                            super(product_images,
                                  self).write(old_image.id, vals))
                        if 'file' in vals:
                            #a new image have been loaded we should remove the old image
                            #TODO it's look like there is something wrong with function field in openerp indeed the preview is always added in the write :(
                            if os.path.isfile(old_path):
                                os.remove(old_path)
                        else:
                            #we have to rename the image on the file system
                            if os.path.isfile(old_path):
                                os.rename(
                                    old_path,
                                    os.path.join(
                                        local_media_repository,
                                        old_image.product_id.default_code,
                                        '%s%s' %
                                        (old_image.name, old_image.extention)))
                return res
        return super(product_images, self).write(vals)

    @api.multi
    def get_image(self):
        product_product_obj = self.env['product.product']
        product_template_obj = self.env['product.template']
        for rec in self:
            each = rec.read([
                'link', 'url', 'name', 'file_db_store', 'product_id',
                'product_t_id', 'name', 'extention'
            ])[0]
            if each['link']:
                (filename, header) = urllib.request.urlretrieve(each['url'])
                f = open(filename, 'rb')
                img = base64.encodestring(f.read())
                f.close()
                rec.file = img
            else:
                local_media_repository = self.env[
                    'res.company'].get_local_media_repository()
                if local_media_repository:
                    if each['product_t_id']:
                        product_id = product_template_obj.browse(
                            each['product_t_id'][0])
                        product_code = product_id.read(['default_code'
                                                        ])[0]['default_code']
                    else:
                        product_id = product_product_obj.browse(
                            each['product_id'][0])
                        product_code = product_id.read(['default_code'
                                                        ])[0]['default_code']
                    full_path = os.path.join(
                        local_media_repository, product_code,
                        '%s%s' % (each['name'], each['extention']))
                    if os.path.exists(full_path):
                        try:
                            f = open(full_path, 'rb')
                            img = base64.encodestring(f.read())
                            f.close()
                        except Exception as e:
                            return False
                    else:
                        return False
                else:
                    img = each['file_db_store']
                    rec.file = img

    @api.multi
    def _get_image(self):
        res = {}
        for each in self:
            res[each] = self.get_image()
        return res

    @api.multi
    def _check_filestore(self, image_filestore):
        '''check if the filestore is created, if not it create it automatically'''
        #         try:
        if not os.path.isdir(image_filestore):
            os.makedirs(image_filestore)
#         except Exception e:
#             raise osv.except_osv(_('Error'), _('The image filestore can not be created, %s'%e))
        return True

    @api.multi
    def _save_file(self, path, filename, b64_file):
        """Save a file encoded in base 64"""
        full_path = os.path.join(path, filename)
        self._check_filestore(path)
        ofile = open(full_path, 'w')
        try:
            ofile.write(base64.decodestring(b64_file))
        finally:
            ofile.close()
        return True

    @api.multi
    def _set_image(self, name, value, arg):
        local_media_repository = self.env[
            'res.company'].get_local_media_repository()
        if local_media_repository:
            #             image = self.browse(cr, uid, id, context=context)
            return self._save_file(
                os.path.join(local_media_repository,
                             self.product_id.default_code),
                '%s%s' % (self.name, self.extention), value)
        return self.write({'file_db_store': value})

    name = fields.Char('Image Title', size=100, required=True)
    extention = fields.Char('file extention', size=6)
    link = fields.Boolean(
        'Link?',
        default=lambda *a: False,
        help=
        "Images can be linked from files on your file system or remote (Preferred)"
    )
    file_db_store = fields.Binary('Image stored in database')
    file = fields.Char(compute=_get_image,
                       inverse=_set_image,
                       type="binary",
                       method=True,
                       filters='*.png,*.jpg,*.gif')
    url = fields.Char('File Location', size=250)
    comments = fields.Text('Comments')
    product_id = fields.Many2one('product.product', 'Product')

    _sql_constraints = [
        ('uniq_name_product_id', 'UNIQUE(product_id, name)',
         _('A product can have only one image with the same name'))
    ]
Пример #20
0
class CooperativeStatus(models.Model):
    _name = "cooperative.status"
    _description = "cooperative.status"
    _rec_name = "cooperator_id"
    _order = "cooperator_id"
    _period = 28

    def _get_status(self):
        return [
            ("ok", _("Up to Date")),
            ("holiday", _("Holidays")),
            ("alert", _("Warning")),
            ("extension", _("Extension")),
            ("suspended", _("Suspended")),
            ("exempted", _("Exempted")),
            ("unsubscribed", _("Unsubscribed")),
            ("resigning", _("Resigning")),
        ]

    today = fields.Date(
        help="Field that allow to compute field and store them even if they "
        "are based on the current date",
        default=fields.Date.today,
    )
    cooperator_id = fields.Many2one("res.partner")
    active = fields.Boolean(related="cooperator_id.active",
                            store=True,
                            index=True)
    info_session = fields.Boolean("Information Session ?")
    info_session_date = fields.Date("Information Session Date")
    super = fields.Boolean("Super Cooperative")
    sr = fields.Integer("Regular shifts counter", default=0)
    sc = fields.Integer("Compensation shifts counter", default=0)
    time_extension = fields.Integer(
        "Extension Days NB",
        default=0,
        help="Addtional days to the automatic extension, 5 mean that you have "
        "a total of 15 extension days of default one is set to 10",
    )
    holiday_start_time = fields.Date("Holidays Start Day")
    holiday_end_time = fields.Date("Holidays End Day")
    alert_start_time = fields.Date("Alert Start Day")
    extension_start_time = fields.Date("Extension Start Day")
    working_mode = fields.Selection(
        [
            ("regular", "Regular worker"),
            ("irregular", "Irregular worker"),
            ("exempt", "Exempted"),
        ],
        string="Working mode",
    )
    exempt_reason_id = fields.Many2one(
        comodel_name="cooperative.exempt.reason", string="Exempt Reason")
    status = fields.Selection(
        selection=_get_status,
        compute="_compute_status",
        string="Cooperative Status",
        translate=True,
        store=True,
    )
    can_shop = fields.Boolean(compute="_compute_can_shop", store=True)
    history_ids = fields.One2many("cooperative.status.history",
                                  "status_id",
                                  readonly=True)
    unsubscribed = fields.Boolean(default=False, help="Manually unsubscribed")
    resigning = fields.Boolean(default=False,
                               help="Want to leave the beescoop")

    # Specific to irregular
    irregular_start_date = fields.Date()  # TODO migration script
    irregular_absence_date = fields.Date()
    irregular_absence_counter = (fields.Integer()
                                 )  # TODO unsubscribe when reach -2
    future_alert_date = fields.Date(compute="_compute_future_alert_date")
    next_countdown_date = fields.Date(compute="_compute_next_countdown_date")

    temporary_exempt_reason_id = fields.Many2one(
        comodel_name="cooperative.exempt.reason",
        string="Temporary Exempt Reason",
    )
    temporary_exempt_start_date = fields.Date()
    temporary_exempt_end_date = fields.Date()

    @api.depends("status")
    def _compute_can_shop(self):
        for rec in self:
            rec.can_shop = rec.status in self._can_shop_status()

    @api.depends(
        "today",
        "sr",
        "sc",
        "holiday_end_time",
        "holiday_start_time",
        "time_extension",
        "alert_start_time",
        "extension_start_time",
        "unsubscribed",
        "irregular_absence_date",
        "irregular_absence_counter",
        "temporary_exempt_start_date",
        "temporary_exempt_end_date",
        "resigning",
        "cooperator_id.subscribed_shift_ids",
        "working_mode",
    )
    def _compute_status(self):
        update = int(self.env["ir.config_parameter"].sudo().get_param(
            "always_update", False))
        for rec in self:
            if update or not rec.today:
                rec.status = "ok"
                continue
            if rec.resigning:
                rec.status = "resigning"
                continue

            if rec.working_mode == "regular":
                rec.status = rec._get_regular_status()
            elif rec.working_mode == "irregular":
                rec.status = rec._get_irregular_status()
            elif rec.working_mode == "exempt":
                rec.status = "ok"

    _sql_constraints = [(
        "cooperator_uniq",
        "unique (cooperator_id)",
        _("You can only set one cooperator status per cooperator"),
    )]

    @api.constrains("working_mode", "irregular_start_date")
    def _constrains_irregular_start_date(self):
        if self.working_mode == "irregular" and not self.irregular_start_date:
            raise UserError(
                _("Irregular workers must have an irregular start date."))

    @api.multi
    def write(self, vals):
        """
            Overwrite write to historize the change
        """
        for field in [
                "sr",
                "sc",
                "time_extension",
                "extension_start_time",
                "alert_start_time",
                "unsubscribed",
        ]:
            if field not in vals:
                continue
            for rec in self:
                data = {
                    "status_id": rec.id,
                    "cooperator_id": rec.cooperator_id.id,
                    "type": "counter",
                    "user_id": self.env.context.get("real_uid", self.env.uid),
                }
                if vals.get(field, rec[field]) != rec[field]:
                    data["change"] = "{}: {} -> {}".format(
                        field.upper(), rec[field], vals.get(field))
                    self.env["cooperative.status.history"].sudo().create(data)
        return super(CooperativeStatus, self).write(vals)

    @api.multi
    def _write(self, vals):
        """
            Overwrite write to historize the change of status
            and make action on status change
        """
        if "status" in vals:
            self._cr.execute(
                'select id, status, sr, sc from "%s" where id in %%s' %
                self._table,
                (self._ids, ),
            )
            result = self._cr.dictfetchall()
            old_status_per_id = {r["id"]: r for r in result}
            for rec in self:
                if old_status_per_id[rec.id]["status"] != vals["status"]:
                    data = {
                        "status_id":
                        rec.id,
                        "cooperator_id":
                        rec.cooperator_id.id,
                        "type":
                        "status",
                        "change":
                        "STATUS: %s -> %s" % (
                            old_status_per_id[rec.id]["status"],
                            vals["status"],
                        ),
                        "user_id":
                        self.env.context.get("real_uid", self.env.uid),
                    }
                    self.env["cooperative.status.history"].sudo().create(data)
                    rec._state_change(vals["status"])
        return super(CooperativeStatus, self)._write(vals)

    def get_status_value(self):
        """
        Workaround to get translated selection value instead of key in mail
        template.
        """
        state_list = (self.env["cooperative.status"]._fields["status"].
                      _description_selection(self.env))
        return dict(state_list)[self.status]

    @api.model
    def _set_today(self):
        """
            Method call by the cron to update store value base on the date
        """
        self.search([]).write({"today": fields.Date.today()})

    @api.model
    def _cron_compute_counter_irregular(self, today=False):
        """
            Journal ensure that a irregular worker will be only check
            once per day
        """
        today = today or fields.Date.today()
        journal = self.env["beesdoo.shift.journal"].search([("date", "=",
                                                             today)])
        if not journal:
            journal = self.env["beesdoo.shift.journal"].create({"date": today})

        domain = self._get_irregular_worker_domain(today=today)
        irregular = self.search(domain)
        for status in irregular:
            delta = (today - status.irregular_start_date).days
            if (delta and delta % self._period == 0
                    and status not in journal.line_ids):
                status._change_irregular_counter()
                journal.line_ids |= status

    @api.multi
    def clear_history(self):
        self.ensure_one()
        self.history_ids.unlink()

    ########################################################
    #                   Method to override                 #
    #           To define the behavior of the status       #
    #                                                      #
    #       By default: everyone is always up to date      #
    ########################################################

    ##############################
    #   Computed field section   #
    ##############################
    @api.depends("today")
    def _compute_future_alert_date(self):
        """
            Compute date until the worker is up to date
            for irregular worker
        """
        for rec in self:
            rec.future_alert_date = False

    @api.depends("today")
    def _compute_next_countdown_date(self):
        """
        Compute the following countdown date. This date is the date when
        the worker will see his counter changed due to the cron. This
        date is like the birthday date of the worker that occurred each
        _period.
        """
        for rec in self:
            rec.next_countdown_date = False

    def _can_shop_status(self):
        """
            return the list of status that give access
            to active cooperator privilege
        """
        return ["ok", "alert", "extension", "exempted"]

    #####################################
    #   Status Change implementation    #
    #####################################

    def _get_regular_status(self):
        """
            Return the value of the status
            for the regular worker
        """
        return "ok"

    def _get_irregular_status(self):
        """
            Return the value of the status
            for the irregular worker
        """
        return "ok"

    def _state_change(self, new_state):
        """
            Hook to watch change in the state
        """
        pass

    def _change_counter(self, data):
        """
            Call when a shift state is changed
            use data generated by _get_counter_date_state_change
        """
        pass

    ###############################################
    #        Irregular Cron implementation        #
    ###############################################

    def _get_irregular_worker_domain(self):
        """
            return the domain the give the list
            of valid irregular worker that should
            get their counter changed by the cron
        """
        return [(0, "=", 1)]

    def _change_irregular_counter(self):
        """
            Define how the counter will change
            for the irregular worker
            where today - start_date is a multiple of the period
            by default 28 days
        """
        pass
Пример #21
0
class PosOrderReport(models.Model):
    _name = "report.pos.order"
    _description = "Point of Sale Orders Statistics"
    _auto = False
    _order = 'date desc'

    date = fields.Datetime(string='Date Order', readonly=True)
    order_id = fields.Many2one('pos.order', string='Order', readonly=True)
    partner_id = fields.Many2one('res.partner',
                                 string='Customer',
                                 readonly=True)
    product_id = fields.Many2one('product.product',
                                 string='Product',
                                 readonly=True)
    product_tmpl_id = fields.Many2one('product.template',
                                      string='Product Template',
                                      readonly=True)
    state = fields.Selection([('draft', 'New'), ('paid', 'Paid'),
                              ('done', 'Posted'), ('invoiced', 'Invoiced'),
                              ('cancel', 'Cancelled')],
                             string='Status')
    user_id = fields.Many2one('res.users', string='Salesperson', readonly=True)
    price_total = fields.Float(string='Total Price', readonly=True)
    price_sub_total = fields.Float(string='Subtotal w/o discount',
                                   readonly=True)
    total_discount = fields.Float(string='Total Discount', readonly=True)
    average_price = fields.Float(string='Average Price',
                                 readonly=True,
                                 group_operator="avg")
    location_id = fields.Many2one('stock.location',
                                  string='Location',
                                  readonly=True)
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 readonly=True)
    nbr_lines = fields.Integer(string='Sale Line Count',
                               readonly=True,
                               oldname='nbr')
    product_qty = fields.Integer(string='Product Quantity', readonly=True)
    journal_id = fields.Many2one('account.journal', string='Journal')
    delay_validation = fields.Integer(string='Delay Validation')
    product_categ_id = fields.Many2one('product.category',
                                       string='Product Category',
                                       readonly=True)
    invoiced = fields.Boolean(readonly=True)
    config_id = fields.Many2one('pos.config',
                                string='Point of Sale',
                                readonly=True)
    pos_categ_id = fields.Many2one('pos.category',
                                   string='PoS Category',
                                   readonly=True)
    stock_location_id = fields.Many2one('stock.location',
                                        string='Warehouse',
                                        readonly=True)
    pricelist_id = fields.Many2one('product.pricelist',
                                   string='Pricelist',
                                   readonly=True)
    session_id = fields.Many2one('pos.session',
                                 string='Session',
                                 readonly=True)

    def _select(self):
        return """
            SELECT
                MIN(l.id) AS id,
                COUNT(*) AS nbr_lines,
                s.date_order AS date,
                SUM(l.qty) AS product_qty,
                SUM(l.qty * l.price_unit) AS price_sub_total,
                SUM((l.qty * l.price_unit) * (100 - l.discount) / 100) AS price_total,
                SUM((l.qty * l.price_unit) * (l.discount / 100)) AS total_discount,
                (SUM(l.qty*l.price_unit)/SUM(l.qty * u.factor))::decimal AS average_price,
                SUM(cast(to_char(date_trunc('day',s.date_order) - date_trunc('day',s.create_date),'DD') AS INT)) AS delay_validation,
                s.id as order_id,
                s.partner_id AS partner_id,
                s.state AS state,
                s.user_id AS user_id,
                s.location_id AS location_id,
                s.company_id AS company_id,
                s.sale_journal AS journal_id,
                l.product_id AS product_id,
                pt.categ_id AS product_categ_id,
                p.product_tmpl_id,
                ps.config_id,
                pt.pos_categ_id,
                pc.stock_location_id,
                s.pricelist_id,
                s.session_id,
                s.invoice_id IS NOT NULL AS invoiced
        """

    def _from(self):
        return """
            FROM pos_order_line AS l
                LEFT JOIN pos_order s ON (s.id=l.order_id)
                LEFT JOIN product_product p ON (l.product_id=p.id)
                LEFT JOIN product_template pt ON (p.product_tmpl_id=pt.id)
                LEFT JOIN uom_uom u ON (u.id=pt.uom_id)
                LEFT JOIN pos_session ps ON (s.session_id=ps.id)
                LEFT JOIN pos_config pc ON (ps.config_id=pc.id)
        """

    def _group_by(self):
        return """
            GROUP BY
                s.id, s.date_order, s.partner_id,s.state, pt.categ_id,
                s.user_id, s.location_id, s.company_id, s.sale_journal,
                s.pricelist_id, s.invoice_id, s.create_date, s.session_id,
                l.product_id,
                pt.categ_id, pt.pos_categ_id,
                p.product_tmpl_id,
                ps.config_id,
                pc.stock_location_id
        """

    def _having(self):
        return """
            HAVING
                SUM(l.qty * u.factor) != 0
        """

    @api.model_cr
    def init(self):
        tools.drop_view_if_exists(self._cr, self._table)
        self._cr.execute("""
            CREATE OR REPLACE VIEW %s AS (
                %s
                %s
                %s
                %s
            )
        """ % (self._table, self._select(), self._from(), self._group_by(),
               self._having()))
Пример #22
0
class FetchmailServer(models.Model):
    _name = 'fetchmail.server'
    _inherit = 'fetchmail.server'

    l10n_it_is_pec = fields.Boolean(
        'PEC server',
        help=
        "If PEC Server, only mail from '*****@*****.**' will be processed."
    )
    l10n_it_last_uid = fields.Integer(string='Last message UID', default=1)

    @api.constrains('l10n_it_is_pec', 'server_type')
    def _check_pec(self):
        for record in self:
            if record.l10n_it_is_pec and record.server_type != 'imap':
                raise ValidationError(
                    _("PEC mail server must be of type IMAP."))

    def fetch_mail(self):
        """ WARNING: meant for cron usage only - will commit() after each email! """

        MailThread = self.env['mail.thread']
        for server in self.filtered(lambda s: s.l10n_it_is_pec):
            _logger.info('start checking for new emails on %s PEC server %s',
                         server.server_type, server.name)

            count, failed = 0, 0
            imap_server = None
            try:
                imap_server = server.connect()
                imap_server.select()

                result, data = imap_server.uid(
                    'search', None, '(FROM "@pec.fatturapa.it")',
                    '(UID %s:*)' % (server.l10n_it_last_uid))
                new_max_uid = server.l10n_it_last_uid
                for uid in data[0].split():
                    if int(uid) <= server.l10n_it_last_uid:
                        # We get always minimum 1 message.  If no new message, we receive the newest already managed.
                        continue

                    result, data = imap_server.uid('fetch', uid, '(RFC822)')

                    if not data[0]:
                        continue
                    message = data[0][1]

                    # To leave the mail in the state in which they were.
                    if "Seen" not in data[1].decode("utf-8"):
                        imap_server.uid('STORE', uid, '+FLAGS', '\\Seen')
                    else:
                        imap_server.uid('STORE', uid, '-FLAGS', '\\Seen')

                    # See details in message_process() in mail_thread.py
                    if isinstance(message, xmlrpclib.Binary):
                        message = bytes(message.data)
                    if isinstance(message, str):
                        message = message.encode('utf-8')
                    msg_txt = email.message_from_bytes(
                        message, policy=email.policy.SMTP)

                    try:
                        self._attachment_invoice(msg_txt)
                        new_max_uid = max(new_max_uid, int(uid))
                    except Exception:
                        _logger.info(
                            'Failed to process mail from %s server %s.',
                            server.server_type,
                            server.name,
                            exc_info=True)
                        failed += 1
                    self._cr.commit()
                    count += 1
                server.write({'l10n_it_last_uid': new_max_uid})
                _logger.info(
                    "Fetched %d email(s) on %s server %s; %d succeeded, %d failed.",
                    count, server.server_type, server.name, (count - failed),
                    failed)
            except Exception:
                _logger.info(
                    "General failure when trying to fetch mail from %s server %s.",
                    server.server_type,
                    server.name,
                    exc_info=True)
            finally:
                if imap_server:
                    imap_server.close()
                    imap_server.logout()
                server.write({'date': fields.Datetime.now()})
        return super(
            FetchmailServer,
            self.filtered(lambda s: not s.l10n_it_is_pec)).fetch_mail()

    def _attachment_invoice(self, msg_txt):
        parsed_values = self.env['mail.thread']._message_parse_extract_payload(
            msg_txt)
        body, attachments = parsed_values['body'], parsed_values['attachments']
        from_address = msg_txt.get('from')
        for attachment in attachments:
            split_attachment = attachment.fname.rpartition('.')
            if len(split_attachment) < 3:
                _logger.info('E-invoice filename not compliant: %s',
                             attachment.fname)
                continue
            attachment_name = split_attachment[0]
            attachment_ext = split_attachment[2]
            split_underscore = attachment_name.rsplit('_', 2)
            if len(split_underscore) < 2:
                _logger.info('E-invoice filename not compliant: %s',
                             attachment.fname)
                continue

            if attachment_ext != 'zip':
                if split_underscore[1] in [
                        'RC', 'NS', 'MC', 'MT', 'EC', 'SE', 'NE', 'DT'
                ]:
                    # we have a receipt
                    self._message_receipt_invoice(split_underscore[1],
                                                  attachment)
                elif re.search(
                        "([A-Z]{2}[A-Za-z0-9]{2,28}_[A-Za-z0-9]{0,5}.(xml.p7m|xml))",
                        attachment.fname):
                    # we have a new E-invoice
                    self._create_invoice_from_mail(attachment.content,
                                                   attachment.fname,
                                                   from_address)
            else:
                if split_underscore[1] == 'AT':
                    # Attestazione di avvenuta trasmissione della fattura con impossibilità di recapito
                    self._message_AT_invoice(attachment)
                else:
                    _logger.info('New E-invoice in zip file: %s',
                                 attachment.fname)
                    self._create_invoice_from_mail_with_zip(
                        attachment, from_address)

    def _create_invoice_from_mail(self, att_content, att_name, from_address):
        if self.env['account.move'].search(
            [('l10n_it_einvoice_name', '=', att_name)], limit=1):
            # invoice already exist
            _logger.info('E-invoice already exist: %s', att_name)
            return

        invoice_attachment = self.env['ir.attachment'].create({
            'name':
            att_name,
            'datas':
            base64.encodebytes(att_content),
            'type':
            'binary',
        })

        try:
            tree = etree.fromstring(att_content)
        except Exception:
            raise UserError(
                _('The xml file is badly formatted : {}').format(att_name))

        invoice = self.env.ref(
            'l10n_it_edi.edi_fatturaPA')._create_invoice_from_xml_tree(
                att_name, tree)
        invoice.l10n_it_send_state = "new"
        invoice.source_email = from_address
        self._cr.commit()

        _logger.info('New E-invoice: %s', att_name)

    def _create_invoice_from_mail_with_zip(self, attachment_zip, from_address):
        with zipfile.ZipFile(io.BytesIO(attachment_zip.content)) as z:
            for att_name in z.namelist():
                if self.env['account.move'].search(
                    [('l10n_it_einvoice_name', '=', att_name)], limit=1):
                    # invoice already exist
                    _logger.info(
                        'E-invoice in zip file (%s) already exist: %s',
                        attachment_zip.fname, att_name)
                    continue
                att_content = z.open(att_name).read()

                self._create_invoice_from_mail(att_content, att_name,
                                               from_address)

    def _message_AT_invoice(self, attachment_zip):
        with zipfile.ZipFile(io.BytesIO(attachment_zip.content)) as z:
            for attachment_name in z.namelist():
                split_name_attachment = attachment_name.rpartition('.')
                if len(split_name_attachment) < 3:
                    continue
                split_underscore = split_name_attachment[0].rsplit('_', 2)
                if len(split_underscore) < 2:
                    continue
                if split_underscore[1] == 'AT':
                    attachment = z.open(attachment_name).read()
                    _logger.info('New AT receipt for: %s', split_underscore[0])
                    try:
                        tree = etree.fromstring(attachment)
                    except:
                        _logger.info('Error in decoding new receipt file: %s',
                                     attachment_name)
                        return

                    elements = tree.xpath('//NomeFile')
                    if elements and elements[0].text:
                        filename = elements[0].text
                    else:
                        return

                    related_invoice = self.env['account.move'].search([
                        ('l10n_it_einvoice_name', '=', filename)
                    ])
                    if not related_invoice:
                        _logger.info(
                            'Error: invoice not found for receipt file: %s',
                            filename)
                        return

                    related_invoice.l10n_it_send_state = 'failed_delivery'
                    info = self._return_multi_line_xml(tree, [
                        '//IdentificativoSdI', '//DataOraRicezione',
                        '//MessageId', '//PecMessageId', '//Note'
                    ])
                    related_invoice.message_post(body=(_(
                        "ES certify that it has received the invoice and that the file \
                        could not be delivered to the addressee. <br/>%s") %
                                                       (info)))

    def _message_receipt_invoice(self, receipt_type, attachment):
        try:
            tree = etree.fromstring(attachment.content)
        except:
            _logger.info('Error in decoding new receipt file: %s',
                         attachment.fname)
            return {}

        elements = tree.xpath('//NomeFile')
        if elements and elements[0].text:
            filename = elements[0].text
        else:
            return {}

        if receipt_type == 'RC':
            # Delivery receipt
            # This is the receipt sent by the ES to the transmitting subject to communicate
            # delivery of the file to the addressee
            related_invoice = self.env['account.move'].search([
                ('l10n_it_einvoice_name', '=', filename),
                ('l10n_it_send_state', '=', 'sent')
            ])
            if not related_invoice:
                _logger.info('Error: invoice not found for receipt file: %s',
                             attachment.fname)
                return
            related_invoice.l10n_it_send_state = 'delivered'
            info = self._return_multi_line_xml(tree, [
                '//IdentificativoSdI', '//DataOraRicezione',
                '//DataOraConsegna', '//Note'
            ])
            related_invoice.message_post(
                body=(_("E-Invoice is delivery to the destinatory:<br/>%s") %
                      (info)))

        elif receipt_type == 'NS':
            # Rejection notice
            # This is the receipt sent by the ES to the transmitting subject if one or more of
            # the checks carried out by the ES on the file received do not have a successful result.
            related_invoice = self.env['account.move'].search([
                ('l10n_it_einvoice_name', '=', filename),
                ('l10n_it_send_state', '=', 'sent')
            ])
            if not related_invoice:
                _logger.info('Error: invoice not found for receipt file: %s',
                             attachment.fname)
                return
            related_invoice.l10n_it_send_state = 'invalid'
            error = self._return_error_xml(tree)
            related_invoice.message_post(
                body=(_("Errors in the E-Invoice :<br/>%s") % (error)))
            related_invoice.activity_schedule(
                'mail.mail_activity_data_todo',
                summary='Rejection notice',
                user_id=related_invoice.invoice_user_id.id
                if related_invoice.invoice_user_id else self.env.user.id)

        elif receipt_type == 'MC':
            # Failed delivery notice
            # This is the receipt sent by the ES to the transmitting subject if the file is not
            # delivered to the addressee.
            related_invoice = self.env['account.move'].search([
                ('l10n_it_einvoice_name', '=', filename),
                ('l10n_it_send_state', '=', 'sent')
            ])
            if not related_invoice:
                _logger.info('Error: invoice not found for receipt file: %s',
                             attachment.fname)
                return
            info = self._return_multi_line_xml(tree, [
                '//IdentificativoSdI', '//DataOraRicezione', '//Descrizione',
                '//MessageId', '//Note'
            ])
            related_invoice.message_post(body=(_(
                "The E-invoice is not delivered to the addressee. The Exchange System is\
                unable to deliver the file to the Public Administration. The Exchange System will\
                contact the PA to report the problem and request that they provide a solution. \
                During the following 15 days, the Exchange System will try to forward the FatturaPA\
                file to the Administration in question again. More information:<br/>%s"
            ) % (info)))

        elif receipt_type == 'NE':
            # Outcome notice
            # This is the receipt sent by the ES to the invoice sender to communicate the result
            # (acceptance or refusal of the invoice) of the checks carried out on the document by
            # the addressee.
            related_invoice = self.env['account.move'].search([
                ('l10n_it_einvoice_name', '=', filename),
                ('l10n_it_send_state', '=', 'delivered')
            ])
            if not related_invoice:
                _logger.info('Error: invoice not found for receipt file: %s',
                             attachment.fname)
                return
            elements = tree.xpath('//Esito')
            if elements and elements[0].text:
                if elements[0].text == 'EC01':
                    related_invoice.l10n_it_send_state = 'delivered_accepted'
                elif elements[0].text == 'EC02':
                    related_invoice.l10n_it_send_state = 'delivered_refused'

            info = self._return_multi_line_xml(tree, [
                '//Esito', '//Descrizione', '//IdentificativoSdI',
                '//DataOraRicezione', '//DataOraConsegna', '//Note'
            ])
            related_invoice.message_post(
                body=(_("Outcome notice: %s<br/>%s") %
                      (related_invoice.l10n_it_send_state, info)))
            if related_invoice.l10n_it_send_state == 'delivered_refused':
                related_invoice.activity_schedule(
                    'mail.mail_activity_todo',
                    user_id=related_invoice.invoice_user_id.id
                    if related_invoice.invoice_user_id else self.env.user.id,
                    summary='Outcome notice: Refused')

        # elif receipt_type == 'MT':
        # Metadata file
        # This is the file sent by the ES to the addressee together with the invoice file,
        # containing the main reference data of the file useful for processing, including
        # the IdentificativoSDI.
        # Useless for Odoo

        elif receipt_type == 'DT':
            # Deadline passed notice
            # This is the receipt sent by the ES to both the invoice sender and the invoice
            # addressee to communicate the expiry of the maximum term for communication of
            # acceptance/refusal.
            related_invoice = self.env['account.move'].search([
                ('l10n_it_einvoice_name', '=', filename),
                ('l10n_it_send_state', '=', 'delivered')
            ])
            if not related_invoice:
                _logger.info('Error: invoice not found for receipt file: %s',
                             attachment.fname)
                return
            related_invoice.l10n_it_send_state = 'delivered_expired'
            info = self._return_multi_line_xml(
                tree, ['//Descrizione', '//IdentificativoSdI', '//Note'])
            related_invoice.message_post(body=(_(
                "Expiration of the maximum term for communication of acceptance/refusal:\
                 %s<br/>%s") % (filename, info)))

    def _return_multi_line_xml(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:
                if element.text:
                    text = " ".join(element.text.split())
                    output_str += "<li>%s: %s</li>" % (element.tag, text)
        return output_str + "</ul>"

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

        elements = tree.xpath('//Errore')
        if not elements:
            return
        for element in elements:
            descrizione = " ".join(element[1].text.split())
            if descrizione:
                output_str += "<li>Errore %s: %s</li>" % (element[0].text,
                                                          descrizione)
        return output_str + "</ul>"
Пример #23
0
class ResConfigSettings(models.TransientModel):
    _inherit = 'res.config.settings'

    group_discount_per_so_line = fields.Boolean("Discounts", implied_group='product.group_discount_per_so_line')
    group_uom = fields.Boolean("Units of Measure", implied_group='uom.group_uom')
    group_product_variant = fields.Boolean("Variants", implied_group='product.group_product_variant')
    module_sale_product_configurator = fields.Boolean("Product Configurator")
    module_sale_product_matrix = fields.Boolean("Product Grid Entry")
    group_stock_packaging = fields.Boolean('Product Packagings',
        implied_group='product.group_stock_packaging')
    group_product_pricelist = fields.Boolean("Pricelists",
        implied_group='product.group_product_pricelist')
    group_sale_pricelist = fields.Boolean("Advanced Pricelists",
        implied_group='product.group_sale_pricelist',
        help="""Allows to manage different prices based on rules per category of customers.
                Example: 10% for retailers, promotion of 5 EUR on this product, etc.""")
    product_pricelist_setting = fields.Selection([
            ('basic', 'Multiple prices per product'),
            ('advanced', 'Advanced price rules (discounts, formulas)')
            ], default='basic', string="Pricelists Method", config_parameter='product.product_pricelist_setting',
            help="Multiple prices: Pricelists with fixed price rules by product,\nAdvanced rules: enables advanced price rules for pricelists.")
    product_weight_in_lbs = fields.Selection([
        ('0', 'Kilogram'),
        ('1', 'Pound'),
    ], 'Weight unit of measure', config_parameter='product.weight_in_lbs', default='0')
    product_volume_volume_in_cubic_feet = fields.Selection([
        ('0', 'Cubic Meters'),
        ('1', 'Cubic Feet'),
    ], 'Volume unit of measure', config_parameter='product.volume_in_cubic_feet', default='0')

    @api.onchange('group_product_variant')
    def _onchange_group_product_variant(self):
        """The product Configurator requires the product variants activated.
        If the user disables the product variants -> disable the product configurator as well"""
        if self.module_sale_product_configurator and not self.group_product_variant:
            self.module_sale_product_configurator = False
        if self.module_sale_product_matrix and not self.group_product_variant:
            self.module_sale_product_matrix = False

    @api.onchange('module_sale_product_configurator')
    def _onchange_module_sale_product_configurator(self):
        """The product Configurator requires the product variants activated
        If the user enables the product configurator -> enable the product variants as well"""
        if self.module_sale_product_configurator and not self.group_product_variant:
            self.group_product_variant = True

    @api.onchange('group_multi_currency')
    def _onchange_group_multi_currency(self):
        if self.group_product_pricelist and not self.group_sale_pricelist and self.group_multi_currency:
            self.group_sale_pricelist = True

    @api.onchange('group_product_pricelist')
    def _onchange_group_sale_pricelist(self):
        if not self.group_product_pricelist and self.group_sale_pricelist:
            self.group_sale_pricelist = False

    @api.onchange('product_pricelist_setting')
    def _onchange_product_pricelist_setting(self):
        if self.product_pricelist_setting == 'basic':
            self.group_sale_pricelist = False
        else:
            self.group_sale_pricelist = True

    def set_values(self):
        super(ResConfigSettings, self).set_values()
        if not self.group_discount_per_so_line:
            pl = self.env['product.pricelist'].search([('discount_policy', '=', 'without_discount')])
            pl.write({'discount_policy': 'with_discount'})

    @api.onchange('module_sale_product_matrix')
    def _onchange_module_module_sale_product_matrix(self):
        """The product Grid Configurator requires the product Configurator activated
        If the user enables the Grid Configurator -> enable the product Configurator as well"""
        if self.module_sale_product_matrix and not self.module_sale_product_configurator:
            self.module_sale_product_configurator = True
Пример #24
0
class Module(models.Model):
    _name = "ir.module.module"
    _rec_name = "shortdesc"
    _description = "Module"
    _order = 'sequence,name'

    @api.model
    def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
        res = super(Module, self).fields_view_get(view_id, view_type, toolbar=toolbar, submenu=False)
        if view_type == 'form' and res.get('toolbar',False):
            install_id = self.env.ref('base.action_server_module_immediate_install').id
            action = [rec for rec in res['toolbar']['action'] if rec.get('id', False) != install_id]
            res['toolbar'] = {'action': action}
        return res

    @classmethod
    def get_module_info(cls, name):
        try:
            return modules.load_information_from_description_file(name)
        except Exception:
            _logger.debug('Error when trying to fetch information for module %s', name, exc_info=True)
            return {}

    @api.depends('name', 'description')
    def _get_desc(self):
        for module in self:
            path = modules.get_module_resource(module.name, 'static/description/index.html')
            if path:
                with tools.file_open(path, 'rb') as desc_file:
                    doc = desc_file.read()
                    html = lxml.html.document_fromstring(doc)
                    for element, attribute, link, pos in html.iterlinks():
                        if element.get('src') and not '//' in element.get('src') and not 'static/' in element.get('src'):
                            element.set('src', "/%s/static/description/%s" % (module.name, element.get('src')))
                    module.description_html = tools.html_sanitize(lxml.html.tostring(html))
            else:
                overrides = {
                    'embed_stylesheet': False,
                    'doctitle_xform': False,
                    'output_encoding': 'unicode',
                    'xml_declaration': False,
                    'file_insertion_enabled': False,
                }
                output = publish_string(source=module.description if not module.application and module.description else '', settings_overrides=overrides, writer=MyWriter())
                module.description_html = tools.html_sanitize(output)

    @api.depends('name')
    def _get_latest_version(self):
        default_version = modules.adapt_version('1.0')
        for module in self:
            module.installed_version = self.get_module_info(module.name).get('version', default_version)

    @api.depends('name', 'state')
    def _get_views(self):
        IrModelData = self.env['ir.model.data'].with_context(active_test=True)
        dmodels = ['ir.ui.view', 'ir.actions.report', 'ir.ui.menu']

        for module in self:
            # Skip uninstalled modules below, no data to find anyway.
            if module.state not in ('installed', 'to upgrade', 'to remove'):
                module.views_by_module = ""
                module.reports_by_module = ""
                module.menus_by_module = ""
                continue

            # then, search and group ir.model.data records
            imd_models = defaultdict(list)
            imd_domain = [('module', '=', module.name), ('model', 'in', tuple(dmodels))]
            for data in IrModelData.sudo().search(imd_domain):
                imd_models[data.model].append(data.res_id)

            def browse(model):
                # as this method is called before the module update, some xmlid
                # may be invalid at this stage; explictly filter records before
                # reading them
                return self.env[model].browse(imd_models[model]).exists()

            def format_view(v):
                return '%s%s (%s)' % (v.inherit_id and '* INHERIT ' or '', v.name, v.type)

            module.views_by_module = "\n".join(sorted(format_view(v) for v in browse('ir.ui.view')))
            module.reports_by_module = "\n".join(sorted(r.name for r in browse('ir.actions.report')))
            module.menus_by_module = "\n".join(sorted(m.complete_name for m in browse('ir.ui.menu')))

    @api.depends('icon')
    def _get_icon_image(self):
        for module in self:
            module.icon_image = ''
            if module.icon:
                path_parts = module.icon.split('/')
                path = modules.get_module_resource(path_parts[1], *path_parts[2:])
            else:
                path = modules.module.get_module_icon(module.name)
            if path:
                with tools.file_open(path, 'rb') as image_file:
                    module.icon_image = base64.b64encode(image_file.read())

    name = fields.Char('Technical Name', readonly=True, required=True, index=True)
    category_id = fields.Many2one('ir.module.category', string='Category', readonly=True, index=True)
    shortdesc = fields.Char('Module Name', readonly=True, translate=True)
    summary = fields.Char('Summary', readonly=True, translate=True)
    description = fields.Text('Description', readonly=True, translate=True)
    description_html = fields.Html('Description HTML', compute='_get_desc')
    author = fields.Char("Author", readonly=True)
    maintainer = fields.Char('Maintainer', readonly=True)
    contributors = fields.Text('Contributors', readonly=True)
    website = fields.Char("Website", readonly=True)

    # attention: Incorrect field names !!
    #   installed_version refers the latest version (the one on disk)
    #   latest_version refers the installed version (the one in database)
    #   published_version refers the version available on the repository
    installed_version = fields.Char('Latest Version', compute='_get_latest_version')
    latest_version = fields.Char('Installed Version', readonly=True)
    published_version = fields.Char('Published Version', readonly=True)

    url = fields.Char('URL', readonly=True)
    sequence = fields.Integer('Sequence', default=100)
    dependencies_id = fields.One2many('ir.module.module.dependency', 'module_id',
                                       string='Dependencies', readonly=True)
    exclusion_ids = fields.One2many('ir.module.module.exclusion', 'module_id',
                                    string='Exclusions', readonly=True)
    auto_install = fields.Boolean('Automatic Installation',
                                   help='An auto-installable module is automatically installed by the '
                                        'system when all its dependencies are satisfied. '
                                        'If the module has no dependency, it is always installed.')
    state = fields.Selection(STATES, string='Status', default='uninstallable', readonly=True, index=True)
    demo = fields.Boolean('Demo Data', default=False, readonly=True)
    license = fields.Selection([
        ('GPL-2', 'GPL Version 2'),
        ('GPL-2 or any later version', 'GPL-2 or later version'),
        ('GPL-3', 'GPL Version 3'),
        ('GPL-3 or any later version', 'GPL-3 or later version'),
        ('AGPL-3', 'Affero GPL-3'),
        ('LGPL-3', 'LGPL Version 3'),
        ('Other OSI approved licence', 'Other OSI Approved Licence'),
        ('OEEL-1', 'Odoo Enterprise Edition License v1.0'),
        ('OPL-1', 'Odoo Proprietary License v1.0'),
        ('Other proprietary', 'Other Proprietary')
    ], string='License', default='LGPL-3', readonly=True)
    menus_by_module = fields.Text(string='Menus', compute='_get_views', store=True)
    reports_by_module = fields.Text(string='Reports', compute='_get_views', store=True)
    views_by_module = fields.Text(string='Views', compute='_get_views', store=True)
    application = fields.Boolean('Application', readonly=True)
    icon = fields.Char('Icon URL')
    icon_image = fields.Binary(string='Icon', compute='_get_icon_image')
    to_buy = fields.Boolean('Odoo Enterprise Module', default=False)

    _sql_constraints = [
        ('name_uniq', 'UNIQUE (name)', 'The name of the module must be unique!'),
    ]

    @api.multi
    def unlink(self):
        if not self:
            return True
        for module in self:
            if module.state in ('installed', 'to upgrade', 'to remove', 'to install'):
                raise UserError(_('You are trying to remove a module that is installed or will be installed.'))
        self.clear_caches()
        return super(Module, self).unlink()

    @staticmethod
    def _check_external_dependencies(terp):
        depends = terp.get('external_dependencies')
        if not depends:
            return
        for pydep in depends.get('python', []):
            try:
                importlib.import_module(pydep)
            except ImportError:
                raise ImportError('No module named %s' % (pydep,))

        for binary in depends.get('bin', []):
            try:
                tools.find_in_path(binary)
            except IOError:
                raise Exception('Unable to find %r in path' % (binary,))

    @classmethod
    def check_external_dependencies(cls, module_name, newstate='to install'):
        terp = cls.get_module_info(module_name)
        try:
            cls._check_external_dependencies(terp)
        except Exception as e:
            if newstate == 'to install':
                msg = _('Unable to install module "%s" because an external dependency is not met: %s')
            elif newstate == 'to upgrade':
                msg = _('Unable to upgrade module "%s" because an external dependency is not met: %s')
            else:
                msg = _('Unable to process module "%s" because an external dependency is not met: %s')
            raise UserError(msg % (module_name, e.args[0]))

    @api.multi
    def _state_update(self, newstate, states_to_update, level=100):
        if level < 1:
            raise UserError(_('Recursion error in modules dependencies !'))

        # whether some modules are installed with demo data
        demo = False

        for module in self:
            # determine dependency modules to update/others
            update_mods, ready_mods = self.browse(), self.browse()
            for dep in module.dependencies_id:
                if dep.state == 'unknown':
                    raise UserError(_("You try to install module '%s' that depends on module '%s'.\nBut the latter module is not available in your system.") % (module.name, dep.name,))
                if dep.depend_id.state == newstate:
                    ready_mods += dep.depend_id
                else:
                    update_mods += dep.depend_id

            # update dependency modules that require it, and determine demo for module
            update_demo = update_mods._state_update(newstate, states_to_update, level=level-1)
            module_demo = module.demo or update_demo or any(mod.demo for mod in ready_mods)
            demo = demo or module_demo

            if module.state in states_to_update:
                # check dependencies and update module itself
                self.check_external_dependencies(module.name, newstate)
                module.write({'state': newstate, 'demo': module_demo})

        return demo

    @assert_log_admin_access
    @api.multi
    def button_install(self):
        # domain to select auto-installable (but not yet installed) modules
        auto_domain = [('state', '=', 'uninstalled'), ('auto_install', '=', True)]

        # determine whether an auto-install module must be installed:
        #  - all its dependencies are installed or to be installed,
        #  - at least one dependency is 'to install'
        install_states = frozenset(('installed', 'to install', 'to upgrade'))
        def must_install(module):
            states = set(dep.state for dep in module.dependencies_id)
            return states <= install_states and 'to install' in states

        modules = self
        while modules:
            # Mark the given modules and their dependencies to be installed.
            modules._state_update('to install', ['uninstalled'])

            # Determine which auto-installable modules must be installed.
            modules = self.search(auto_domain).filtered(must_install)

        # the modules that are installed/to install/to upgrade
        install_mods = self.search([('state', 'in', list(install_states))])

        # check individual exclusions
        install_names = {module.name for module in install_mods}
        for module in install_mods:
            for exclusion in module.exclusion_ids:
                if exclusion.name in install_names:
                    msg = _('Modules "%s" and "%s" are incompatible.')
                    raise UserError(msg % (module.shortdesc, exclusion.exclusion_id.shortdesc))

        # check category exclusions
        def closure(module):
            todo = result = module
            while todo:
                result |= todo
                todo = todo.mapped('dependencies_id.depend_id')
            return result

        exclusives = self.env['ir.module.category'].search([('exclusive', '=', True)])
        for category in exclusives:
            # retrieve installed modules in category and sub-categories
            categories = category.search([('id', 'child_of', category.ids)])
            modules = install_mods.filtered(lambda mod: mod.category_id in categories)
            # the installation is valid if all installed modules in categories
            # belong to the transitive dependencies of one of them
            if modules and not any(modules <= closure(module) for module in modules):
                msg = _('You are trying to install incompatible modules in category "%s":')
                labels = dict(self.fields_get(['state'])['state']['selection'])
                raise UserError("\n".join([msg % category.name] + [
                    "- %s (%s)" % (module.shortdesc, labels[module.state])
                    for module in modules
                ]))

        return dict(ACTION_DICT, name=_('Install'))

    @assert_log_admin_access
    @api.multi
    def button_immediate_install(self):
        """ Installs the selected module(s) immediately and fully,
        returns the next res.config action to execute

        :returns: next res.config item to execute
        :rtype: dict[str, object]
        """
        _logger.info('User #%d triggered module installation', self.env.uid)
        return self._button_immediate_function(type(self).button_install)

    @assert_log_admin_access
    @api.multi
    def button_install_cancel(self):
        self.write({'state': 'uninstalled', 'demo': False})
        return True

    @assert_log_admin_access
    @api.multi
    def module_uninstall(self):
        """ Perform the various steps required to uninstall a module completely
        including the deletion of all database structures created by the module:
        tables, columns, constraints, etc.
        """
        modules_to_remove = self.mapped('name')
        self.env['ir.model.data']._module_data_uninstall(modules_to_remove)
        # we deactivate prefetching to not try to read a column that has been deleted
        self.with_context(prefetch_fields=False).write({'state': 'uninstalled', 'latest_version': False})
        return True

    @api.multi
    def _remove_copied_views(self):
        """ Remove the copies of the views installed by the modules in `self`.

        Those copies do not have an external id so they will not be cleaned by
        `_module_data_uninstall`. This is why we rely on `key` instead.

        It is important to remove these copies because using them will crash if
        they rely on data that don't exist anymore if the module is removed.
        """
        domain = expression.OR([[('key', '=like', m.name + '.%')] for m in self])
        orphans = self.env['ir.ui.view'].with_context(**{'active_test': False, MODULE_UNINSTALL_FLAG: True}).search(domain)
        orphans.unlink()

    @api.multi
    @api.returns('self')
    def downstream_dependencies(self, known_deps=None,
                                exclude_states=('uninstalled', 'uninstallable', 'to remove')):
        """ Return the modules that directly or indirectly depend on the modules
        in `self`, and that satisfy the `exclude_states` filter.
        """
        if not self:
            return self
        known_deps = known_deps or self.browse()
        query = """ SELECT DISTINCT m.id
                    FROM ir_module_module_dependency d
                    JOIN ir_module_module m ON (d.module_id=m.id)
                    WHERE
                        d.name IN (SELECT name from ir_module_module where id in %s) AND
                        m.state NOT IN %s AND
                        m.id NOT IN %s """
        self._cr.execute(query, (tuple(self.ids), tuple(exclude_states), tuple(known_deps.ids or self.ids)))
        new_deps = self.browse([row[0] for row in self._cr.fetchall()])
        missing_mods = new_deps - known_deps
        known_deps |= new_deps
        if missing_mods:
            known_deps |= missing_mods.downstream_dependencies(known_deps, exclude_states)
        return known_deps

    @api.multi
    @api.returns('self')
    def upstream_dependencies(self, known_deps=None,
                              exclude_states=('installed', 'uninstallable', 'to remove')):
        """ Return the dependency tree of modules of the modules in `self`, and
        that satisfy the `exclude_states` filter.
        """
        if not self:
            return self
        known_deps = known_deps or self.browse()
        query = """ SELECT DISTINCT m.id
                    FROM ir_module_module_dependency d
                    JOIN ir_module_module m ON (d.module_id=m.id)
                    WHERE
                        m.name IN (SELECT name from ir_module_module_dependency where module_id in %s) AND
                        m.state NOT IN %s AND
                        m.id NOT IN %s """
        self._cr.execute(query, (tuple(self.ids), tuple(exclude_states), tuple(known_deps.ids or self.ids)))
        new_deps = self.browse([row[0] for row in self._cr.fetchall()])
        missing_mods = new_deps - known_deps
        known_deps |= new_deps
        if missing_mods:
            known_deps |= missing_mods.upstream_dependencies(known_deps, exclude_states)
        return known_deps

    def next(self):
        """
        Return the action linked to an ir.actions.todo is there exists one that
        should be executed. Otherwise, redirect to /web
        """
        Todos = self.env['ir.actions.todo']
        _logger.info('getting next %s', Todos)
        active_todo = Todos.search([('state', '=', 'open')], limit=1)
        if active_todo:
            _logger.info('next action is "%s"', active_todo.name)
            return active_todo.action_launch()
        return {
            'type': 'ir.actions.act_url',
            'target': 'self',
            'url': '/web',
        }

    @api.multi
    def _button_immediate_function(self, function):
        try:
            # This is done because the installation/uninstallation/upgrade can modify a currently
            # running cron job and prevent it from finishing, and since the ir_cron table is locked
            # during execution, the lock won't be released until timeout.
            self._cr.execute("SELECT * FROM ir_cron FOR UPDATE NOWAIT")
        except psycopg2.OperationalError:
            raise UserError(_("The server is busy right now, module operations are not possible at"
                              " this time, please try again later."))
        function(self)

        self._cr.commit()
        api.Environment.reset()
        modules.registry.Registry.new(self._cr.dbname, update_module=True)

        self._cr.commit()
        env = api.Environment(self._cr, self._uid, self._context)
        # pylint: disable=next-method-called
        config = env['ir.module.module'].next() or {}
        if config.get('type') not in ('ir.actions.act_window_close',):
            return config

        # reload the client; open the first available root menu
        menu = env['ir.ui.menu'].search([('parent_id', '=', False)])[:1]
        return {
            'type': 'ir.actions.client',
            'tag': 'reload',
            'params': {'menu_id': menu.id},
        }

    @assert_log_admin_access
    @api.multi
    def button_immediate_uninstall(self):
        """
        Uninstall the selected module(s) immediately and fully,
        returns the next res.config action to execute
        """
        _logger.info('User #%d triggered module uninstallation', self.env.uid)
        return self._button_immediate_function(type(self).button_uninstall)

    @assert_log_admin_access
    @api.multi
    def button_uninstall(self):
        if 'base' in self.mapped('name'):
            raise UserError(_("The `base` module cannot be uninstalled"))
        if not all(state in ('installed', 'to upgrade') for state in self.mapped('state')):
            raise UserError(_(
                "One or more of the selected modules have already been uninstalled, if you "
                "believe this to be an error, you may try again later or contact support."
            ))
        deps = self.downstream_dependencies()
        (self + deps).write({'state': 'to remove'})
        return dict(ACTION_DICT, name=_('Uninstall'))

    @assert_log_admin_access
    @api.multi
    def button_uninstall_wizard(self):
        """ Launch the wizard to uninstall the given module. """
        return {
            'type': 'ir.actions.act_window',
            'target': 'new',
            'name': _('Uninstall module'),
            'view_mode': 'form',
            'res_model': 'base.module.uninstall',
            'context': {'default_module_id': self.id},
        }

    @api.multi
    def button_uninstall_cancel(self):
        self.write({'state': 'installed'})
        return True

    @assert_log_admin_access
    @api.multi
    def button_immediate_upgrade(self):
        """
        Upgrade the selected module(s) immediately and fully,
        return the next res.config action to execute
        """
        return self._button_immediate_function(type(self).button_upgrade)

    @assert_log_admin_access
    @api.multi
    def button_upgrade(self):
        Dependency = self.env['ir.module.module.dependency']
        self.update_list()

        todo = list(self)
        i = 0
        while i < len(todo):
            module = todo[i]
            i += 1
            if module.state not in ('installed', 'to upgrade'):
                raise UserError(_("Can not upgrade module '%s'. It is not installed.") % (module.name,))
            self.check_external_dependencies(module.name, 'to upgrade')
            for dep in Dependency.search([('name', '=', module.name)]):
                if dep.module_id.state == 'installed' and dep.module_id not in todo:
                    todo.append(dep.module_id)

        self.browse(module.id for module in todo).write({'state': 'to upgrade'})

        to_install = []
        for module in todo:
            for dep in module.dependencies_id:
                if dep.state == 'unknown':
                    raise UserError(_('You try to upgrade the module %s that depends on the module: %s.\nBut this module is not available in your system.') % (module.name, dep.name,))
                if dep.state == 'uninstalled':
                    to_install += self.search([('name', '=', dep.name)]).ids

        self.browse(to_install).button_install()
        return dict(ACTION_DICT, name=_('Apply Schedule Upgrade'))

    @assert_log_admin_access
    @api.multi
    def button_upgrade_cancel(self):
        self.write({'state': 'installed'})
        return True

    @staticmethod
    def get_values_from_terp(terp):
        return {
            'description': terp.get('description', ''),
            'shortdesc': terp.get('name', ''),
            'author': terp.get('author', 'Unknown'),
            'maintainer': terp.get('maintainer', False),
            'contributors': ', '.join(terp.get('contributors', [])) or False,
            'website': terp.get('website', ''),
            'license': terp.get('license', 'LGPL-3'),
            'sequence': terp.get('sequence', 100),
            'application': terp.get('application', False),
            'auto_install': terp.get('auto_install', False),
            'icon': terp.get('icon', False),
            'summary': terp.get('summary', ''),
            'url': terp.get('url') or terp.get('live_test_url', ''),
            'to_buy': False
        }

    @api.model
    def create(self, vals):
        new = super(Module, self).create(vals)
        module_metadata = {
            'name': 'module_%s' % vals['name'],
            'model': 'ir.module.module',
            'module': 'base',
            'res_id': new.id,
            'noupdate': True,
        }
        self.env['ir.model.data'].create(module_metadata)
        return new

    # update the list of available packages
    @assert_log_admin_access
    @api.model
    def update_list(self):
        res = [0, 0]    # [update, add]

        default_version = modules.adapt_version('1.0')
        known_mods = self.with_context(lang=None).search([])
        known_mods_names = {mod.name: mod for mod in known_mods}

        # iterate through detected modules and update/create them in db
        for mod_name in modules.get_modules():
            mod = known_mods_names.get(mod_name)
            terp = self.get_module_info(mod_name)
            values = self.get_values_from_terp(terp)

            if mod:
                updated_values = {}
                for key in values:
                    old = getattr(mod, key)
                    updated = tools.ustr(values[key]) if isinstance(values[key], pycompat.string_types) else values[key]
                    if (old or updated) and updated != old:
                        updated_values[key] = values[key]
                if terp.get('installable', True) and mod.state == 'uninstallable':
                    updated_values['state'] = 'uninstalled'
                if parse_version(terp.get('version', default_version)) > parse_version(mod.latest_version or default_version):
                    res[0] += 1
                if updated_values:
                    mod.write(updated_values)
            else:
                mod_path = modules.get_module_path(mod_name)
                if not mod_path or not terp:
                    continue
                state = "uninstalled" if terp.get('installable', True) else "uninstallable"
                mod = self.create(dict(name=mod_name, state=state, **values))
                res[1] += 1

            mod._update_dependencies(terp.get('depends', []))
            mod._update_exclusions(terp.get('excludes', []))
            mod._update_category(terp.get('category', 'Uncategorized'))

        return res

    @assert_log_admin_access
    @api.multi
    def download(self, download=True):
        return []

    @assert_log_admin_access
    @api.model
    def install_from_urls(self, urls):
        if not self.env.user.has_group('base.group_system'):
            raise AccessDenied()

        # One-click install is opt-in - cfr Issue #15225
        ad_dir = tools.config.addons_data_dir
        if not os.access(ad_dir, os.W_OK):
            msg = (_("Automatic install of downloaded Apps is currently disabled.") + "\n\n" +
                   _("To enable it, make sure this directory exists and is writable on the server:") +
                   "\n%s" % ad_dir)
            _logger.warning(msg)
            raise UserError(msg)

        apps_server = urls.url_parse(self.get_apps_server())

        OPENERP = odoo.release.product_name.lower()
        tmp = tempfile.mkdtemp()
        _logger.debug('Install from url: %r', urls)
        try:
            # 1. Download & unzip missing modules
            for module_name, url in urls.items():
                if not url:
                    continue    # nothing to download, local version is already the last one

                up = urls.url_parse(url)
                if up.scheme != apps_server.scheme or up.netloc != apps_server.netloc:
                    raise AccessDenied()

                try:
                    _logger.info('Downloading module `%s` from OpenERP Apps', module_name)
                    response = requests.get(url)
                    response.raise_for_status()
                    content = response.content
                except Exception:
                    _logger.exception('Failed to fetch module %s', module_name)
                    raise UserError(_('The `%s` module appears to be unavailable at the moment, please try again later.') % module_name)
                else:
                    zipfile.ZipFile(io.BytesIO(content)).extractall(tmp)
                    assert os.path.isdir(os.path.join(tmp, module_name))

            # 2a. Copy/Replace module source in addons path
            for module_name, url in urls.items():
                if module_name == OPENERP or not url:
                    continue    # OPENERP is special case, handled below, and no URL means local module
                module_path = modules.get_module_path(module_name, downloaded=True, display_warning=False)
                bck = backup(module_path, False)
                _logger.info('Copy downloaded module `%s` to `%s`', module_name, module_path)
                shutil.move(os.path.join(tmp, module_name), module_path)
                if bck:
                    shutil.rmtree(bck)

            # 2b.  Copy/Replace server+base module source if downloaded
            if urls.get(OPENERP):
                # special case. it contains the server and the base module.
                # extract path is not the same
                base_path = os.path.dirname(modules.get_module_path('base'))

                # copy all modules in the SERVER/odoo/addons directory to the new "odoo" module (except base itself)
                for d in os.listdir(base_path):
                    if d != 'base' and os.path.isdir(os.path.join(base_path, d)):
                        destdir = os.path.join(tmp, OPENERP, 'addons', d)    # XXX 'odoo' subdirectory ?
                        shutil.copytree(os.path.join(base_path, d), destdir)

                # then replace the server by the new "base" module
                server_dir = tools.config['root_path']      # XXX or dirname()
                bck = backup(server_dir)
                _logger.info('Copy downloaded module `odoo` to `%s`', server_dir)
                shutil.move(os.path.join(tmp, OPENERP), server_dir)
                #if bck:
                #    shutil.rmtree(bck)

            self.update_list()

            with_urls = [module_name for module_name, url in urls.items() if url]
            downloaded = self.search([('name', 'in', with_urls)])
            installed = self.search([('id', 'in', downloaded.ids), ('state', '=', 'installed')])

            to_install = self.search([('name', 'in', list(urls)), ('state', '=', 'uninstalled')])
            post_install_action = to_install.button_immediate_install()

            if installed or to_install:
                # in this case, force server restart to reload python code...
                self._cr.commit()
                odoo.service.server.restart()
                return {
                    'type': 'ir.actions.client',
                    'tag': 'home',
                    'params': {'wait': True},
                }
            return post_install_action

        finally:
            shutil.rmtree(tmp)

    @api.model
    def get_apps_server(self):
        return tools.config.get('apps_server', 'https://apps.odoo.com/apps')

    def _update_dependencies(self, depends=None):
        existing = set(dep.name for dep in self.dependencies_id)
        needed = set(depends or [])
        for dep in (needed - existing):
            self._cr.execute('INSERT INTO ir_module_module_dependency (module_id, name) values (%s, %s)', (self.id, dep))
        for dep in (existing - needed):
            self._cr.execute('DELETE FROM ir_module_module_dependency WHERE module_id = %s and name = %s', (self.id, dep))
        self.invalidate_cache(['dependencies_id'], self.ids)

    def _update_exclusions(self, excludes=None):
        existing = set(excl.name for excl in self.exclusion_ids)
        needed = set(excludes or [])
        for name in (needed - existing):
            self._cr.execute('INSERT INTO ir_module_module_exclusion (module_id, name) VALUES (%s, %s)', (self.id, name))
        for name in (existing - needed):
            self._cr.execute('DELETE FROM ir_module_module_exclusion WHERE module_id=%s AND name=%s', (self.id, name))
        self.invalidate_cache(['exclusion_ids'], self.ids)

    def _update_category(self, category='Uncategorized'):
        current_category = self.category_id
        current_category_path = []
        while current_category:
            current_category_path.insert(0, current_category.name)
            current_category = current_category.parent_id

        categs = category.split('/')
        if categs != current_category_path:
            cat_id = modules.db.create_categories(self._cr, categs)
            self.write({'category_id': cat_id})

    @api.multi
    def _update_translations(self, filter_lang=None):
        if not filter_lang:
            langs = self.env['res.lang'].search([('translatable', '=', True)])
            filter_lang = [lang.code for lang in langs]
        elif not isinstance(filter_lang, (list, tuple)):
            filter_lang = [filter_lang]

        update_mods = self.filtered(lambda r: r.state in ('installed', 'to install', 'to upgrade'))
        mod_dict = {
            mod.name: mod.dependencies_id.mapped('name')
            for mod in update_mods
        }
        mod_names = topological_sort(mod_dict)
        self.env['ir.translation'].load_module_terms(mod_names, filter_lang)

    @api.multi
    def _check(self):
        for module in self:
            if not module.description_html:
                _logger.warning('module %s: description is empty !', module.name)

    @api.model
    @tools.ormcache()
    def _installed(self):
        """ Return the set of installed modules as a dictionary {name: id} """
        return {
            module.name: module.id
            for module in self.sudo().search([('state', '=', 'installed')])
        }
Пример #25
0
class IrSequence(models.Model):
    _inherit = "ir.sequence"

    def get_qty_available(self, folio=None):
        folio = folio or self._get_folio()
        try:
            cafs = self.get_caf_files(folio)
        except UserError:
            cafs = False
        available = 0
        folio = int(folio)
        if cafs:
            for c in cafs:
                if folio >= c.start_nm and folio <= c.final_nm:
                    available += c.final_nm - folio
                elif folio <= c.final_nm:
                    available += (c.final_nm - c.start_nm) + 1
                if folio > c.start_nm:
                    available += 1
        return available

    def _compute_qty_available(self):
        for i in self:
            if i.class_id:
                i.qty_available = i.get_qty_available()

    class_id = fields.Many2one("sii.document.class", string="Document Type")
    is_dte = fields.Boolean(string="Is DTE?", related="class_id.dte")
    folio_ids = fields.One2many("ir.sequence.folio",
                                "sequence_id",
                                string="DTE Caf")
    qty_available = fields.Integer(string="Quantity Available",
                                   compute="_compute_qty_available")
    forced_by_caf = fields.Boolean(string="Forced By CAF", default=True)

    def _get_folio(self):
        return self.number_next_actual

    def time_stamp(self, formato="%Y-%m-%dT%H:%M:%S"):
        tz = pytz.timezone("America/Santiago")
        return datetime.now(tz).strftime(formato)

    def get_caf_file(self, folio=False):
        folio = folio or self._get_folio()
        caffiles = self.get_caf_files(folio)
        msg = """There is no folio for this document: {}, out of range.
                Request a new CAF on https://www.sii.cl""".format(folio)
        if not caffiles:
            raise UserError(
                _("There is no folio available for the document %s folio %s. "
                  "Please request a new CAF to SII." % (self.name, folio)))
        for caffile in caffiles:
            if caffile.start_nm <= int(folio) <= caffile.final_nm:
                if caffile.expiration_date:
                    if fields.Date.context_today(self) > \
                            caffile.expiration_date:
                        msg = "CAF Expired. %s" % msg
                        continue
                alert_msg = caffile.check_nivel(folio)
                if alert_msg != "":
                    self.env["bus.bus"].sendone(
                        (self._cr.dbname, "dte.caf",
                         self.env.user.partner_id.id),
                        {
                            "title": "Alert on CAF",
                            "message": alert_msg,
                            "url": "res_config",
                            "type": "dte_notif",
                        },
                    )
                return caffile.decode_caf()
        raise UserError(_(msg))

    def get_caf_files(self, folio=None):
        folio = folio or self._get_folio()
        if not self.folio_ids:
            raise UserError(
                _("There is no CAF available for the sequence of %s. "
                  "Please upload a new CAF or request a new one to SII."
                  "" % (self.name)))
        cafs = self.folio_ids
        cafs = sorted(cafs, key=lambda e: e.start_nm)
        result = []
        for caffile in cafs:
            if int(folio) <= caffile.final_nm:
                result.append(caffile)
        if result:
            return result
        return False

    def update_next_by_caf(self, folio=None):
        if self.class_id:
            return
        folio = folio or self._get_folio()
        menor = False
        cafs = self.get_caf_files(folio)
        if not cafs:
            raise UserError(_("There is no CAF available for %s.") % self.name)
        for c in cafs:
            if not menor or c.start_nm < menor.start_nm:
                menor = c
        if menor and int(folio) < menor.start_nm:
            self.sudo(SUPERUSER_ID).write({"number_next": menor.start_nm})

    def _next_do(self):
        number_next = self.number_next
        if self.implementation == "standard":
            number_next = self.number_next_actual
        folio = super(IrSequence, self)._next_do()
        if self.class_id and self.forced_by_caf and \
                self.folio_ids:
            self.update_next_by_caf(folio)
            actual = self.number_next
            if self.implementation == "standard":
                actual = self.number_next_actual
            if number_next + 1 != actual:  # Fue actualizado
                number_next = actual
            folio = self.get_next_char(number_next)
        return folio
Пример #26
0
class AgedPartnerBalanceWizard(models.TransientModel):
    """Aged partner balance report wizard."""

    _name = "aged.partner.balance.report.wizard"
    _description = "Aged Partner Balance Wizard"
    _inherit = "account_financial_report_abstract_wizard"

    company_id = fields.Many2one(
        comodel_name="res.company",
        default=lambda self: self.env.company,
        required=False,
        string="Company",
    )
    date_at = fields.Date(required=True, default=fields.Date.context_today)
    date_from = fields.Date(string="Date From")
    target_move = fields.Selection(
        [("posted", "All Posted Entries"), ("all", "All Entries")],
        string="Target Moves",
        required=True,
        default="posted",
    )
    account_ids = fields.Many2many(
        comodel_name="account.account",
        string="Filter accounts",
        domain=[("reconcile", "=", True)],
        required=True,
    )
    receivable_accounts_only = fields.Boolean()
    payable_accounts_only = fields.Boolean()
    partner_ids = fields.Many2many(comodel_name="res.partner",
                                   string="Filter partners")
    show_move_line_details = fields.Boolean()

    account_code_from = fields.Many2one(
        comodel_name="account.account",
        string="Account Code From",
        help="Starting account in a range",
    )
    account_code_to = fields.Many2one(
        comodel_name="account.account",
        string="Account Code To",
        help="Ending account in a range",
    )

    @api.onchange("account_code_from", "account_code_to")
    def on_change_account_range(self):
        if (self.account_code_from and self.account_code_from.code.isdigit()
                and self.account_code_to
                and self.account_code_to.code.isdigit()):
            start_range = int(self.account_code_from.code)
            end_range = int(self.account_code_to.code)
            self.account_ids = self.env["account.account"].search([
                ("code", "in", [x for x in range(start_range, end_range + 1)]),
                ("reconcile", "=", True),
            ])
            if self.company_id:
                self.account_ids = self.account_ids.filtered(
                    lambda a: a.company_id == self.company_id)
        return {
            "domain": {
                "account_code_from": [("reconcile", "=", True)],
                "account_code_to": [("reconcile", "=", True)],
            }
        }

    @api.onchange("company_id")
    def onchange_company_id(self):
        """Handle company change."""
        if self.company_id and self.partner_ids:
            self.partner_ids = self.partner_ids.filtered(
                lambda p: p.company_id == self.company_id or not p.company_id)
        if self.company_id and self.account_ids:
            if self.receivable_accounts_only or self.payable_accounts_only:
                self.onchange_type_accounts_only()
            else:
                self.account_ids = self.account_ids.filtered(
                    lambda a: a.company_id == self.company_id)
        res = {"domain": {"account_ids": [], "partner_ids": []}}
        if not self.company_id:
            return res
        else:
            res["domain"]["account_ids"] += [("company_id", "=",
                                              self.company_id.id)]
            res["domain"]["partner_ids"] += self._get_partner_ids_domain()
        return res

    @api.onchange("account_ids")
    def onchange_account_ids(self):
        return {"domain": {"account_ids": [("reconcile", "=", True)]}}

    @api.onchange("receivable_accounts_only", "payable_accounts_only")
    def onchange_type_accounts_only(self):
        """Handle receivable/payable accounts only change."""
        domain = [("company_id", "=", self.company_id.id)]
        if self.receivable_accounts_only or self.payable_accounts_only:
            if self.receivable_accounts_only and self.payable_accounts_only:
                domain += [("internal_type", "in", ("receivable", "payable"))]
            elif self.receivable_accounts_only:
                domain += [("internal_type", "=", "receivable")]
            elif self.payable_accounts_only:
                domain += [("internal_type", "=", "payable")]
            self.account_ids = self.env["account.account"].search(domain)
        else:
            self.account_ids = None

    def _print_report(self, report_type):
        self.ensure_one()
        data = self._prepare_report_aged_partner_balance()
        if report_type == "xlsx":
            report_name = "a_f_r.report_aged_partner_balance_xlsx"
        else:
            report_name = "account_financial_report.aged_partner_balance"
        return (self.env["ir.actions.report"].search(
            [("report_name", "=", report_name),
             ("report_type", "=", report_type)],
            limit=1,
        ).report_action(self, data=data))

    def button_export_html(self):
        self.ensure_one()
        report_type = "qweb-html"
        return self._export(report_type)

    def button_export_pdf(self):
        self.ensure_one()
        report_type = "qweb-pdf"
        return self._export(report_type)

    def button_export_xlsx(self):
        self.ensure_one()
        report_type = "xlsx"
        return self._export(report_type)

    def _prepare_report_aged_partner_balance(self):
        self.ensure_one()
        return {
            "wizard_id": self.id,
            "date_at": self.date_at,
            "date_from": self.date_from or False,
            "only_posted_moves": self.target_move == "posted",
            "company_id": self.company_id.id,
            "account_ids": self.account_ids.ids,
            "partner_ids": self.partner_ids.ids,
            "show_move_line_details": self.show_move_line_details,
            "account_financial_report_lang": self.env.lang,
        }

    def _export(self, report_type):
        """Default export is PDF."""
        return self._print_report(report_type)
Пример #27
0
class OtherMoneyOrder(models.Model):
    _name = 'other.money.order'
    _description = u'其他收入/其他支出'
    _inherit = ['mail.thread']

    TYPE_SELECTION = [
        ('other_pay', u'其他支出'),
        ('other_get', u'其他收入'),
    ]

    @api.model
    def create(self, values):
        # 创建单据时,更新订单类型的不同,生成不同的单据编号
        if self.env.context.get('type') == 'other_get':
            values.update({
                'name':
                self.env['ir.sequence'].next_by_code('other.get.order')
            })
        if self.env.context.get('type') == 'other_pay' or values.get(
                'name', '/') == '/':
            values.update({
                'name':
                self.env['ir.sequence'].next_by_code('other.pay.order')
            })

        return super(OtherMoneyOrder, self).create(values)

    @api.one
    @api.depends('line_ids.amount', 'line_ids.tax_amount')
    def _compute_total_amount(self):
        # 计算应付金额/应收金额
        self.total_amount = sum(
            (line.amount + line.tax_amount) for line in self.line_ids)

    state = fields.Selection([
        ('draft', u'草稿'),
        ('done', u'已确认'),
        ('cancel', u'已作废'),
    ],
                             string=u'状态',
                             readonly=True,
                             default='draft',
                             copy=False,
                             index=True,
                             help=u'其他收支单状态标识,新建时状态为草稿;确认后状态为已确认')
    partner_id = fields.Many2one('partner',
                                 string=u'往来单位',
                                 readonly=True,
                                 ondelete='restrict',
                                 states={'draft': [('readonly', False)]},
                                 help=u'单据对应的业务伙伴,单据确认时会影响他的应收应付余额')
    date = fields.Date(string=u'单据日期',
                       readonly=True,
                       default=lambda self: fields.Date.context_today(self),
                       states={'draft': [('readonly', False)]},
                       copy=False,
                       help=u'单据创建日期')
    name = fields.Char(string=u'单据编号',
                       copy=False,
                       readonly=True,
                       default='/',
                       help=u'单据编号,创建时会根据类型自动生成')
    total_amount = fields.Float(string=u'金额',
                                compute='_compute_total_amount',
                                store=True,
                                readonly=True,
                                digits=dp.get_precision('Amount'),
                                help=u'本次其他收支的总金额')
    bank_id = fields.Many2one('bank.account',
                              string=u'结算账户',
                              required=True,
                              ondelete='restrict',
                              readonly=True,
                              states={'draft': [('readonly', False)]},
                              help=u'本次其他收支的结算账户')
    line_ids = fields.One2many('other.money.order.line',
                               'other_money_id',
                               string=u'收支单行',
                               readonly=True,
                               copy=True,
                               states={'draft': [('readonly', False)]},
                               help=u'其他收支单明细行')
    type = fields.Selection(TYPE_SELECTION,
                            string=u'类型',
                            readonly=True,
                            default=lambda self: self._context.get('type'),
                            states={'draft': [('readonly', False)]},
                            help=u'类型:其他收入 或者 其他支出')
    note = fields.Text(u'备注', help=u'可以为该单据添加一些需要的标识信息')

    is_init = fields.Boolean(u'初始化应收应付', help=u'此单是否为初始化单')
    company_id = fields.Many2one(
        'res.company',
        string=u'公司',
        change_default=True,
        default=lambda self: self.env['res.company']._company_default_get())
    receiver = fields.Char(u'收款人', help=u'收款人')
    bank_name = fields.Char(u'开户行')
    bank_num = fields.Char(u'银行账号')
    voucher_id = fields.Many2one('voucher',
                                 u'对应凭证',
                                 readonly=True,
                                 ondelete='restrict',
                                 copy=False,
                                 help=u'其他收支单确认时生成的对应凭证')
    currency_amount = fields.Float(u'外币金额', digits=dp.get_precision('Amount'))

    @api.onchange('date')
    def onchange_date(self):
        if self._context.get('type') == 'other_get':
            return {'domain': {'partner_id': [('c_category_id', '!=', False)]}}
        else:
            return {'domain': {'partner_id': [('s_category_id', '!=', False)]}}

    @api.onchange('partner_id')
    def onchange_partner_id(self):
        """
        更改业务伙伴,自动填入收款人、开户行和银行帐号
        """
        if self.partner_id:
            self.receiver = self.partner_id.name
            self.bank_name = self.partner_id.bank_name
            self.bank_num = self.partner_id.bank_num

    @api.multi
    def other_money_done(self):
        '''其他收支单的审核按钮'''
        self.ensure_one()
        if float_compare(self.total_amount, 0, 3) <= 0:
            raise UserError(u'金额应该大于0。\n金额:%s' % self.total_amount)
        if not self.bank_id.account_id:
            raise UserError(u'请配置%s的会计科目' % (self.bank_id.name))

        # 根据单据类型更新账户余额
        if self.type == 'other_pay':
            decimal_amount = self.env.ref('core.decimal_amount')
            if float_compare(self.bank_id.balance, self.total_amount,
                             decimal_amount.digits) == -1:
                raise UserError(u'账户余额不足。\n账户余额:%s 本次支出金额:%s' %
                                (self.bank_id.balance, self.total_amount))
            self.bank_id.balance -= self.total_amount
        else:
            self.bank_id.balance += self.total_amount

        # 创建凭证并审核非初始化凭证
        vouch_obj = self.create_voucher()
        return self.write({
            'voucher_id': vouch_obj.id,
            'state': 'done',
        })

    @api.multi
    def other_money_draft(self):
        '''其他收支单的反审核按钮'''
        self.ensure_one()
        # 根据单据类型更新账户余额
        if self.type == 'other_pay':
            self.bank_id.balance += self.total_amount
        else:
            decimal_amount = self.env.ref('core.decimal_amount')
            if float_compare(self.bank_id.balance, self.total_amount,
                             decimal_amount.digits) == -1:
                raise UserError(u'账户余额不足。\n账户余额:%s 本次支出金额:%s' %
                                (self.bank_id.balance, self.total_amount))
            self.bank_id.balance -= self.total_amount

        voucher = self.voucher_id
        self.write({
            'voucher_id': False,
            'state': 'draft',
        })
        # 反审核凭证并删除
        if voucher.state == 'done':
            voucher.voucher_draft()
        # 始初化单反审核只删除明细行
        if self.is_init:
            vouch_obj = self.env['voucher'].search([('id', '=', voucher.id)])
            vouch_obj_lines = self.env['voucher.line'].search([
                ('voucher_id', '=', vouch_obj.id),
                ('account_id', '=', self.bank_id.account_id.id),
                ('init_obj', '=', 'other_money_order-%s' % (self.id))
            ])
            for vouch_obj_line in vouch_obj_lines:
                vouch_obj_line.unlink()
        else:
            voucher.unlink()
        return True

    @api.multi
    def create_voucher(self):
        """创建凭证并审核非初始化凭证"""
        init_obj = ''
        # 初始化单的话,先找是否有初始化凭证,没有则新建一个
        if self.is_init:
            vouch_obj = self.env['voucher'].search([('is_init', '=', True)])
            if not vouch_obj:
                vouch_obj = self.env['voucher'].create({
                    'date':
                    self.date,
                    'is_init':
                    True,
                    'ref':
                    '%s,%s' % (self._name, self.id)
                })
        else:
            vouch_obj = self.env['voucher'].create({
                'date':
                self.date,
                'ref':
                '%s,%s' % (self._name, self.id)
            })
        if self.is_init:
            init_obj = 'other_money_order-%s' % (self.id)

        if self.type == 'other_get':  # 其他收入单
            self.other_get_create_voucher_line(vouch_obj, init_obj)
        else:  # 其他支出单
            self.other_pay_create_voucher_line(vouch_obj)

        # 如果非初始化单则审核
        if not self.is_init:
            vouch_obj.voucher_done()
        return vouch_obj

    def other_get_create_voucher_line(self, vouch_obj, init_obj):
        """
        其他收入单生成凭证明细行
        :param vouch_obj: 凭证
        :return:
        """
        vals = {}
        for line in self.line_ids:
            if not line.category_id.account_id:
                raise UserError(u'请配置%s的会计科目' % (line.category_id.name))

            rate_silent = self.env['res.currency'].get_rate_silent(
                self.date, self.bank_id.currency_id.id)
            vals.update({
                'vouch_obj_id': vouch_obj.id,
                'name': self.name,
                'note': line.note or '',
                'credit_auxiliary_id': line.auxiliary_id.id,
                'amount': abs(line.amount + line.tax_amount),
                'credit_account_id': line.category_id.account_id.id,
                'debit_account_id': self.bank_id.account_id.id,
                'partner_credit': self.partner_id.id,
                'partner_debit': '',
                'sell_tax_amount': line.tax_amount or 0,
                'init_obj': init_obj,
                'currency_id': self.bank_id.currency_id.id,
                'currency_amount': self.currency_amount,
                'rate_silent': rate_silent,
            })
            # 贷方行
            if not init_obj:
                self.env['voucher.line'].create({
                    'name':
                    u"%s %s" % (vals.get('name'), vals.get('note')),
                    'partner_id':
                    vals.get('partner_credit', ''),
                    'account_id':
                    vals.get('credit_account_id'),
                    'credit':
                    line.amount,
                    'voucher_id':
                    vals.get('vouch_obj_id'),
                    'auxiliary_id':
                    vals.get('credit_auxiliary_id', False),
                })
            # 销项税行
            if vals.get('sell_tax_amount'):
                if not self.env.user.company_id.output_tax_account:
                    raise UserError(
                        u'您还没有配置公司的销项税科目。\n请通过"配置-->高级配置-->公司"菜单来设置销项税科目!')
                self.env['voucher.line'].create({
                    'name':
                    u"%s %s" % (vals.get('name'), vals.get('note')),
                    'account_id':
                    self.env.user.company_id.output_tax_account.id,
                    'credit':
                    line.tax_amount or 0,
                    'voucher_id':
                    vals.get('vouch_obj_id'),
                })
        # 借方行
        self.env['voucher.line'].create({
            'name':
            u"%s" % (vals.get('name')),
            'account_id':
            vals.get('debit_account_id'),
            'debit':
            self.total_amount,  # 借方和
            'voucher_id':
            vals.get('vouch_obj_id'),
            'partner_id':
            vals.get('partner_debit', ''),
            'auxiliary_id':
            vals.get('debit_auxiliary_id', False),
            'init_obj':
            vals.get('init_obj', False),
            'currency_id':
            vals.get('currency_id', False),
            'currency_amount':
            vals.get('currency_amount'),
            'rate_silent':
            vals.get('rate_silent'),
        })

    def other_pay_create_voucher_line(self, vouch_obj):
        """
        其他支出单生成凭证明细行
        :param vouch_obj: 凭证
        :return:
        """
        vals = {}
        for line in self.line_ids:
            if not line.category_id.account_id:
                raise UserError(u'请配置%s的会计科目' % (line.category_id.name))

            rate_silent = self.env['res.currency'].get_rate_silent(
                self.date, self.bank_id.currency_id.id)
            vals.update({
                'vouch_obj_id': vouch_obj.id,
                'name': self.name,
                'note': line.note or '',
                'debit_auxiliary_id': line.auxiliary_id.id,
                'amount': abs(line.amount + line.tax_amount),
                'credit_account_id': self.bank_id.account_id.id,
                'debit_account_id': line.category_id.account_id.id,
                'partner_credit': '',
                'partner_debit': self.partner_id.id,
                'buy_tax_amount': line.tax_amount or 0,
                'currency_id': self.bank_id.currency_id.id,
                'currency_amount': self.currency_amount,
                'rate_silent': rate_silent,
            })
            # 借方行
            self.env['voucher.line'].create({
                'name':
                u"%s %s " % (vals.get('name'), vals.get('note')),
                'account_id':
                vals.get('debit_account_id'),
                'debit':
                line.amount,
                'voucher_id':
                vals.get('vouch_obj_id'),
                'partner_id':
                vals.get('partner_debit', ''),
                'auxiliary_id':
                vals.get('debit_auxiliary_id', False),
                'init_obj':
                vals.get('init_obj', False),
            })
            # 进项税行
            if vals.get('buy_tax_amount'):
                if not self.env.user.company_id.import_tax_account:
                    raise UserError(u'请通过"配置-->高级配置-->公司"菜单来设置进项税科目')
                self.env['voucher.line'].create({
                    'name':
                    u"%s %s" % (vals.get('name'), vals.get('note')),
                    'account_id':
                    self.env.user.company_id.import_tax_account.id,
                    'debit':
                    line.tax_amount or 0,
                    'voucher_id':
                    vals.get('vouch_obj_id'),
                })
        # 贷方行
        self.env['voucher.line'].create({
            'name':
            u"%s" % (vals.get('name')),
            'partner_id':
            vals.get('partner_credit', ''),
            'account_id':
            vals.get('credit_account_id'),
            'credit':
            self.total_amount,  # 贷方和
            'voucher_id':
            vals.get('vouch_obj_id'),
            'auxiliary_id':
            vals.get('credit_auxiliary_id', False),
            'init_obj':
            vals.get('init_obj', False),
            'currency_id':
            vals.get('currency_id', False),
            'currency_amount':
            vals.get('currency_amount'),
            'rate_silent':
            vals.get('rate_silent'),
        })
class VendorApplication(models.Model):
    _name = 'crm.vendor.application'

    name = fields.Char('Title', required=True,
                       default=lambda self: self.env['ir.sequence'].next_by_code('crm.vendor.application'))
    sale_order_id = fields.Many2one('sale.order', 'Sale Order')
    vendor_id = fields.Many2one('res.partner', 'Vendor Name', required=True)
    vendor_link = fields.Char(related='vendor_id.website', readonly=True)
    company_name = fields.Char(related='sale_order_id.partner_id.parent_id.name', readonly=True)
    company_street = fields.Char(related='sale_order_id.partner_id.parent_id.street', readonly=True)
    company_city = fields.Char(related='sale_order_id.partner_id.parent_id.city', readonly=True)
    company_state_id = fields.Many2one(related='sale_order_id.partner_id.parent_id.state_id', readonly=True)
    company_zip = fields.Char(related='sale_order_id.partner_id.parent_id.zip', readonly=True)
    company_years_business = fields.Integer(related='sale_order_id.opportunity_id.years_business', readonly=True)
    company_annual_revenue = fields.Float(related='sale_order_id.opportunity_id.annual_revenue', readonly=True)
    contact_name = fields.Many2one(related='sale_order_id.partner_id', readonly=True)
    contact_email = fields.Char(related='sale_order_id.partner_id.email', readonly=True)
    contact_phone = fields.Char(related='sale_order_id.partner_id.phone', readonly=True)
    contact_mobile = fields.Char(related='sale_order_id.partner_id.mobile', readonly=True)
    country_id = fields.Many2one(related='sale_order_id.partner_shipping_id.country_id', readonly=True, default=233)
    sent_last = fields.Datetime(string='Last Sent', help='Date and time of last application sent to this vendor.')
    sent_count = fields.Integer(string='Sent Count', help='No. of times this application has been sent.', default=0)
    so_vendor_status = fields.Boolean(related='sale_order_id.vendor_application_flag', readonly=True)
    vendor_status = fields.Selection([
        ('pending', 'Pending'),
        ('submitted', 'Submitted'),
        ('approved', 'Approved'),
        ('rejected', 'Rejected')], string='Status', default='pending', required=True)

    def _count_status(self, vid):
        desc = ''
        pending = 0
        submitted = 0
        approved = 0
        rejected = 0
        total = 0
        vendors = self.env['crm.vendor.application'].search([('sale_order_id', '=', vid)])
        for vendor in vendors:
            if vendor.vendor_status == 'pending':
                pending += 1
            if vendor.vendor_status == 'submitted':
                submitted += 1
            if vendor.vendor_status == 'approved':
                approved += 1
                if not vendor.sale_order_id.vendor_application_flag:
                    vendor.sale_order_id.vendor_application_flag = True
            if vendor.vendor_status == 'rejected':
                rejected += 1
        total = rejected + approved
        if submitted > 0 and total < 1:
            desc = 'Applications submitted to ' + str(submitted) + ' vendors.'
        elif rejected > 0 or approved > 0:
            desc = 'Response received from ' + str(total) + ' vendors: ' + str(approved) + ' approved and ' + str(
                rejected) + ' declined.'
        else:
            if pending == 0:
                pending += 1
            desc = 'Preparing applications for ' + str(pending) + ' vendors.'
        return desc

    @api.multi
    def action_approve(self):
        vendor = self.filtered(lambda s: s.vendor_status in ['submitted', 'rejected'])
        vendor.vendor_status = 'approved'
        vendor.sale_order_id.vendor_application_status = self._count_status(self.sale_order_id.id)
        vendor.sale_order_id.selected_vendor_id = self.vendor_id
        return {'type': 'ir.actions.act_window_close'}

    @api.multi
    def action_reject(self):
        vendor = self.filtered(lambda s: s.vendor_status in ['submitted'])
        vendor.vendor_status = 'rejected'
        vendor.sale_order_id.vendor_application_status = self._count_status(self.sale_order_id.id)
        return {'type': 'ir.actions.act_window_close'}
Пример #29
0
class  napataPresentation(models.Model):
    _name = 'napata.presentation'
    name = fields.Char(string="Full name", readonly=True)

    prenent_code = fields.Char(string='prenent code')
    is_done = fields.Boolean(string="Can apply")
    # get student name

    first_name = fields.Char(string='First Name')
    second_name = fields.Char(string='Second Name')
    third_name = fields.Char(string='Third Name')
    forth_name = fields.Char(string='Fourth Name')


    # end of name field
    nationality= fields.Many2one('res.country',
      string="Nationality")
    # nationality= fields.Char(straing='Nationalities')
    idtype=fields.Selection([
                        ('number', 'National number'),
                         ('card', 'National card'),
                        ('passport', 'Passport'),
                        ('other', 'Other')

    ],string="ID Tyep")
    id_number = fields.Char(string="The National Number")
    phone_1=fields.Integer(string="Phone Number")
    phone_2=fields.Integer(string="watsapp")
   
    Certificate = fields.Selection([
                        ('sudanese', 'Sudanese'),
                         ('arabic', 'Arabic'),
                        ('foreign', 'foreign'),
                        ('other', 'Other')

    ],string="Certificate Tyep")
    form = fields.Char(string="Form  Number")

    #
    # abut Us
    facebook=fields.Boolean(string="Face Book")
    website=fields.Boolean(string="Web Site")
    newspaper=fields.Boolean(string="Newspaper")
    tv=fields.Boolean(string="TV")
    radio=fields.Boolean(string="Radio")
    admission_book = fields.Boolean(string="Admission Book")

    course = fields.Selection([
                        ('Scientific', 'Scientific'),
                        ('literary', 'literary')],string="The course")
    sit_number = fields.Integer(string="Sitting Number")
    ratio=fields.Float(string="The ratio")
    # form_number= fields.Char(string='Form Number',
    #                        required=True, copy=False, readonly=True, index=True, default=lambda self: _('New'))
    exm_year= fields.Char(string=" exm_year" , default=(lambda self: str(datetime.datetime.now().year-1)+" / "+str(datetime.datetime.now().year) ))

    provider=fields.Char(string="Applicant Name")
    job=fields.Char(string="job")








    National_card=fields.Binary(string="National card")
    School_card=fields.Binary(string="High School Certificate")
    phone3=fields.Integer(string="Phone Number")
    parent= fields.Many2one('na.parent',
        ondelete='cascade')



    main=fields.Many2one("na.program",ondelete="cascade",string="Main Desire")
    sub=fields.Many2many("na.program",ondelete="cascade",string="Sub Desire")
    preType2=fields.Char(string="PresntaionType")


    fees = fields.Char(straing='Fees')
    data = fields.Char('Date current action', default=(lambda self: datetime.datetime.now().year))
    admission_ids = fields.Char(string='admission_ids')
    register = fields.Char(string='register')
    #
    pay_date = fields.Char()



    attendees_count = fields.Integer(
        string="Attendees count", compute='_get_attendees_count', store=True)
    # Degree   Holders
    submet=fields.Char(straing='Ok',default=("No hierarchy position.This employee has no manager or subordinate.In order to get an organigram, set a manager and save the record"))
    signup_valid = fields.Boolean(string='Submet')
    state = fields.Selection([
        ('draft', 'Draft'),
        ('done', 'done'),
    ], string='Status', default="draft", readonly=True)
    # about us desar
    @api.constrains('main', 'sub')
    def _check_instructor_not_in_attendees(self):
         for r in self:
            if r.main and r.main in r.sub:
                raise exceptions.ValidationError("This desire has been chosen as the main desire that cannot be chosen within the sub-desire")
    # about us validation
    @api.constrains('facebook',"website","newspaper","tv","radio","admission_book")
    def _check_validation(self):
        is_valid=False

        if self.facebook == True:
            is_valid = True
        elif self.website == True:
            is_valid = True
        elif self.newspaper == True:
            is_valid = True
        elif self.tv == True:
            is_valid = True
        elif self.radio == True:
            is_valid = True
        elif self.admission_book == True:
            is_valid = True
        if is_valid==False:
            raise exceptions.ValidationError("not valid")
    # Submet
    @api.constrains("signup_valid")
    def _check_submet(self):
        if self.signup_valid == False:
            raise exceptions.ValidationError("not signup valid")

        # code Serial auto no





    def action_confirm(self,vals):
        self.env['napata.register'].write({

            'name': self.name,
            'first_name': self.first_name,
            'second_name': self.second_name,
            'third_name': self.third_name,
            'forth_name': self.forth_name,
            'nationality': self.nationality.name,
            'type_id': self.idtype,
            'number_ids': self.id_number,
            'phone1': self.phone,
            'phone2': self.phone_2,
            #     parint provider_name
            'provider_name': self.provider,
            'parent': self.parent.name,
            'job': self.job,
            'phone3': self.phone3,
            # fees
            'accept_type': self.preType2,
            'total_fees': self.fees,
            # study  information
            'cource': self.course,
            'ratio': self.ratio,
            'main_desires': self.main.name,
            # 'athoer_desires':self.name,
            'siting_number': self.sit_number,
            'form_number': self.form,
            'pay_date': self.pay_date,
            'admission_ids': self.admission_ids,

        })
        res = super(napataPresentation, self).write(vals)
        return res
Пример #30
0
class MailComposer(models.TransientModel):
    """ Generic message composition wizard. You may inherit from this wizard
        at model and view levels to provide specific features.

        The behavior of the wizard depends on the composition_mode field:
        - 'comment': post on a record. The wizard is pre-populated via ``get_record_data``
        - 'mass_mail': wizard in mass mailing mode where the mail details can
            contain template placeholders that will be merged with actual data
            before being sent to each recipient.
    """
    _name = 'mail.compose.message'
    _inherit = 'mail.composer.mixin'
    _description = 'Email composition wizard'
    _log_access = True
    _batch_size = 500

    @api.model
    def default_get(self, fields):
        """ Handle composition mode. Some details about context keys:
            - comment: default mode, model and ID of a record the user comments
                - default_model or active_model
                - default_res_id or active_id
            - mass_mail: model and IDs of records the user mass-mails
                - active_ids: record IDs
                - default_model or active_model
        """
        # backward compatibility of context before addition of
        # email_layout_xmlid field: to remove in 15.1+
        if self._context.get('custom_layout') and 'default_email_layout_xmlid' not in self._context:
            self = self.with_context(default_email_layout_xmlid=self._context['custom_layout'])

        result = super(MailComposer, self).default_get(fields)

        # author
        missing_author = 'author_id' in fields and 'author_id' not in result
        missing_email_from = 'email_from' in fields and 'email_from' not in result
        if missing_author or missing_email_from:
            author_id, email_from = self.env['mail.thread']._message_compute_author(result.get('author_id'), result.get('email_from'), raise_exception=False)
            if missing_email_from:
                result['email_from'] = email_from
            if missing_author:
                result['author_id'] = author_id

        if 'model' in fields and 'model' not in result:
            result['model'] = self._context.get('active_model')
        if 'res_id' in fields and 'res_id' not in result:
            result['res_id'] = self._context.get('active_id')
        if 'reply_to_mode' in fields and 'reply_to_mode' not in result and result.get('model'):
            # doesn't support threading
            if result['model'] not in self.env or not hasattr(self.env[result['model']], 'message_post'):
                result['reply_to_mode'] = 'new'

        if 'active_domain' in self._context:  # not context.get() because we want to keep global [] domains
            result['active_domain'] = '%s' % self._context.get('active_domain')
        if result.get('composition_mode') == 'comment' and (set(fields) & set(['model', 'res_id', 'partner_ids', 'record_name', 'subject'])):
            result.update(self.get_record_data(result))

        # when being in new mode, create_uid is not granted -> ACLs issue may arise
        if 'create_uid' in fields and 'create_uid' not in result:
            result['create_uid'] = self.env.uid

        filtered_result = dict((fname, result[fname]) for fname in result if fname in fields)
        return filtered_result

    # content
    subject = fields.Char('Subject', compute=False)
    body = fields.Html('Contents', render_engine='qweb', compute=False, default='', sanitize_style=True)
    parent_id = fields.Many2one(
        'mail.message', 'Parent Message', ondelete='set null',
        help="Initial thread message.")
    template_id = fields.Many2one('mail.template', 'Use template', domain="[('model', '=', model)]")
    attachment_ids = fields.Many2many(
        'ir.attachment', 'mail_compose_message_ir_attachments_rel',
        'wizard_id', 'attachment_id', 'Attachments')
    email_layout_xmlid = fields.Char('Email Notification Layout', copy=False)
    email_add_signature = fields.Boolean(default=True)
    # origin
    email_from = fields.Char('From', help="Email address of the sender. This field is set when no matching partner is found and replaces the author_id field in the chatter.")
    author_id = fields.Many2one(
        'res.partner', 'Author',
        help="Author of the message. If not set, email_from may hold an email address that did not match any partner.")
    # composition
    composition_mode = fields.Selection(selection=[
        ('comment', 'Post on a document'),
        ('mass_mail', 'Email Mass Mailing'),
        ('mass_post', 'Post on Multiple Documents')], string='Composition mode', default='comment')
    model = fields.Char('Related Document Model')
    res_id = fields.Integer('Related Document ID')
    record_name = fields.Char('Message Record Name', help="Name get of the related document.")
    use_active_domain = fields.Boolean('Use active domain')
    active_domain = fields.Text('Active domain', readonly=True)
    # characteristics
    message_type = fields.Selection([
        ('comment', 'Comment'),
        ('notification', 'System notification')],
        'Type', required=True, default='comment',
        help="Message type: email for email message, notification for system "
             "message, comment for other messages such as user replies")
    subtype_id = fields.Many2one(
        'mail.message.subtype', 'Subtype', ondelete='set null',
        default=lambda self: self.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment'))
    mail_activity_type_id = fields.Many2one('mail.activity.type', 'Mail Activity Type', ondelete='set null')
    # destination
    reply_to = fields.Char('Reply To', help='Reply email address. Setting the reply_to bypasses the automatic thread creation.')
    reply_to_force_new = fields.Boolean(
        string='Considers answers as new thread',
        help='Manage answers as new incoming emails instead of replies going to the same thread.')
    reply_to_mode = fields.Selection([
        ('update', 'Store email and replies in the chatter of each record'),
        ('new', 'Collect replies on a specific email address')],
        string='Replies', compute='_compute_reply_to_mode', inverse='_inverse_reply_to_mode',
        help="Original Discussion: Answers go in the original document discussion thread. \n Another Email Address: Answers go to the email address mentioned in the tracking message-id instead of original document discussion thread. \n This has an impact on the generated message-id.")
    is_log = fields.Boolean('Log an Internal Note',
                            help='Whether the message is an internal note (comment mode only)')
    partner_ids = fields.Many2many(
        'res.partner', 'mail_compose_message_res_partner_rel',
        'wizard_id', 'partner_id', 'Additional Contacts',
        domain=[('type', '!=', 'private')])
    # mass mode options
    notify = fields.Boolean('Notify followers', help='Notify followers of the document (mass post only)')
    auto_delete = fields.Boolean('Delete Emails',
        help='This option permanently removes any track of email after it\'s been sent, including from the Technical menu in the Settings, in order to preserve storage space of your Odoo database.')
    auto_delete_message = fields.Boolean('Delete Message Copy', help='Do not keep a copy of the email in the document communication history (mass mailing only)')
    mail_server_id = fields.Many2one('ir.mail_server', 'Outgoing mail server')

    @api.depends('reply_to_force_new')
    def _compute_reply_to_mode(self):
        for composer in self:
            composer.reply_to_mode = 'new' if composer.reply_to_force_new else 'update'

    def _inverse_reply_to_mode(self):
        for composer in self:
            composer.reply_to_force_new = composer.reply_to_mode == 'new'

    # Overrides of mail.render.mixin
    @api.depends('model')
    def _compute_render_model(self):
        for composer in self:
            composer.render_model = composer.model

    # Onchanges

    @api.onchange('template_id')
    def _onchange_template_id_wrapper(self):
        self.ensure_one()
        values = self._onchange_template_id(self.template_id.id, self.composition_mode, self.model, self.res_id)['value']
        for fname, value in values.items():
            setattr(self, fname, value)

    def _compute_can_edit_body(self):
        """Can edit the body if we are not in "mass_mail" mode because the template is
        rendered before it's modified.
        """
        non_mass_mail = self.filtered(lambda m: m.composition_mode != 'mass_mail')
        non_mass_mail.can_edit_body = True
        super(MailComposer, self - non_mass_mail)._compute_can_edit_body()

    @api.model
    def get_record_data(self, values):
        """ Returns a defaults-like dict with initial values for the composition
        wizard when sending an email related a previous email (parent_id) or
        a document (model, res_id). This is based on previously computed default
        values. """
        result, subject = {}, False
        if values.get('parent_id'):
            parent = self.env['mail.message'].browse(values.get('parent_id'))
            result['record_name'] = parent.record_name
            subject = tools.ustr(parent.subject or parent.record_name or '')
            if not values.get('model'):
                result['model'] = parent.model
            if not values.get('res_id'):
                result['res_id'] = parent.res_id
            partner_ids = values.get('partner_ids', list()) + parent.partner_ids.ids
            result['partner_ids'] = partner_ids
        elif values.get('model') and values.get('res_id'):
            doc_name_get = self.env[values.get('model')].browse(values.get('res_id')).name_get()
            result['record_name'] = doc_name_get and doc_name_get[0][1] or ''
            subject = tools.ustr(result['record_name'])

        re_prefix = _('Re:')
        if subject and not (subject.startswith('Re:') or subject.startswith(re_prefix)):
            subject = "%s %s" % (re_prefix, subject)
        result['subject'] = subject

        return result

    # ------------------------------------------------------------
    # CRUD / ORM
    # ------------------------------------------------------------

    @api.autovacuum
    def _gc_lost_attachments(self):
        """ Garbage collect lost mail attachments. Those are attachments
            - linked to res_model 'mail.compose.message', the composer wizard
            - with res_id 0, because they were created outside of an existing
                wizard (typically user input through Chatter or reports
                created on-the-fly by the templates)
            - unused since at least one day (create_date and write_date)
        """
        limit_date = fields.Datetime.subtract(fields.Datetime.now(), days=1)
        self.env['ir.attachment'].search([
            ('res_model', '=', self._name),
            ('res_id', '=', 0),
            ('create_date', '<', limit_date),
            ('write_date', '<', limit_date)]
        ).unlink()

    # ------------------------------------------------------------
    # ACTIONS
    # ------------------------------------------------------------

    def action_send_mail(self):
        """ Used for action button that do not accept arguments. """
        self._action_send_mail(auto_commit=False)
        return {'type': 'ir.actions.act_window_close'}

    def _action_send_mail(self, auto_commit=False):
        """ Process the wizard content and proceed with sending the related
            email(s), rendering any template patterns on the fly if needed. """
        # Several custom layouts make use of the model description at rendering, e.g. in the
        # 'View <document>' button. Some models are used for different business concepts, such as
        # 'purchase.order' which is used for a RFQ and and PO. To avoid confusion, we must use a
        # different wording depending on the state of the object.
        # Therefore, we can set the description in the context from the beginning to avoid falling
        # back on the regular display_name retrieved in ``_notify_by_email_prepare_rendering_context()``.
        model_description = self._context.get('model_description')

        for wizard in self:
            # Duplicate attachments linked to the email.template.
            # Indeed, basic mail.compose.message wizard duplicates attachments in mass
            # mailing mode. But in 'single post' mode, attachments of an email template
            # also have to be duplicated to avoid changing their ownership.
            if wizard.attachment_ids and wizard.composition_mode != 'mass_mail' and wizard.template_id:
                new_attachment_ids = []
                for attachment in wizard.attachment_ids:
                    if attachment in wizard.template_id.attachment_ids:
                        new_attachment_ids.append(attachment.copy({'res_model': 'mail.compose.message', 'res_id': wizard.id}).id)
                    else:
                        new_attachment_ids.append(attachment.id)
                new_attachment_ids.reverse()
                wizard.write({'attachment_ids': [Command.set(new_attachment_ids)]})

            # Mass Mailing
            mass_mode = wizard.composition_mode in ('mass_mail', 'mass_post')

            ActiveModel = self.env[wizard.model] if wizard.model and hasattr(self.env[wizard.model], 'message_post') else self.env['mail.thread']
            if wizard.composition_mode == 'mass_post':
                # do not send emails directly but use the queue instead
                # add context key to avoid subscribing the author
                ActiveModel = ActiveModel.with_context(mail_notify_force_send=False, mail_create_nosubscribe=True)
            # wizard works in batch mode: [res_id] or active_ids or active_domain
            if mass_mode and wizard.use_active_domain and wizard.model:
                res_ids = self.env[wizard.model].search(ast.literal_eval(wizard.active_domain)).ids
            elif mass_mode and wizard.model and self._context.get('active_ids'):
                res_ids = self._context['active_ids']
            else:
                res_ids = [wizard.res_id]

            batch_size = int(self.env['ir.config_parameter'].sudo().get_param('mail.batch_size')) or self._batch_size
            sliced_res_ids = [res_ids[i:i + batch_size] for i in range(0, len(res_ids), batch_size)]

            if wizard.composition_mode == 'mass_mail' or wizard.is_log or (wizard.composition_mode == 'mass_post' and not wizard.notify):  # log a note: subtype is False
                subtype_id = False
            elif wizard.subtype_id:
                subtype_id = wizard.subtype_id.id
            else:
                subtype_id = self.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment')

            for res_ids in sliced_res_ids:
                # mass mail mode: mail are sudo-ed, as when going through get_mail_values
                # standard access rights on related records will be checked when browsing them
                # to compute mail values. If people have access to the records they have rights
                # to create lots of emails in sudo as it is consdiered as a technical model.
                batch_mails_sudo = self.env['mail.mail'].sudo()
                all_mail_values = wizard.get_mail_values(res_ids)
                for res_id, mail_values in all_mail_values.items():
                    if wizard.composition_mode == 'mass_mail':
                        batch_mails_sudo |= self.env['mail.mail'].sudo().create(mail_values)
                    else:
                        post_params = dict(
                            message_type=wizard.message_type,
                            subtype_id=subtype_id,
                            email_layout_xmlid=wizard.email_layout_xmlid,
                            email_add_signature=not bool(wizard.template_id) and wizard.email_add_signature,
                            mail_auto_delete=wizard.template_id.auto_delete if wizard.template_id else self._context.get('mail_auto_delete', True),
                            model_description=model_description)
                        post_params.update(mail_values)
                        if ActiveModel._name == 'mail.thread':
                            if wizard.model:
                                post_params['model'] = wizard.model
                                post_params['res_id'] = res_id
                            if not ActiveModel.message_notify(**post_params):
                                # if message_notify returns an empty record set, no recipients where found.
                                raise UserError(_("No recipient found."))
                        else:
                            ActiveModel.browse(res_id).message_post(**post_params)

                if wizard.composition_mode == 'mass_mail':
                    batch_mails_sudo.send(auto_commit=auto_commit)

    def action_save_as_template(self):
        """ hit save as template button: current form value will be a new
            template attached to the current document. """
        for record in self:
            model = self.env['ir.model']._get(record.model or 'mail.message')
            model_name = model.name or ''
            template_name = "%s: %s" % (model_name, tools.ustr(record.subject))
            values = {
                'name': template_name,
                'subject': record.subject or False,
                'body_html': record.body or False,
                'model_id': model.id or False,
            }
            template = self.env['mail.template'].create(values)

            if record.attachment_ids:
                attachments = self.env['ir.attachment'].sudo().browse(record.attachment_ids.ids).filtered(
                    lambda a: a.res_model == 'mail.compose.message' and a.create_uid.id == self._uid)
                if attachments:
                    attachments.write({'res_model': template._name, 'res_id': template.id})
                template.attachment_ids |= record.attachment_ids

            # generate the saved template
            record.write({'template_id': template.id})
            record._onchange_template_id_wrapper()
            return _reopen(self, record.id, record.model, context=self._context)

    # ------------------------------------------------------------
    # RENDERING / VALUES GENERATION
    # ------------------------------------------------------------

    def get_mail_values(self, res_ids):
        """Generate the values that will be used by send_mail to create mail_messages
        or mail_mails. """
        self.ensure_one()
        results = dict.fromkeys(res_ids, False)
        rendered_values = {}
        mass_mail_mode = self.composition_mode == 'mass_mail'

        # render all template-based value at once
        if mass_mail_mode and self.model:
            rendered_values = self.render_message(res_ids)
        # compute alias-based reply-to in batch
        reply_to_value = dict.fromkeys(res_ids, None)
        if mass_mail_mode and not self.reply_to_force_new:
            records = self.env[self.model].browse(res_ids)
            reply_to_value = records._notify_get_reply_to(default=self.email_from)

        for res_id in res_ids:
            # static wizard (mail.message) values
            mail_values = {
                'subject': self.subject,
                'body': self.body or '',
                'parent_id': self.parent_id and self.parent_id.id,
                'partner_ids': [partner.id for partner in self.partner_ids],
                'attachment_ids': [attach.id for attach in self.attachment_ids],
                'author_id': self.author_id.id,
                'email_from': self.email_from,
                'record_name': self.record_name,
                'reply_to_force_new': self.reply_to_force_new,
                'mail_server_id': self.mail_server_id.id,
                'mail_activity_type_id': self.mail_activity_type_id.id,
            }

            # mass mailing: rendering override wizard static values
            if mass_mail_mode and self.model:
                record = self.env[self.model].browse(res_id)
                mail_values['headers'] = repr(record._notify_by_email_get_headers())
                # keep a copy unless specifically requested, reset record name (avoid browsing records)
                mail_values.update(is_notification=not self.auto_delete_message, model=self.model, res_id=res_id, record_name=False)
                # auto deletion of mail_mail
                if self.auto_delete or self.template_id.auto_delete:
                    mail_values['auto_delete'] = True
                # rendered values using template
                email_dict = rendered_values[res_id]
                mail_values['partner_ids'] += email_dict.pop('partner_ids', [])
                mail_values.update(email_dict)
                if not self.reply_to_force_new:
                    mail_values.pop('reply_to')
                    if reply_to_value.get(res_id):
                        mail_values['reply_to'] = reply_to_value[res_id]
                if self.reply_to_force_new and not mail_values.get('reply_to'):
                    mail_values['reply_to'] = mail_values['email_from']
                # mail_mail values: body -> body_html, partner_ids -> recipient_ids
                mail_values['body_html'] = mail_values.get('body', '')
                mail_values['recipient_ids'] = [Command.link(id) for id in mail_values.pop('partner_ids', [])]

                # process attachments: should not be encoded before being processed by message_post / mail_mail create
                mail_values['attachments'] = [(name, base64.b64decode(enc_cont)) for name, enc_cont in email_dict.pop('attachments', list())]
                attachment_ids = []
                for attach_id in mail_values.pop('attachment_ids'):
                    new_attach_id = self.env['ir.attachment'].browse(attach_id).copy({'res_model': self._name, 'res_id': self.id})
                    attachment_ids.append(new_attach_id.id)
                attachment_ids.reverse()
                mail_values['attachment_ids'] = self.env['mail.thread'].with_context(attached_to=record)._message_post_process_attachments(
                    mail_values.pop('attachments', []),
                    attachment_ids,
                    {'model': 'mail.message', 'res_id': 0}
                )['attachment_ids']

            results[res_id] = mail_values

        results = self._process_state(results)
        return results

    def _process_recipient_values(self, mail_values_dict):
        # Preprocess res.partners to batch-fetch from db if recipient_ids is present
        # it means they are partners (the only object to fill get_default_recipient this way)
        recipient_pids = [
            recipient_command[1]
            for mail_values in mail_values_dict.values()
            # recipient_ids is a list of x2m command tuples at this point
            for recipient_command in mail_values.get('recipient_ids') or []
            if recipient_command[1]
        ]
        recipient_emails = {
            p.id: p.email
            for p in self.env['res.partner'].browse(set(recipient_pids))
        } if recipient_pids else {}

        recipients_info = {}
        for record_id, mail_values in mail_values_dict.items():
            mail_to = []
            if mail_values.get('email_to'):
                mail_to += email_re.findall(mail_values['email_to'])
                # if unrecognized email in email_to -> keep it as used for further processing
                if not mail_to:
                    mail_to.append(mail_values['email_to'])
            # add email from recipients (res.partner)
            mail_to += [
                recipient_emails[recipient_command[1]]
                for recipient_command in mail_values.get('recipient_ids') or []
                if recipient_command[1]
            ]
            mail_to = list(set(mail_to))
            recipients_info[record_id] = {
                'mail_to': mail_to,
                'mail_to_normalized': [
                    tools.email_normalize(mail)
                    for mail in mail_to
                    if tools.email_normalize(mail)
                ]
            }
        return recipients_info

    def _process_state(self, mail_values_dict):
        recipients_info = self._process_recipient_values(mail_values_dict)
        blacklist_ids = self._get_blacklist_record_ids(mail_values_dict)
        optout_emails = self._get_optout_emails(mail_values_dict)
        done_emails = self._get_done_emails(mail_values_dict)

        for record_id, mail_values in mail_values_dict.items():
            recipients = recipients_info[record_id]
            # when having more than 1 recipient: we cannot really decide when a single
            # email is linked to several to -> skip that part. Mass mailing should
            # anyway always have a single recipient per record as this is default behavior.
            if len(recipients['mail_to']) > 1:
                continue

            mail_to = recipients['mail_to'][0] if recipients['mail_to'] else ''
            mail_to_normalized = recipients['mail_to_normalized'][0] if recipients['mail_to_normalized'] else ''

            # prevent sending to blocked addresses that were included by mistake
            # blacklisted or optout or duplicate -> cancel
            if record_id in blacklist_ids:
                mail_values['state'] = 'cancel'
                mail_values['failure_type'] = 'mail_bl'
                # Do not post the mail into the recipient's chatter
                mail_values['is_notification'] = False
            elif optout_emails and mail_to in optout_emails:
                mail_values['state'] = 'cancel'
                mail_values['failure_type'] = 'mail_optout'
            elif done_emails and mail_to in done_emails:
                mail_values['state'] = 'cancel'
                mail_values['failure_type'] = 'mail_dup'
            # void of falsy values -> error
            elif not mail_to:
                mail_values['state'] = 'cancel'
                mail_values['failure_type'] = 'mail_email_missing'
            elif not mail_to_normalized or not email_re.findall(mail_to):
                mail_values['state'] = 'cancel'
                mail_values['failure_type'] = 'mail_email_invalid'
            elif done_emails is not None:
                done_emails.append(mail_to)

        return mail_values_dict

    def _get_blacklist_record_ids(self, mail_values_dict):
        blacklisted_rec_ids = set()
        if self.composition_mode == 'mass_mail' and issubclass(type(self.env[self.model]), self.pool['mail.thread.blacklist']):
            self.env['mail.blacklist'].flush(['email'])
            self._cr.execute("SELECT email FROM mail_blacklist WHERE active=true")
            blacklist = {x[0] for x in self._cr.fetchall()}
            if blacklist:
                targets = self.env[self.model].browse(mail_values_dict.keys()).read(['email_normalized'])
                # First extract email from recipient before comparing with blacklist
                blacklisted_rec_ids.update(target['id'] for target in targets
                                           if target['email_normalized'] in blacklist)
        return blacklisted_rec_ids

    def _get_done_emails(self, mail_values_dict):
        return []

    def _get_optout_emails(self, mail_values_dict):
        return []

    def _onchange_template_id(self, template_id, composition_mode, model, res_id):
        """ - mass_mailing: we cannot render, so return the template values
            - normal mode: return rendered values
            /!\ for x2many field, this onchange return command instead of ids
        """
        if template_id and composition_mode == 'mass_mail':
            template = self.env['mail.template'].browse(template_id)
            fields = ['subject', 'body_html', 'email_from', 'reply_to', 'mail_server_id']
            values = dict((field, getattr(template, field)) for field in fields if getattr(template, field))
            if template.attachment_ids:
                values['attachment_ids'] = [att.id for att in template.attachment_ids]
            if template.mail_server_id:
                values['mail_server_id'] = template.mail_server_id.id
        elif template_id:
            values = self.generate_email_for_composer(
                template_id, [res_id],
                ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc',  'reply_to', 'attachment_ids', 'mail_server_id']
            )[res_id]
            # transform attachments into attachment_ids; not attached to the document because this will
            # be done further in the posting process, allowing to clean database if email not send
            attachment_ids = []
            Attachment = self.env['ir.attachment']
            for attach_fname, attach_datas in values.pop('attachments', []):
                data_attach = {
                    'name': attach_fname,
                    'datas': attach_datas,
                    'res_model': 'mail.compose.message',
                    'res_id': 0,
                    'type': 'binary',  # override default_type from context, possibly meant for another model!
                }
                attachment_ids.append(Attachment.create(data_attach).id)
            if values.get('attachment_ids', []) or attachment_ids:
                values['attachment_ids'] = [Command.set(values.get('attachment_ids', []) + attachment_ids)]
        else:
            default_values = self.with_context(default_composition_mode=composition_mode, default_model=model, default_res_id=res_id).default_get(['composition_mode', 'model', 'res_id', 'parent_id', 'partner_ids', 'subject', 'body', 'email_from', 'reply_to', 'attachment_ids', 'mail_server_id'])
            values = dict((key, default_values[key]) for key in ['subject', 'body', 'partner_ids', 'email_from', 'reply_to', 'attachment_ids', 'mail_server_id'] if key in default_values)

        if values.get('body_html'):
            values['body'] = values.pop('body_html')

        # This onchange should return command instead of ids for x2many field.
        values = self._convert_to_write(values)

        return {'value': values}

    def render_message(self, res_ids):
        """Generate template-based values of wizard, for the document records given
        by res_ids. This method is meant to be inherited by email_template that
        will produce a more complete dictionary, using qweb templates.

        Each template is generated for all res_ids, allowing to parse the template
        once, and render it multiple times. This is useful for mass mailing where
        template rendering represent a significant part of the process.

        Default recipients are also computed, based on mail_thread method
        _message_get_default_recipients. This allows to ensure a mass mailing has
        always some recipients specified.

        :param browse wizard: current mail.compose.message browse record
        :param list res_ids: list of record ids

        :return dict results: for each res_id, the generated template values for
                              subject, body, email_from and reply_to
        """
        self.ensure_one()
        multi_mode = True
        if isinstance(res_ids, int):
            multi_mode = False
            res_ids = [res_ids]

        subjects = self._render_field('subject', res_ids, options={"render_safe": True})
        # We want to preserve comments in emails so as to keep mso conditionals
        bodies = self._render_field('body', res_ids, post_process=True, options={'preserve_comments': self.composition_mode == 'mass_mail'})
        emails_from = self._render_field('email_from', res_ids)
        replies_to = self._render_field('reply_to', res_ids)
        default_recipients = {}
        if not self.partner_ids:
            records = self.env[self.model].browse(res_ids).sudo()
            default_recipients = records._message_get_default_recipients()

        results = dict.fromkeys(res_ids, False)
        for res_id in res_ids:
            results[res_id] = {
                'subject': subjects[res_id],
                'body': bodies[res_id],
                'email_from': emails_from[res_id],
                'reply_to': replies_to[res_id],
            }
            results[res_id].update(default_recipients.get(res_id, dict()))

        # generate template-based values
        if self.template_id:
            template_values = self.generate_email_for_composer(
                self.template_id.id, res_ids,
                ['email_to', 'partner_to', 'email_cc', 'attachment_ids', 'mail_server_id'])
        else:
            template_values = {}

        for res_id in res_ids:
            if template_values.get(res_id):
                # recipients are managed by the template
                results[res_id].pop('partner_ids', None)
                results[res_id].pop('email_to', None)
                results[res_id].pop('email_cc', None)
                # remove attachments from template values as they should not be rendered
                template_values[res_id].pop('attachment_ids', None)
            else:
                template_values[res_id] = dict()
            # update template values by composer values
            template_values[res_id].update(results[res_id])

        return multi_mode and template_values or template_values[res_ids[0]]

    @api.model
    def generate_email_for_composer(self, template_id, res_ids, fields):
        """ Call email_template.generate_email(), get fields relevant for
            mail.compose.message, transform email_cc and email_to into partner_ids """
        multi_mode = True
        if isinstance(res_ids, int):
            multi_mode = False
            res_ids = [res_ids]

        returned_fields = fields + ['partner_ids', 'attachments']
        values = dict.fromkeys(res_ids, False)

        template_values = self.env['mail.template'].with_context(tpl_partners_only=True).browse(template_id).generate_email(res_ids, fields)
        for res_id in res_ids:
            res_id_values = dict((field, template_values[res_id][field]) for field in returned_fields if template_values[res_id].get(field))
            res_id_values['body'] = res_id_values.pop('body_html', '')
            values[res_id] = res_id_values

        return multi_mode and values or values[res_ids[0]]