class SetuInventoryAgeReport(models.TransientModel): _name = 'setu.inventory.age.report' _description = """ Inventory age report is useful to determine how oldest your inventories are. It gives you detailed analysis products wise about oldest inventories at company level. """ stock_file_data = fields.Binary('Inventory Age Report File') company_ids = fields.Many2many("res.company", string="Companies") product_category_ids = fields.Many2many("product.category", string="Product Categories") product_ids = fields.Many2many("product.product", string="Products") @api.onchange('product_category_ids') def onchange_product_category_id(self): if self.product_category_ids: return { 'domain': { 'product_ids': [('categ_id', 'child_of', self.product_category_ids.ids)] } } def get_file_name(self): filename = "inventory_age_report.xlsx" return filename def create_excel_workbook(self, file_pointer): workbook = xlsxwriter.Workbook(file_pointer) return workbook def create_excel_worksheet(self, workbook, sheet_name): worksheet = workbook.add_worksheet(sheet_name) worksheet.set_default_row(22) # worksheet.set_border() return worksheet def set_column_width(self, workbook, worksheet): worksheet.set_column(0, 1, 25) worksheet.set_column(2, 8, 14) def set_format(self, workbook, wb_format): wb_new_format = workbook.add_format(wb_format) wb_new_format.set_border() return wb_new_format def set_report_title(self, workbook, worksheet): wb_format = self.set_format(workbook, setu_excel_formatter.FONT_TITLE_CENTER) worksheet.merge_range(0, 0, 1, 8, "Inventory Age Report", wb_format) wb_format_left = self.set_format( workbook, setu_excel_formatter.FONT_MEDIUM_BOLD_LEFT) wb_format_center = self.set_format( workbook, setu_excel_formatter.FONT_MEDIUM_BOLD_CENTER) def get_inventory_age_report_data(self): """ :return: """ category_ids = company_ids = {} if self.product_category_ids: categories = self.env['product.category'].search([ ('id', 'child_of', self.product_category_ids.ids) ]) category_ids = set(categories.ids) or {} products = self.product_ids and set(self.product_ids.ids) or {} if self.company_ids: companies = self.env['res.company'].search([ ('id', 'child_of', self.company_ids.ids) ]) company_ids = set(companies.ids) or {} else: company_ids = set(self.env.user.company_ids.ids) or {} # warehouses = self.warehouse_ids and set(self.warehouse_ids.ids) or {} # get_products_overstock_data(company_ids, product_ids, category_ids, warehouse_ids, start_date, end_date, advance_stock_days) query = """ Select * from inventory_stock_age_report('%s','%s','%s') """ % (company_ids, products, category_ids) print(query) self._cr.execute(query) stock_data = self._cr.dictfetchall() return stock_data def prepare_data_to_write(self, stock_data={}): """ :param stock_data: :return: """ company_wise_data = {} for data in stock_data: key = (data.get('company_id'), data.get('company_name')) if not company_wise_data.get(key, False): company_wise_data[key] = {data.get('product_id'): data} else: company_wise_data.get(key).update( {data.get('product_id'): data}) return company_wise_data def download_report(self): file_name = self.get_file_name() file_pointer = BytesIO() stock_data = self.get_inventory_age_report_data() company_wise_analysis_data = self.prepare_data_to_write( stock_data=stock_data) if not company_wise_analysis_data: return False workbook = self.create_excel_workbook(file_pointer) for stock_data_key, stock_data_value in company_wise_analysis_data.items( ): sheet_name = stock_data_key[1] wb_worksheet = self.create_excel_worksheet(workbook, sheet_name) row_no = 3 self.write_report_data_header(workbook, wb_worksheet, row_no) for age_data_key, age_data_value in stock_data_value.items(): row_no = row_no + 1 self.write_data_to_worksheet(workbook, wb_worksheet, age_data_value, row=row_no) # workbook.save(file_name) workbook.close() file_pointer.seek(0) file_data = base64.encodestring(file_pointer.read()) self.write({'stock_file_data': file_data}) file_pointer.close() return { 'name': 'Inventory Age Report', 'type': 'ir.actions.act_url', 'url': '/web/binary/download_document?model=setu.inventory.age.report&field=stock_file_data&id=%s&filename=%s' % (self.id, file_name), 'target': 'self', } def download_report_in_listview(self): stock_data = self.get_inventory_age_report_data() print(stock_data) for fsn_data_value in stock_data: fsn_data_value['wizard_id'] = self.id self.create_data(fsn_data_value) graph_view_id = self.env.ref( 'setu_advance_inventory_reports.setu_inventory_age_bi_report_graph' ).id tree_view_id = self.env.ref( 'setu_advance_inventory_reports.setu_inventory_age_bi_report_tree' ).id is_graph_first = self.env.context.get('graph_report', False) report_display_views = [] viewmode = '' if is_graph_first: report_display_views.append((graph_view_id, 'graph')) report_display_views.append((tree_view_id, 'tree')) viewmode = "graph,tree" else: report_display_views.append((tree_view_id, 'tree')) report_display_views.append((graph_view_id, 'graph')) viewmode = "tree,graph" return { 'name': _('Inventory Age Analysis'), 'domain': [('wizard_id', '=', self.id)], 'res_model': 'setu.inventory.age.bi.report', 'view_mode': viewmode, 'type': 'ir.actions.act_window', 'views': report_display_views, } def create_data(self, data): del data['company_name'] del data['product_name'] del data['category_name'] return self.env['setu.inventory.age.bi.report'].create(data) def write_report_data_header(self, workbook, worksheet, row): self.set_report_title(workbook, worksheet) self.set_column_width(workbook, worksheet) wb_format = self.set_format( workbook, setu_excel_formatter.FONT_MEDIUM_BOLD_CENTER) wb_format.set_text_wrap() odd_normal_right_format = self.set_format( workbook, setu_excel_formatter.ODD_FONT_MEDIUM_BOLD_RIGHT) even_normal_right_format = self.set_format( workbook, setu_excel_formatter.EVEN_FONT_MEDIUM_BOLD_RIGHT) normal_left_format = self.set_format( workbook, setu_excel_formatter.FONT_MEDIUM_BOLD_LEFT) worksheet.write(row, 0, 'Product Name', normal_left_format) worksheet.write(row, 1, 'Category', normal_left_format) worksheet.write(row, 2, 'Current Stock', odd_normal_right_format) worksheet.write(row, 3, 'Stock Value', even_normal_right_format) worksheet.write(row, 4, 'Stock Qty (%)', odd_normal_right_format) worksheet.write(row, 5, 'Stock Value (%)', even_normal_right_format) worksheet.write(row, 6, "Oldest Stock Age", odd_normal_right_format) worksheet.write(row, 7, "Oldest Qty", even_normal_right_format) worksheet.write(row, 8, "Oldest Stock Value", odd_normal_right_format) return worksheet def write_data_to_worksheet(self, workbook, worksheet, data, row): # Start from the first cell. Rows and # columns are zero indexed. odd_normal_right_format = self.set_format( workbook, setu_excel_formatter.ODD_FONT_MEDIUM_NORMAL_RIGHT) even_normal_right_format = self.set_format( workbook, setu_excel_formatter.EVEN_FONT_MEDIUM_NORMAL_RIGHT) odoo_normal_center_format = self.set_format( workbook, setu_excel_formatter.ODD_FONT_MEDIUM_NORMAL_CENTER) # odd_normal_left_format = self.set_format(workbook, setu_excel_formatter.ODD_FONT_MEDIUM_NORMAL_LEFT) normal_left_format = self.set_format( workbook, setu_excel_formatter.FONT_MEDIUM_NORMAL_LEFT) worksheet.write(row, 0, data.get('product_name', ''), normal_left_format) worksheet.write(row, 1, data.get('category_name', ''), normal_left_format) worksheet.write(row, 2, data.get('current_stock', ''), odd_normal_right_format) worksheet.write(row, 3, data.get('current_stock_value', ''), even_normal_right_format) worksheet.write(row, 4, data.get('stock_qty_ratio', ''), odd_normal_right_format) worksheet.write(row, 5, data.get('stock_value_ratio', ''), even_normal_right_format) worksheet.write(row, 6, data.get('days_old', ''), odd_normal_right_format) worksheet.write(row, 7, data.get('oldest_stock_qty', ''), even_normal_right_format) worksheet.write(row, 8, data.get('oldest_stock_value', ''), odd_normal_right_format) return worksheet
class ImportItemWizard(models.TransientModel): _name = "import.item.wizard" _description = "Import Items" file_data = fields.Binary(string='Import File') filename = fields.Char(string='File Name') @api.multi def action_import(self): try: if not self.file_data: raise Warning("Silahkan upload file.") wb = open_workbook( file_contents=base64.decodestring(self.file_data)) values = [] for s in wb.sheets(): for row in range(s.nrows): col_value = [] for col in range(s.ncols): value = (s.cell(row, col).value) col_value.append(value) values.append(col_value) item_obj = self.env['master.item'] item_lines_obj = self.env['master.item.lines'] component_obj = self.env['master.component'] row = 1 for data in values: # posisi : name | start_working_date | component_name | working_day | percentage if row != 1: item_name = data[0] if not item_name: raise UserError(_("Kolom name ada yang kosong")) start_working_date = data[1] if not start_working_date: raise UserError( _("Kolom start_working_date ada yang kosong")) component_name = data[2] if not component_name: raise UserError( _("Kolom component_name ada yang kosong")) working_day = data[3] if not working_day: raise UserError(_("Kolom working_day ada yang kosong")) percentage = data[4] if not percentage: raise UserError(_("Kolom percentage ada yang kosong")) start_working_date = xlrd.xldate_as_tuple( start_working_date, wb.datemode) start_working_date = datetime(*start_working_date) start_working_date = start_working_date.strftime( "%Y-%m-%d") #start_working_date = self.make_date_valid(start_working_date) # cek di master komponen component_exist = component_obj.search( [('name', '=', component_name)], limit=1) if component_exist: component_id = component_exist.id else: component = component_obj.create({ 'name': component_name, 'day': int(working_day) }) component_id = component.id # cek di master item item_exist = item_obj.search([('name', '=', item_name)], limit=1) if not item_exist: item = item_obj.create({ 'name': item_name, 'date': start_working_date, 'line_ids': [(0, 0, { 'component_id': component_id, 'percent': float(percentage) })] }) else: item = item_lines_obj.create({ 'item_id': item_exist.id, 'component_id': component_id, 'percent': float(percentage) }) row += 1 except Exception as e: raise Warning(e) views = [ (self.env.ref('toffin_test.item_master_tree_view').id, 'tree'), (self.env.ref('toffin_test.master_item_form_view').id, 'form') ] return { 'name': _('Master Items'), 'domain': [], 'view_type': 'form', 'res_model': 'master.item', 'context': {}, 'view_id': False, 'views': views, 'view_mode': 'tree,form', 'type': 'ir.actions.act_window', } def make_date_valid(self, date_string): date_valid = '%s-%s-%s' % ( date_string[-4:], month_range[date_string[3:6]], date_string[0:2]) return date_valid
class PaymentOrder(models.Model): _name = 'payment.order' _order = 'id desc' @api.depends('line_ids') def _compute_amount_total(self): for item in self: amount_total = 0 for line in item.line_ids: amount_total += line.amount_total item.amount_total = amount_total name = fields.Char(max_length=30, string="Nome", required=True, default='/') company_id = fields.Many2one( 'res.company', string='Company', required=True, ondelete='restrict', default=lambda self: self.env['res.company']._company_default_get( 'account.l10n_br.payment.mode')) type = fields.Selection([('receivable', 'Recebível'), ('payable', 'Pagável')], string="Tipo de Ordem", default='receivable') user_id = fields.Many2one('res.users', string=u'Responsável', required=True) payment_mode_id = fields.Many2one('l10n_br.payment.mode', string='Modo de Pagamento', required=True) journal_id = fields.Many2one('account.journal', string="Diário") src_bank_account_id = fields.Many2one('res.partner.bank', string="Conta Bancária") state = fields.Selection([('draft', 'Rascunho'), ('open', 'Aberto'), ('attention', 'Necessita Atenção'), ('done', 'Finalizado')], string="Situação", compute="_compute_state", store=True) line_ids = fields.One2many('payment.order.line', 'payment_order_id', required=True, string=u'Linhas de Cobrança') currency_id = fields.Many2one('res.currency', string='Moeda') amount_total = fields.Float(string="Total", compute='_compute_amount_total') cnab_file = fields.Binary('CNAB File', readonly=True) file_number = fields.Integer(u'Número sequencial do arquivo', readonly=1) data_emissao_cnab = fields.Datetime('Data de Emissão do CNAB') def _get_next_code(self): sequence_id = self.env['ir.sequence'].sudo().search([ ('code', '=', 'l10n_br_.payment.cnab.sequential'), ('company_id', '=', self.company_id.id) ]) if not sequence_id: sequence_id = self.env['ir.sequence'].sudo().create({ 'name': 'Sequencia para numeração CNAB', 'code': 'l10n_br_.payment.cnab.sequential', 'company_id': self.company_id.id, 'suffix': '.REM', 'padding': 8, }) return sequence_id.next_by_id() @api.multi @api.depends('line_ids.state') def _compute_state(self): for item in self: lines = item.line_ids.filtered(lambda x: x.state != 'cancelled') if all(line.state in ('draft', 'approved') for line in lines): if len(item.line_ids - lines) > 0: item.state = 'done' else: item.state = 'draft' elif all(line.state in ('paid', 'rejected') for line in lines): item.state = 'done' elif any(line.state == 'rejected' for line in lines): item.state = 'attention' else: item.state = 'open' @api.multi def unlink(self): for item in self: item.line_ids.unlink() return super(PaymentOrder, self).unlink()
class import_purchase_order(models.TransientModel): _name = 'import.purchase.order' import_data = fields.Binary(string="File Import") check_import = fields.Boolean('Allow import',default=False) @api.multi def check_import_file(self): for record in self: data = base64.b64decode(record.import_data) wb = open_workbook(file_contents=data) sheet = wb.sheet_by_index(0) data_list = { 'product_not_find': [], 'price_not_sync': [], 'customer_not_find' : [], } for row_no in range(sheet.nrows): if row_no >= 1: line = ( map(lambda row: isinstance(row.value, unicode) and row.value.encode('utf-8') or str(row.value), sheet.row(row_no))) product_id = self.env['product.product'].search([('default_code', '=', line[5])], limit=1) if not product_id: data_list['product_not_find'].append({ 'line': row_no + 1, 'default_code': line[5], }) partner_id = self.env['res.partner'].search([('name', '=', line[4]), ('supplier', '=', True)], limit=1) if not partner_id: data_list['customer_not_find'].append({ 'line': row_no + 1, 'customer_name': line[4], }) return { 'name': 'Import Warning Order', 'type': 'ir.actions.act_window', 'res_model': 'import.warning.order', 'view_type': 'form', 'view_mode': 'form', 'target': 'new', 'context': { 'data': data_list, 'import_data' : record.import_data, 'name_action' : "import.purchase.order", 'name_model' : "import.purchase.order", } } @api.model def default_get(self, fields): res = super(import_purchase_order, self).default_get(fields) if 'import_data' in self._context: res['import_data'] = self._context.get('import_data') return res @api.multi def import_xls(self): for record in self: if record.import_data: data = base64.b64decode(record.import_data) wb = open_workbook(file_contents=data) sheet = wb.sheet_by_index(0) so_chung_tu = False sale_order = False count_order = 0 count_order_line = 0 list_not_import = [] for row_no in range(sheet.nrows): if row_no >= 1: line = (map(lambda row: isinstance(row.value, unicode) and row.value.encode('utf-8') or str(row.value),sheet.row(row_no))) partner_id = self.env['res.partner'].search([('name', '=', line[4]), ('supplier', '=', True)], limit=1) if not partner_id: if record.check_import: self.env['res.partner'].create({ 'name' : line[4], 'customer' : False, 'supplier': True, }) else: raise UserError(_('Please check file first')) product_id = self.env['product.product'].search([('default_code', '=', line[5])], limit=1) if not product_id: if record.check_import: continue else: raise UserError(_('Please check file first')) if int(float(line[11])) != 0: continue if row_no == 1: line = (map(lambda row: isinstance(row.value, unicode) and row.value.encode('utf-8') or str(row.value), sheet.row(row_no))) if line[0]: so_chung_tu = line[1] date_order = datetime.utcfromtimestamp((float(line[0]) - 25569) * 86400.0) date_planned = datetime.utcfromtimestamp((float(line[0]) - 25569) * 86400.0) invoice_date_real = False if line[2]: invoice_date_real = datetime.utcfromtimestamp((float(line[2]) - 25569) * 86400.0) or False invoice_number_real = line[3] or False partner_id = self.env['res.partner'].search([('name','=',line[4]),('supplier','=',True)],limit=1) product_id = self.env['product.product'].search([('default_code','=',line[5])],limit=1) product_uom_quantity = int(float(line[6])) price_discount = float(line[10]) discount = float(line[9]) * 100 sale_order = self.env['purchase.order'].create({ 'partner_id' : partner_id.id, 'notes' : so_chung_tu, 'date_order' : date_order, 'date_planned': date_planned, 'invoice_date_real' : invoice_date_real, 'invoice_number_real' : invoice_number_real, }) count_order += 1 line_data = { 'product_id': product_id.id, 'product_uom': product_id.uom_id.id, 'discount': discount, } count_order_line += 1 purchase_order_line = sale_order.order_line.new(line_data) purchase_order_line.onchange_product_id() purchase_order_line.price_discount = price_discount purchase_order_line.price_unit = price_discount*100/(100-discount) purchase_order_line.product_qty = product_uom_quantity tax = [] if line[8]: number_tax = int(float(line[8])*100) tax = self.env['account.tax'].search([('amount', '=', number_tax),('type_tax_use','=','purchase')], limit=1).ids if not tax: tax = self.env['account.tax'].search([('amount', '=', 0),('type_tax_use','=','purchase')], limit=1).ids if tax: purchase_order_line.taxes_id = [(6, 0, tax)] if int(float(line[8]) * 100) in [0, 5, 10]: purchase_order_line.tax_sub = int(float(line[8]) * 100) else: purchase_order_line.tax_sub = 0 sale_order.order_line += purchase_order_line if row_no > 1 : line = (map(lambda row: isinstance(row.value, unicode) and row.value.encode('utf-8') or str(row.value),sheet.row(row_no))) if line[0]: if line[1] == so_chung_tu: product_id = self.env['product.product'].search([('default_code', '=', line[5])],limit=1) product_uom_quantity = int(float(line[6])) price_discount = float(line[10]) discount = float(line[9]) * 100 line_data = { 'product_id': product_id.id, 'product_uom': product_id.uom_id.id, 'discount': discount, } count_order_line += 1 purchase_order_line = sale_order.order_line.new(line_data) purchase_order_line.onchange_product_id() purchase_order_line.price_discount = price_discount purchase_order_line.price_unit = price_discount * 100 / (100 - discount) purchase_order_line.product_qty = product_uom_quantity tax = [] if line[8]: number_tax = int(float(line[8])*100) tax = self.env['account.tax'].search([('amount', '=', number_tax),('type_tax_use','=','purchase')], limit=1).ids if not tax: tax = self.env['account.tax'].search([('amount', '=', 0),('type_tax_use','=','purchase')], limit=1).ids if tax: purchase_order_line.taxes_id = [(6, 0, tax)] if int(float(line[8])*100) in [0,5,10]: purchase_order_line.tax_sub = int(float(line[8])*100) else: purchase_order_line.tax_sub = 0 sale_order.order_line += purchase_order_line if line[1] != so_chung_tu: so_chung_tu = line[1] date_order = datetime.utcfromtimestamp((float(line[0]) - 25569) * 86400.0) date_planned = datetime.utcfromtimestamp((float(line[0]) - 25569) * 86400.0) invoice_date_real = False if line[2]: invoice_date_real = datetime.utcfromtimestamp((float(line[2]) - 25569) * 86400.0) or False invoice_number_real = line[3] or False partner_id = self.env['res.partner'].search([('name', '=', line[4]),('supplier','=',True)],limit=1) product_id = self.env['product.product'].search([('default_code', '=', line[5])],limit=1) product_uom_quantity = int(float(line[6])) price_discount = float(line[10]) discount = float(line[9]) * 100 sale_order = self.env['purchase.order'].create({ 'partner_id': partner_id.id, 'notes': so_chung_tu, 'date_order' : date_order, 'date_planned' : date_planned, 'invoice_date_real': invoice_date_real, 'invoice_number_real': invoice_number_real, }) count_order += 1 line_data = { 'product_id': product_id.id, 'product_uom': product_id.uom_id.id, 'discount': discount, } count_order_line += 1 purchase_order_line = sale_order.order_line.new(line_data) purchase_order_line.onchange_product_id() purchase_order_line.price_discount = price_discount purchase_order_line.price_unit = price_discount * 100 / (100 - discount) purchase_order_line.product_qty = product_uom_quantity tax = [] if line[8]: number_tax = int(float(line[8])*100) tax = self.env['account.tax'].search([('amount', '=', number_tax),('type_tax_use','=','purchase')], limit=1).ids if not tax: tax = self.env['account.tax'].search([('amount', '=', 0),('type_tax_use','=','purchase')], limit=1).ids if tax: purchase_order_line.taxes_id = [(6, 0, tax)] if int(float(line[8])*100) in [0,5,10]: purchase_order_line.tax_sub = int(float(line[8])*100) else: purchase_order_line.tax_sub = 0 sale_order.order_line += purchase_order_line print count_order print count_order_line print list_not_import # @api.multi # def import_xls(self): # for record in self: # if record.import_data: # data = base64.b64decode(record.import_data) # wb = open_workbook(file_contents=data) # sheet = wb.sheet_by_index(0) # list_customer = [] # for row_no in range(sheet.nrows): # # # TODO import customer # if row_no >= 1: # line = (map(lambda row: isinstance(row.value, unicode) and row.value.encode('utf-8') or str( # row.value), sheet.row(row_no))) # partner_id = self.env['res.partner'].search([('name','=',line[1])]) # if partner_id: # partner_id.write({ # 'street' : line[2], # 'feosco_business_license' : line[4], # 'phone' : line[5], # 'ref' : line[0], # 'supplier': True, # }) # print "----------------------------write" + str(row_no+1) # else: # partner_id.create({ # 'street' : line[2], # 'feosco_business_license' : line[4], # 'phone' : line[5], # 'ref' : line[0], # 'name' : line[1], # 'customer' : False, # 'supplier': True, # }) # print "**************************create" + str(row_no + 1) # TODO update name product # if row_no >= 1: # line = (map(lambda row: isinstance(row.value, unicode) and row.value.encode('utf-8') or str( # row.value), sheet.row(row_no))) # product_id = self.env['product.template'].search([('default_code','=',line[0])]) # group_id = False # if line[11]: # group_id = self.env['product.group'].search([('name','=',line[11])]) # if not group_id: # group_id = self.env['product.group'].create({ # 'name' : line[11] # }) # if not product_id: # raise UserError("not found" + str(row_no+1)) # else: # product_id.name = line[2] # product_id.group_id = group_id.id # print row_no+1 # @api.multi # def import_xls(self): # for record in self: # if record.import_data: # data = base64.b64decode(record.import_data) # wb = open_workbook(file_contents=data) # sheet = wb.sheet_by_index(0) # list_customer = [] # for row_no in range(sheet.nrows): # # # TODO import group product sale # if row_no == 0: # line = (map(lambda row: isinstance(row.value, unicode) and row.value.encode('utf-8') or str(row.value), sheet.row(row_no))) # for no in range(1, len(line)): # list_customer.append(line[no]) # if row_no >= 1: # line = (map(lambda row: isinstance(row.value, unicode) and row.value.encode('utf-8') or str(row.value), # sheet.row(row_no))) # group_id = self.env['product.group.sale'].search([('name','=',line[0])]) # if group_id.group_line_ids: # group_id.group_line_ids = None # if not group_id: # group_id = self.env['product.group.sale'].create({ # 'name' : line[0] # }) # for no in range(0, len(line) - 1): # line_data = { # 'partner_name' : list_customer[no], # 'discount' : float(line[no+1])*100 # } # group_line = group_id.group_line_ids.new(line_data) # group_id.group_line_ids += group_line
class PaymentAcquirer(models.Model): """ Acquirer Model. Each specific acquirer can extend the model by adding its own fields, using the acquirer_name as a prefix for the new fields. Using the required_if_provider='<name>' attribute on fields it is possible to have required fields that depend on a specific acquirer. Each acquirer has a link to an ir.ui.view record that is a template of a button used to display the payment form. See examples in ``payment_ogone`` and ``payment_paypal`` modules. Methods that should be added in an acquirer-specific implementation: - ``<name>_form_generate_values(self, reference, amount, currency, partner_id=False, partner_values=None, tx_custom_values=None)``: method that generates the values used to render the form button template. - ``<name>_get_form_action_url(self):``: method that returns the url of the button form. It is used for example in ecommerce application if you want to post some data to the acquirer. - ``<name>_compute_fees(self, amount, currency_id, country_id)``: computes the fees of the acquirer, using generic fields defined on the acquirer model (see fields definition). Each acquirer should also define controllers to handle communication between OpenERP and the acquirer. It generally consists in return urls given to the button form and that the acquirer uses to send the customer back after the transaction, with transaction details given as a POST request. """ _name = 'payment.acquirer' _description = 'Payment Acquirer' _order = 'website_published desc, sequence, name' name = fields.Char('Name', required=True, translate=True) description = fields.Html('Description') sequence = fields.Integer('Sequence', default=10, help="Determine the display order") provider = fields.Selection(selection=[('manual', 'Manual Configuration')], string='Provider', default='manual', required=True) company_id = fields.Many2one( 'res.company', 'Company', default=lambda self: self.env.user.company_id.id, required=True) view_template_id = fields.Many2one('ir.ui.view', 'Form Button Template', required=True) registration_view_template_id = fields.Many2one( 'ir.ui.view', 'S2S Form Template', domain=[('type', '=', 'qweb')], help="Template for method registration") environment = fields.Selection([('test', 'Test'), ('prod', 'Production')], string='Environment', default='test', oldname='env', required=True) website_published = fields.Boolean( 'Visible in Portal / Website', copy=False, help="Make this payment acquirer available (Customer invoices, etc.)") # Formerly associated to `authorize` option from auto_confirm capture_manually = fields.Boolean( string="Capture Amount Manually", help="Capture the amount from Odoo, when the delivery is completed.") # Formerly associated to `generate_and_pay_invoice` option from auto_confirm journal_id = fields.Many2one( 'account.journal', 'Payment Journal', domain=[('type', 'in', ['bank', 'cash'])], default=lambda self: self.env['account.journal'].search( [('type', 'in', ['bank', 'cash'])], limit=1), help= """Payments will be registered into this journal. If you get paid straight on your bank account, select your bank account. If you get paid in batch for several transactions, create a specific payment journal for this payment acquirer to easily manage the bank reconciliation. You hold the amount in a temporary transfer account of your books (created automatically when you create the payment journal). Then when you get paid on your bank account by the payment acquirer, you reconcile the bank statement line with this temporary transfer account. Use reconciliation templates to do it in one-click.""") specific_countries = fields.Boolean( string="Specific Countries", help= "If you leave it empty, the payment acquirer will be available for all the countries." ) country_ids = fields.Many2many( 'res.country', 'payment_country_rel', 'payment_id', 'country_id', 'Countries', help= "This payment gateway is available for selected countries. If none is selected it is available for all countries." ) pre_msg = fields.Html( 'Help Message', translate=True, help='Message displayed to explain and help the payment process.') post_msg = fields.Html( 'Thanks Message', translate=True, help='Message displayed after having done the payment process.') pending_msg = fields.Html( 'Pending Message', translate=True, default=lambda s: _('<i>Pending,</i> Your online payment has been successfully processed. But your order is not validated yet.' ), help= 'Message displayed, if order is in pending state after having done the payment process.' ) done_msg = fields.Html( 'Done Message', translate=True, default=lambda s: _('<i>Done,</i> Your online payment has been successfully processed. Thank you for your order.' ), help= 'Message displayed, if order is done successfully after having done the payment process.' ) cancel_msg = fields.Html( 'Cancel Message', translate=True, default=lambda s: _('<i>Cancel,</i> Your payment has been cancelled.'), help='Message displayed, if order is cancel during the payment process.' ) error_msg = fields.Html( 'Error Message', translate=True, default=lambda s: _('<i>Error,</i> Please be aware that an error occurred during the transaction. The order has been confirmed but will not be paid. Do not hesitate to contact us if you have any questions on the status of your order.' ), help='Message displayed, if error is occur during the payment process.' ) save_token = fields.Selection( [('none', 'Never'), ('ask', 'Let the customer decide'), ('always', 'Always')], string='Save Cards', default='none', help= "This option allows customers to save their credit card as a payment token and to reuse it for a later purchase. " "If you manage subscriptions (recurring invoicing), you need it to automatically charge the customer when you " "issue an invoice.") token_implemented = fields.Boolean('Saving Card Data supported', compute='_compute_feature_support', search='_search_is_tokenized') authorize_implemented = fields.Boolean('Authorize Mechanism Supported', compute='_compute_feature_support') fees_implemented = fields.Boolean('Fees Computation Supported', compute='_compute_feature_support') fees_active = fields.Boolean('Add Extra Fees') fees_dom_fixed = fields.Float('Fixed domestic fees') fees_dom_var = fields.Float('Variable domestic fees (in percents)') fees_int_fixed = fields.Float('Fixed international fees') fees_int_var = fields.Float('Variable international fees (in percents)') # TDE FIXME: remove that brol module_id = fields.Many2one('ir.module.module', string='Corresponding Module') module_state = fields.Selection(selection=ir_module.STATES, string='Installation State', related='module_id.state') image = fields.Binary( "Image", attachment=True, help= "This field holds the image used for this provider, limited to 1024x1024px" ) image_medium = fields.Binary( "Medium-sized image", attachment=True, help="Medium-sized image of this provider. It is automatically " "resized as a 128x128px image, with aspect ratio preserved. " "Use this field in form views or some kanban views.") image_small = fields.Binary( "Small-sized image", attachment=True, help="Small-sized image of this provider. It is automatically " "resized as a 64x64px image, with aspect ratio preserved. " "Use this field anywhere a small image is required.") payment_icon_ids = fields.Many2many('payment.icon', string='Supported Payment Icons') payment_flow = fields.Selection( selection=[('form', 'Redirection to the acquirer website'), ('s2s', 'Payment from Odoo')], default='form', required=True, string='Payment Flow', help= """Note: Subscriptions does not take this field in account, it uses server to server by default.""" ) def _search_is_tokenized(self, operator, value): tokenized = self._get_feature_support()['tokenize'] if (operator, value) in [('=', True), ('!=', False)]: return [('provider', 'in', tokenized)] return [('provider', 'not in', tokenized)] @api.multi def _compute_feature_support(self): feature_support = self._get_feature_support() for acquirer in self: acquirer.fees_implemented = acquirer.provider in feature_support[ 'fees'] acquirer.authorize_implemented = acquirer.provider in feature_support[ 'authorize'] acquirer.token_implemented = acquirer.provider in feature_support[ 'tokenize'] @api.multi def _check_required_if_provider(self): """ If the field has 'required_if_provider="<provider>"' attribute, then it required if record.provider is <provider>. """ for acquirer in self: if any( getattr(f, 'required_if_provider', None) == acquirer.provider and not acquirer[k] for k, f in self._fields.items()): return False return True _constraints = [ (_check_required_if_provider, 'Required fields not filled', []), ] def _get_feature_support(self): """Get advanced feature support by provider. Each provider should add its technical in the corresponding key for the following features: * fees: support payment fees computations * authorize: support authorizing payment (separates authorization and capture) * tokenize: support saving payment data in a payment.tokenize object """ return dict(authorize=[], tokenize=[], fees=[]) @api.model def create(self, vals): image_resize_images(vals) return super(PaymentAcquirer, self).create(vals) @api.multi def write(self, vals): image_resize_images(vals) return super(PaymentAcquirer, self).write(vals) @api.multi def toggle_website_published(self): self.write({'website_published': not self.website_published}) return True @api.multi def get_form_action_url(self): """ Returns the form action URL, for form-based acquirer implementations. """ if hasattr(self, '%s_get_form_action_url' % self.provider): return getattr(self, '%s_get_form_action_url' % self.provider)() return False def _get_available_payment_input(self, partner=None, company=None): """ Generic (model) method that fetches available payment mechanisms to use in all portal / eshop pages that want to use the payment form. It contains * form_acquirers: record set of acquirers based on a local form that sends customer to the acquirer website; * s2s_acquirers: reset set of acquirers that send customer data to acquirer without redirecting to any other website; * pms: record set of stored credit card data (aka payment.token) connected to a given partner to allow customers to reuse them """ if not company: company = self.env.user.company_id if not partner: partner = self.env.user.partner_id active_acquirers = self.sudo().search([ ('website_published', '=', True), ('company_id', '=', company.id) ]) form_acquirers = active_acquirers.filtered( lambda acq: acq.payment_flow == 'form' and acq.view_template_id) s2s_acquirers = active_acquirers.filtered( lambda acq: acq.payment_flow == 's2s' and acq. registration_view_template_id) return { 'form_acquirers': form_acquirers, 's2s_acquirers': s2s_acquirers, 'pms': self.env['payment.token'].search([('partner_id', '=', partner.id), ('acquirer_id', 'in', s2s_acquirers.ids)]), } @api.multi def render(self, reference, amount, currency_id, partner_id=False, values=None): """ Renders the form template of the given acquirer as a qWeb template. :param string reference: the transaction reference :param float amount: the amount the buyer has to pay :param currency_id: currency id :param dict partner_id: optional partner_id to fill values :param dict values: a dictionary of values for the transction that is given to the acquirer-specific method generating the form values All templates will receive: - acquirer: the payment.acquirer browse record - user: the current user browse record - currency_id: id of the transaction currency - amount: amount of the transaction - reference: reference of the transaction - partner_*: partner-related values - partner: optional partner browse record - 'feedback_url': feedback URL, controler that manage answer of the acquirer (without base url) -> FIXME - 'return_url': URL for coming back after payment validation (wihout base url) -> FIXME - 'cancel_url': URL if the client cancels the payment -> FIXME - 'error_url': URL if there is an issue with the payment -> FIXME - context: Odoo context """ if values is None: values = {} # reference and amount values.setdefault('reference', reference) amount = float_round(amount, 2) values.setdefault('amount', amount) # currency id currency_id = values.setdefault('currency_id', currency_id) if currency_id: currency = self.env['res.currency'].browse(currency_id) else: currency = self.env.user.company_id.currency_id values['currency'] = currency # Fill partner_* using values['partner_id'] or partner_id argument partner_id = values.get('partner_id', partner_id) billing_partner_id = values.get('billing_partner_id', partner_id) if partner_id: partner = self.env['res.partner'].browse(partner_id) if partner_id != billing_partner_id: billing_partner = self.env['res.partner'].browse( billing_partner_id) else: billing_partner = partner values.update({ 'partner': partner, 'partner_id': partner_id, 'partner_name': partner.name, 'partner_lang': partner.lang, 'partner_email': partner.email, 'partner_zip': partner.zip, 'partner_city': partner.city, 'partner_address': _partner_format_address(partner.street, partner.street2), 'partner_country_id': partner.country_id.id, 'partner_country': partner.country_id, 'partner_phone': partner.phone, 'partner_state': partner.state_id, 'billing_partner': billing_partner, 'billing_partner_id': billing_partner_id, 'billing_partner_name': billing_partner.name, 'billing_partner_lang': billing_partner.lang, 'billing_partner_email': billing_partner.email, 'billing_partner_zip': billing_partner.zip, 'billing_partner_city': billing_partner.city, 'billing_partner_address': _partner_format_address(billing_partner.street, billing_partner.street2), 'billing_partner_country_id': billing_partner.country_id.id, 'billing_partner_country': billing_partner.country_id, 'billing_partner_phone': billing_partner.phone, 'billing_partner_state': billing_partner.state_id, }) if values.get('partner_name'): values.update({ 'partner_first_name': _partner_split_name(values.get('partner_name'))[0], 'partner_last_name': _partner_split_name(values.get('partner_name'))[1], }) if values.get('billing_partner_name'): values.update({ 'billing_partner_first_name': _partner_split_name(values.get('billing_partner_name'))[0], 'billing_partner_last_name': _partner_split_name(values.get('billing_partner_name'))[1], }) # Fix address, country fields if not values.get('partner_address'): values['address'] = _partner_format_address( values.get('partner_street', ''), values.get('partner_street2', '')) if not values.get('partner_country') and values.get( 'partner_country_id'): values['country'] = self.env['res.country'].browse( values.get('partner_country_id')) if not values.get('billing_partner_address'): values['billing_address'] = _partner_format_address( values.get('billing_partner_street', ''), values.get('billing_partner_street2', '')) if not values.get('billing_partner_country') and values.get( 'billing_partner_country_id'): values['billing_country'] = self.env['res.country'].browse( values.get('billing_partner_country_id')) # compute fees fees_method_name = '%s_compute_fees' % self.provider if hasattr(self, fees_method_name): fees = getattr(self, fees_method_name)(values['amount'], values['currency_id'], values.get('partner_country_id')) values['fees'] = float_round(fees, 2) # call <name>_form_generate_values to update the tx dict with acqurier specific values cust_method_name = '%s_form_generate_values' % (self.provider) if hasattr(self, cust_method_name): method = getattr(self, cust_method_name) values = method(values) values.update({ 'tx_url': self._context.get('tx_url', self.get_form_action_url()), 'submit_class': self._context.get('submit_class', 'btn btn-link'), 'submit_txt': self._context.get('submit_txt'), 'acquirer': self, 'user': self.env.user, 'context': self._context, 'type': values.get('type') or 'form', }) values.setdefault('return_url', False) _logger.info( 'payment.acquirer.render: <%s> values rendered for form payment:\n%s', self.provider, pprint.pformat(values)) return self.view_template_id.render(values, engine='ir.qweb') def get_s2s_form_xml_id(self): if self.registration_view_template_id: model_data = self.env['ir.model.data'].search([ ('model', '=', 'ir.ui.view'), ('res_id', '=', self.registration_view_template_id.id) ]) return ('%s.%s') % (model_data.module, model_data.name) return False @api.multi def s2s_process(self, data): cust_method_name = '%s_s2s_form_process' % (self.provider) if not self.s2s_validate(data): return False if hasattr(self, cust_method_name): # As this method may be called in JSON and overriden in various addons # let us raise interesting errors before having stranges crashes if not data.get('partner_id'): raise ValueError( _('Missing partner reference when trying to create a new payment token' )) method = getattr(self, cust_method_name) return method(data) return True @api.multi def s2s_validate(self, data): cust_method_name = '%s_s2s_form_validate' % (self.provider) if hasattr(self, cust_method_name): method = getattr(self, cust_method_name) return method(data) return True @api.multi def toggle_environment_value(self): prod = self.filtered(lambda acquirer: acquirer.environment == 'prod') prod.write({'environment': 'test'}) (self - prod).write({'environment': 'prod'}) @api.multi def button_immediate_install(self): # TDE FIXME: remove that brol if self.module_id and self.module_state != 'installed': self.module_id.button_immediate_install() return { 'type': 'ir.actions.client', 'tag': 'reload', }
class import_emp_img_wizard(models.TransientModel): _name="import.emp.img.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) emp_by = fields.Selection([ ('name','Name'), ('db_id','ID'), ('id_no','Identification No') ], default="name", string = "Employee By", required = True) @api.multi def show_success_msg(self,counter,skipped_line_no): #to close the current active wizard action = self.env.ref('sh_all_in_one_import.sh_import_emp_img_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" 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_emp_img_apply(self): hr_emp_obj = self.env['hr.employee'] #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 for row in myreader: try: if skip_header: skip_header=False counter = counter + 1 continue if row[0] not in (None,"") and row[1].strip() not in (None,""): vals={} image_path = row[1].strip() if "http://" in image_path or "https://" in image_path: try: r = requests.get(image_path) if r and r.content: image_base64 = base64.encodestring(r.content) vals.update({'image': image_base64}) else: skipped_line_no[str(counter)] = " - URL not correct or check your image size. " counter = counter + 1 continue except Exception as e: skipped_line_no[str(counter)] = " - URL not correct or check your image size " + ustr(e) counter = counter + 1 continue else: try: with open(image_path, 'rb') as image: image.seek(0) binary_data = image.read() image_base64 = codecs.encode(binary_data, 'base64') if image_base64: vals.update({'image': image_base64}) else: skipped_line_no[str(counter)] = " - Could not find the image or please make sure it is accessible to this app. " counter = counter + 1 continue except Exception as e: skipped_line_no[str(counter)] = " - Could not find the image or please make sure it is accessible to this app. " + ustr(e) counter = counter + 1 continue field_nm = 'name' if self.emp_by == 'name': field_nm = 'name' elif self.emp_by == 'db_id': field_nm = 'id' elif self.emp_by == 'id_no': field_nm = 'identification_id' search_emp = hr_emp_obj.search([(field_nm,'=',row[0] )], limit = 1) if search_emp: search_emp.write(vals) else: skipped_line_no[str(counter)] = " - Employee not found. " counter = counter + 1 else: skipped_line_no[str(counter)] = " - Employee or URL/Path field is empty. " counter = counter + 1 continue except Exception as e: skipped_line_no[str(counter)]=" - Value is not valid. " + ustr(e) counter = counter + 1 continue except Exception as e: raise UserError(_("Sorry, Your csv file does not match with our format " + ustr(e) )) if counter > 1: completed_records = (counter - len(skipped_line_no)) - 2 res = self.show_success_msg(completed_records, skipped_line_no) return res #For Excel if self.import_type == 'excel': counter = 1 skipped_line_no = {} try: wb = xlrd.open_workbook(file_contents=base64.decodestring(self.file)) sheet = wb.sheet_by_index(0) skip_header = True 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,1).value.strip() not in (None,""): vals={} image_path = sheet.cell(row,1).value.strip() if "http://" in image_path or "https://" in image_path: try: r = requests.get(image_path) if r and r.content: image_base64 = base64.encodestring(r.content) vals.update({'image': image_base64}) else: skipped_line_no[str(counter)] = " - URL not correct or check your image size. " counter = counter + 1 continue except Exception as e: skipped_line_no[str(counter)] = " - URL not correct or check your image size " + ustr(e) counter = counter + 1 continue else: try: with open(image_path, 'rb') as image: image.seek(0) binary_data = image.read() image_base64 = codecs.encode(binary_data, 'base64') if image_base64: vals.update({'image': image_base64}) else: skipped_line_no[str(counter)] = " - Could not find the image or please make sure it is accessible to this app. " counter = counter + 1 continue except Exception as e: skipped_line_no[str(counter)] = " - Could not find the image or please make sure it is accessible to this app. " + ustr(e) counter = counter + 1 continue search_str = sheet.cell(row,0).value field_nm = 'name' if self.emp_by == 'name': field_nm = 'name' elif self.emp_by == 'db_id': field_nm = 'id' str_id = str(sheet.cell(row,0).value) str_id = str_id.split('.', 1)[0] search_str = int(str_id) elif self.emp_by == 'id_no': field_nm = 'identification_id' search_emp = hr_emp_obj.search([(field_nm,'=',search_str )], limit = 1) if search_emp: search_emp.write(vals) else: skipped_line_no[str(counter)] = " - Employee not found. " counter = counter + 1 else: skipped_line_no[str(counter)] = " - Employee or URL/Path field is empty. " counter = counter + 1 continue except Exception as e: skipped_line_no[str(counter)]=" - Value is not valid. " + ustr(e) counter = counter + 1 continue except Exception as e: raise UserError(_("Sorry, Your excel file does not match with our format " + ustr(e) )) if counter > 1: completed_records = (counter - len(skipped_line_no)) - 2 res = self.show_success_msg(completed_records, skipped_line_no) return res
class GhuExamination(models.Model): _name = 'ghu_custom_mba.examination' _rec_name = 'name' _description = "Examination" enrollment_id = fields.Many2one( string=u'Enrollment', comodel_name='ghu_custom_mba.course_enrollment', ondelete='cascade', ) name = fields.Char('Name', compute='_compute_name') @api.depends('request_date') def _compute_name(self): for rec in self: rec.name = rec.request_date.strftime("%b %d %Y") type = fields.Char('Type', size=128, required=True) question_title = fields.Char('Question Title', required=True) question = fields.Html('Question', required=True) request_date = fields.Date( string='Request Date', default=fields.Date.context_today, ) end_date = fields.Date(string='End Date', compute='_compute_enddate') @api.depends('request_date') def _compute_enddate(self): for rec in self: rec.end_date = rec.request_date + datetime.timedelta(days=21) @api.one def isActive(self): if fields.Date.context_today >= self.request_date and fields.Date.context_today <= self.end_date: return True return False submission = fields.Binary(string='Submission', attachment=True) submission_filename = fields.Char(string='Submission Filename') # Grading section grade = fields.Float(string='Grade', digits=(3, 1)) # 44 to 50 Points = Excellent (1) # 38 to 43 Points = Good (2) # 32 to 37 Points = Satisfactory (3) # 26 to 31 Points = Pass (4) # below 25 = Fail (5) @api.one @api.constrains('grade') def _check_grade(self): if self.grade > 50.0 or self.grade < 0.0: raise ValidationError( "Grading score must be between 0 and 50 points.") result = fields.Html(string='Comment on grading', )
class StudentCertificate(models.Model): _name = "student.certificate" student_id = fields.Many2one('student.student', 'Student') description = fields.Char('Description') certi = fields.Binary('Certificate', required=True)
class import_attendance(models.TransientModel): _name = "import.attendance" file = fields.Binary('File') file_opt = fields.Selection([('csv', 'CSV'), ('excel', 'EXCEL')]) def import_file(self): if self.file_opt == 'csv': try: keys = ['name', 'check_in', 'check_out'] csv_data = base64.b64decode(self.file) data_file = io.StringIO(csv_data.decode("utf-8")) data_file.seek(0) file_reader = [] csv_reader = csv.reader(data_file, delimiter=',') file_reader.extend(csv_reader) except Exception: raise exceptions.Warning( _("Please select an CSV/XLS file or You have selected invalid file" )) values = {} for i in range(len(file_reader)): field = list(map(str, file_reader[i])) values = dict(zip(keys, field)) if values: if i == 0: continue else: res = self._create_timesheet(values) else: try: fp = tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx") fp.write(binascii.a2b_base64(self.file)) fp.seek(0) values = {} workbook = xlrd.open_workbook(fp.name) sheet = workbook.sheet_by_index(0) for row_no in range(sheet.nrows): if row_no <= 0: fields = map(lambda row: row.value.encode('utf-8'), sheet.row(row_no)) else: line = list( map( lambda row: isinstance(row.value, bytes) and row.value.encode('utf-8') or str(row.value), sheet.row(row_no))) values.update({ 'name': line[0], 'check_in': line[1], 'check_out': line[2], }) res = self._create_timesheet(values) except Exception: raise exceptions.Warning( _("Please select an CSV/XLS file or You have selected invalid file" )) return res def _create_timesheet(self, val): emp_id = self._find_employee(val.get('name')) if not emp_id: raise Warning('Employee Not Found') if not val.get('check_in'): raise Warning('Please Provide Sign In Time') if not val.get('check_out'): self._cr.execute( "insert into hr_attendance (employee_id,check_in) values (%s,%s)", (emp_id.id, val.get('check_in'))) else: self._cr.execute( "insert into hr_attendance (employee_id,check_in,check_out) values (%s,%s,%s)", (emp_id.id, val.get('check_in'), val.get('check_out'))) return True def _find_employee(self, name): emp_id = self.env['hr.employee'].search([('name', '=', name)]) if emp_id: return emp_id else: raise Warning(_("Employee '%s' Not Found!") % ustr(name))
class PurchaseRequestReportOut(models.Model): _name = 'purchase.request.recap.qcf.reports' _description = 'purchase request recap qcf report' purchase_request_data = fields.Char('Name', size=256) file_name = fields.Binary('PR Excel Report', readonly=True)
class StudentStudent(models.Model): ''' Defining a student information ''' _name = 'student.student' _table = "student_student" _description = 'Student Information' _inherits = {'res.users': 'user_id'} @api.multi @api.depends('date_of_birth') def _compute_student_age(self): '''Method to calculate student age''' current_dt = datetime.today() for rec in self: if rec.date_of_birth: start = datetime.strptime(rec.date_of_birth, DEFAULT_SERVER_DATE_FORMAT) age_calc = ((current_dt - start).days / 365) # Age should be greater than 0 if age_calc > 0.0: rec.age = age_calc @api.model def createsql(self, vals): for stu in vals: print "vals" print vals standard_ids = self.env['school.standard'].search([ ('all_name', '=', stu.get('standard')), ('school_id', '=', stu.get('school_id')) ]) standard = None if not standard_ids: # res = self.env['standard.standard'].search([('name','=','standard_name')]) standard_new = self.env['school.standard'].create({ 'all_name': stu.get('standard'), 'school_id': stu.get('school_id'), }) standard = standard_new.id else: standard = standard_ids[0].id print "standard" print standard print { 'name': "aa bb", 'barcode': stu.get('barcode'), 'login': stu.get('barcode'), 'password': '******', 'company_id': int(stu.get('company_id')), } print "user_new" user_new = self.env['res.users'].sudo().create({ 'name': "AA", 'barcode': stu.get('barcode'), 'login': stu.get('barcode'), 'password': '******', 'company_id': int(stu.get('company_id')), }) print user_new insert_id = self._cr.execute( "insert into student_student (user_id,school_id,state,standard_id) VALUES(%s,%s,%s,%s) " % (user_new.id, stu.get('school_id'), 'done', standard)) print "insert_id" print insert_id self._cr.execute( "insert into library_card (code,user,type,standard_id,student_id) VALUES(%s,%s,%s,%s) " % (stu.get('barcode'), 'student', 78, standard, insert_id)) self._cr.commit() return 1 @api.model def create(self, vals): _logger.info("create_StudentStudent_school_school_start") '''Method to create user when student is created''' if vals.get('pid', False): vals['login'] = uuid.uuid1() vals['password'] = vals['pid'] vals['name'] = vals['name'] # else: # raise except_orm(_('Error!'), # _('''PID not valid # so record will not be saved.''')) if vals.get('cmp_id', False): h = { 'company_ids': [(4, vals.get('cmp_id'))], 'company_id': vals.get('cmp_id') } vals.update(h) _logger.info("create_StudentStudent_school_school_start_mmm") res = super(StudentStudent, self).create(vals) _logger.info("create_StudentStudent_school_school_end") # Assign group to student based on condition emp_grp = self.env.ref('base.group_user') #9/19 要30秒 故取消 # if res.state == 'draft': # admission_group = self.env.ref('school.group_is_admission') # new_grp_list = [admission_group.id, emp_grp.id] # res.user_id.write({'groups_id': [(6, 0, new_grp_list)]}) # elif res.state == 'done': # done_student = self.env.ref('school.group_school_student') # group_list = [done_student.id, emp_grp.id] # res.user_id.write({'groups_id': [(6, 0, group_list)]}) _logger.info("create_StudentStudent_after") return res @api.model def _get_default_image(self, is_company, colorize=False): '''Method to get default Image''' # added in try-except because import statements are in try-except try: img_path = get_module_resource('base', 'static/src/img', 'avatar.png') with open(img_path, 'rb') as f: image = f.read() image = image_colorize(image) return image_resize_image_big(image.encode('base64')) except: return False family_con_ids = fields.One2many('student.family.contact', 'family_contact_id', 'Family Contact Detail', states={'done': [('readonly', True)]}) user_id = fields.Many2one('res.users', 'User ID', ondelete="cascade", required=True) student_name = fields.Char('学生姓名', related='user_id.name', store=True, readonly=True) pid = fields.Char('系统编号', required=True, default=lambda obj: obj.env['ir.sequence'].next_by_code( 'student.student'), help='Personal IDentification Number') reg_code = fields.Char('Registration Code', help='Student Registration Code') student_code = fields.Char('学号') contact_phone1 = fields.Char('Phone no.', ) contact_mobile1 = fields.Char('Mobile no', ) roll_no = fields.Integer('Roll No.', readonly=True) photo = fields.Binary('Photo', default=lambda self: self._get_default_image( self._context.get('default_is_company', False))) year = fields.Many2one('academic.year', 'Academic Year', states={'done': [('readonly', True)]}) cast_id = fields.Many2one('student.cast', 'Religion') relation = fields.Many2one('student.relation.master', 'Relation') admission_date = fields.Date('Admission Date', default=date.today()) middle = fields.Char('Middle Name', states={'done': [('readonly', True)]}) last = fields.Char('Surname', states={'done': [('readonly', True)]}) gender = fields.Selection([('male', 'Male'), ('female', 'Female')], 'Gender', states={'done': [('readonly', True)]}) date_of_birth = fields.Date('BirthDate', states={'done': [('readonly', True)]}) mother_tongue = fields.Many2one('mother.toungue', "Mother Tongue") age = fields.Integer(compute='_compute_student_age', string='Age', readonly=True) maritual_status = fields.Selection([('unmarried', 'Unmarried'), ('married', 'Married')], 'Marital Status', states={'done': [('readonly', True)]}) reference_ids = fields.One2many('student.reference', 'reference_id', 'References', states={'done': [('readonly', True)]}) previous_school_ids = fields.One2many( 'student.previous.school', 'previous_school_id', 'Previous School Detail', states={'done': [('readonly', True)]}) doctor = fields.Char('Doctor Name', states={'done': [('readonly', True)]}) designation = fields.Char('Designation') doctor_phone = fields.Char('Phone') blood_group = fields.Char('Blood Group') height = fields.Float('Height', help="Hieght in C.M") weight = fields.Float('Weight', help="Weight in K.G") eye = fields.Boolean('Eyes') ear = fields.Boolean('Ears') nose_throat = fields.Boolean('Nose & Throat') respiratory = fields.Boolean('Respiratory') cardiovascular = fields.Boolean('Cardiovascular') neurological = fields.Boolean('Neurological') muskoskeletal = fields.Boolean('Musculoskeletal') dermatological = fields.Boolean('Dermatological') blood_pressure = fields.Boolean('Blood Pressure') remark = fields.Text('Remark', states={'done': [('readonly', True)]}) school_id = fields.Many2one('school.school', '学校', states={'done': [('readonly', True)]}) state = fields.Selection([('draft', '草稿'), ('done', '在读'), ('terminate', '中断'), ('alumni', '毕业')], 'State', readonly=True, default="draft") history_ids = fields.One2many('student.history', 'student_id', 'History') certificate_ids = fields.One2many('student.certificate', 'student_id', 'Certificate') student_discipline_line = fields.One2many('student.descipline', 'student_id', 'Descipline') address_ids = fields.One2many('res.partner', 'student_id', 'Contacts') document = fields.One2many('student.document', 'doc_id', 'Documents') description = fields.One2many('student.description', 'des_id', 'Description') student_id = fields.Many2one('student.student', 'Name') contact_phone = fields.Char('Phone No', related='student_id.phone', readonly=True) contact_mobile = fields.Char('Mobile No', related='student_id.mobile', readonly=True) contact_email = fields.Char('Email', related='student_id.email', readonly=True) contact_website = fields.Char('WebSite', related='student_id.website', readonly=True) award_list = fields.One2many('student.award', 'award_list_id', 'Award List') student_status = fields.Selection('Status', related='student_id.state', help="Shows Status Of Student", readonly=True) stu_name = fields.Char('First Name', related='user_id.name', readonly=True) Acadamic_year = fields.Char('Academic Year', related='year.name', help='Academic Year', readonly=True) division_id = fields.Many2one('standard.division', 'Division') medium_id = fields.Many2one('standard.medium', 'Medium') cmp_id = fields.Many2one('res.company', 'Company Name', related='school_id.company_id', store=True) standard_id = fields.Many2one('school.standard', '班级') parent_id = fields.Many2many('res.partner', 'student_parent_rel', 'student_id', 'parent_id', 'Parent(s)', states={'done': [('readonly', True)]}) terminate_reason = fields.Text('Reason') card_import_code = fields.Char('借书卡') @api.multi def set_to_draft(self): '''Method to change state to draft''' for rec in self: rec.state = 'draft' return True @api.multi def set_alumni(self): '''Method to change state to alumni''' for rec in self: rec.state = 'alumni' return True @api.multi def set_done(self): '''Method to change state to done''' for rec in self: rec.state = 'done' return True @api.multi def admission_draft(self): '''Set the state to draft''' for rec in self: rec.state = 'draft' return True @api.multi def set_terminate(self): for rec in self: rec.state = 'terminate' return True @api.multi def admission_done(self): '''Method to confirm admission''' school_standard_obj = self.env['school.standard'] ir_sequence = self.env['ir.sequence'] student_group = self.env.ref('school.group_school_student') emp_group = self.env.ref('base.group_user') for rec in self: domain = [('standard_id', '=', rec.standard_id.id)] # Checks the standard if not defined raise error print domain if not school_standard_obj.search(domain): raise except_orm(_('Warning'), _('''班级未指定''')) # Assign group to student rec.user_id.write( {'groups_id': [(6, 0, [emp_group.id, student_group.id])]}) # Assign roll no to student number = 1 for rec_std in rec.search(domain): rec_std.roll_no = number number += 1 # Assign registration code to student reg_code = ir_sequence.next_by_code('student.registration') registation_code = (str(rec.school_id.state_id.name) + str('/') + str(rec.school_id.city) + str('/') + str(rec.school_id.name) + str('/') + str(reg_code)) stu_code = ir_sequence.next_by_code('student.code') student_code = (str(rec.school_id.code) + str('/') + str(rec.year.code) + str('/') + str(stu_code)) rec.write({ 'state': 'done', 'admission_date': time.strftime('%Y-%m-%d'), 'student_code': student_code, 'reg_code': registation_code }) card = self.env['library.card'].create({ 'student_id': rec.id, 'user': '******', 'gt_name': rec.name, 'book_limit': 0 }) rec.user_id.write({'card_id': card.id}) return True
class create_hospital_invoice_wizard(models.TransientModel): _name = 'create.hospital.invoice.wizard' _description = 'Hospital Invoice Import' excel = fields.Binary(u'导入系统导出的excel文件', ) excel2 = fields.Binary(u'导入系统导出的excel文件', ) excel3 = fields.Binary(u'导入系统导出的excel文件', ) type = fields.Selection([('hospitalization', u'住院'), ('outpatient', u'门诊')], u'单据类型', default='hospitalization') @api.multi def create_hospital_invoice(self): """ 通过Excel文件导入信息到hospital.invoice """ month = self.env['hospital.month'].browse( self.env.context.get('active_id')) if not month: return {} xls_data = xlrd.open_workbook( file_contents=base64.decodestring(self.excel)) table = xls_data.sheets()[0] #取得行数 ncows = table.nrows #取得第1行数据 colnames = table.row_values(0) list = [] newcows = 0 for rownum in range(1, ncows): row = table.row_values(rownum) if row: app = {} for i in range(len(colnames)): app[colnames[i]] = row[i] #过滤掉不需要的行,详见销货清单的会在清单中再次导入 if app.get(u'病人姓名') or app.get(u'姓名'): list.append(app) newcows += 1 #数据读入。 for data in range(0, newcows): in_xls_data = list[data] invoice_ids = self.env['hospital.invoice'].create({ 'name': in_xls_data.get(u'病人姓名') or in_xls_data.get(u'姓名'), 'invoice': in_xls_data.get(u'票据号'), 'name_id': in_xls_data.get(u'身份证号'), 'pay_type': in_xls_data.get(u'病人类别'), 'amount': float(in_xls_data.get(u'结账金额') or 0.00), 'difference_amount': float(in_xls_data.get(u'尾数处理') or 0.00), 'type': self.type, 'month_id': month.id or '', }) if invoice_ids.type == 'outpatient' and in_xls_data.get( u'结账金额') != in_xls_data.get(u'应收金额'): invoice_ids.write({ 'difference_amount': float(in_xls_data.get(u'应收金额') - in_xls_data.get(u'结账金额')) }) if in_xls_data.get(u'冲预交'): self.env['hospital.pay.line'].create({ 'invoice_id': invoice_ids.id, 'name': u'冲预交', 'amount': float(in_xls_data.get(u'冲预交') or 0.00), 'type': self.type, }) if in_xls_data.get(u'个人自付'): self.env['hospital.pay.line'].create({ 'invoice_id': invoice_ids.id, 'name': u'个人自付', 'amount': float(in_xls_data.get(u'个人自付') or 0.00), 'type': self.type, }) if in_xls_data.get(u'应收金额'): self.env['hospital.pay.line'].create({ 'invoice_id': invoice_ids.id, 'name': u'个人自付', 'amount': float(in_xls_data.get(u'应收金额') or 0.00), 'type': self.type, }) if in_xls_data.get(u'材料费'): self.env['hospital.invoice.line'].create({ 'invoice_id': invoice_ids.id, 'name': u'材料费', 'amount': float(in_xls_data.get(u'材料费') or 0.00), 'type': self.type, }) if in_xls_data.get(u'床位费'): self.env['hospital.invoice.line'].create({ 'invoice_id': invoice_ids.id, 'name': u'床位费', 'amount': float(in_xls_data.get(u'床位费') or 0.00), 'type': self.type, }) if in_xls_data.get(u'护理费'): self.env['hospital.invoice.line'].create({ 'invoice_id': invoice_ids.id, 'name': u'护理费', 'amount': float(in_xls_data.get(u'护理费') or 0.00), 'type': self.type, }) if in_xls_data.get(u'检查费'): self.env['hospital.invoice.line'].create({ 'invoice_id': invoice_ids.id, 'name': u'检查费', 'amount': float(in_xls_data.get(u'检查费') or 0.00), 'type': self.type, }) if in_xls_data.get(u'检验费'): self.env['hospital.invoice.line'].create({ 'invoice_id': invoice_ids.id, 'name': u'检验费', 'amount': float(in_xls_data.get(u'检验费') or 0.00), 'type': self.type, }) if in_xls_data.get(u'输氧费'): self.env['hospital.invoice.line'].create({ 'invoice_id': invoice_ids.id, 'name': u'输氧费', 'amount': float(in_xls_data.get(u'输氧费') or 0.00), 'type': self.type, }) if in_xls_data.get(u'西药费'): self.env['hospital.invoice.line'].create({ 'invoice_id': invoice_ids.id, 'name': u'西药费', 'amount': float(in_xls_data.get(u'西药费') or 0.00), 'type': self.type, }) if in_xls_data.get(u'诊查费'): self.env['hospital.invoice.line'].create({ 'invoice_id': invoice_ids.id, 'name': u'诊查费', 'amount': float(in_xls_data.get(u'诊查费') or 0.00), 'type': self.type, }) if in_xls_data.get(u'治疗费'): self.env['hospital.invoice.line'].create({ 'invoice_id': invoice_ids.id, 'name': u'治疗费', 'amount': float(in_xls_data.get(u'治疗费') or 0.00), 'type': self.type, }) if in_xls_data.get(u'中成药费'): self.env['hospital.invoice.line'].create({ 'invoice_id': invoice_ids.id, 'name': u'中成药费', 'amount': float(in_xls_data.get(u'中成药费') or 0.00), 'type': self.type, }) if in_xls_data.get(u'中草药费'): self.env['hospital.invoice.line'].create({ 'invoice_id': invoice_ids.id, 'name': u'中草药费', 'amount': float(in_xls_data.get(u'中草药费') or 0.00), 'type': self.type, }) if in_xls_data.get(u'其他费'): self.env['hospital.invoice.line'].create({ 'invoice_id': invoice_ids.id, 'name': u'其他费', 'amount': float(in_xls_data.get(u'其他费') or 0.00), 'type': self.type, }) if in_xls_data.get(u'手术费'): self.env['hospital.invoice.line'].create({ 'invoice_id': invoice_ids.id, 'name': u'手术费', 'amount': float(in_xls_data.get(u'手术费') or 0.00), 'type': self.type, }) if in_xls_data.get(u'其他'): self.env['hospital.invoice.line'].create({ 'invoice_id': invoice_ids.id, 'name': u'其他', 'amount': float(in_xls_data.get(u'其他') or 0.00), 'type': self.type, }) if in_xls_data.get(u'检验'): self.env['hospital.invoice.line'].create({ 'invoice_id': invoice_ids.id, 'name': u'检验', 'amount': float(in_xls_data.get(u'检验') or 0.00), 'type': self.type, }) if in_xls_data.get(u'中成药'): self.env['hospital.invoice.line'].create({ 'invoice_id': invoice_ids.id, 'name': u'中成药', 'amount': float(in_xls_data.get(u'中成药') or 0.00), 'type': self.type, }) if in_xls_data.get(u'西药'): self.env['hospital.invoice.line'].create({ 'invoice_id': invoice_ids.id, 'name': u'西药', 'amount': float(in_xls_data.get(u'西药') or 0.00), 'type': self.type, }) if in_xls_data.get(u'检查'): self.env['hospital.invoice.line'].create({ 'invoice_id': invoice_ids.id, 'name': u'检查', 'amount': float(in_xls_data.get(u'检查') or 0.00), 'type': self.type, }) if in_xls_data.get(u'卫生材料'): self.env['hospital.invoice.line'].create({ 'invoice_id': invoice_ids.id, 'name': u'卫生材料', 'amount': float(in_xls_data.get(u'卫生材料') or 0.00), 'type': self.type, }) if in_xls_data.get(u'治疗'): self.env['hospital.invoice.line'].create({ 'invoice_id': invoice_ids.id, 'name': u'治疗', 'amount': float(in_xls_data.get(u'治疗') or 0.00), 'type': self.type, }) if in_xls_data.get(u'中草药'): self.env['hospital.invoice.line'].create({ 'invoice_id': invoice_ids.id, 'name': u'中草药', 'amount': float(in_xls_data.get(u'中草药') or 0.00), 'type': self.type, }) if in_xls_data.get(u'护理'): self.env['hospital.invoice.line'].create({ 'invoice_id': invoice_ids.id, 'name': u'护理', 'amount': float(in_xls_data.get(u'护理') or 0.00), 'type': self.type, }) def excel_date(self, data): #将excel日期改为正常日期 if type(data) in (int, float): year, month, day, hour, minute, second = xlrd.xldate_as_tuple( data, 0) py_date = datetime.datetime(year, month, day, hour, minute, second) else: py_date = data return py_date @api.multi def synchro_hospital_invoice(self): month = self.env['hospital.month'].browse( self.env.context.get('active_id')) if not month: return {} conn = self.createConnection() self.create_hospital_invoice2(conn, self.type, month) self.closeConnection(conn) @api.multi def createConnection(self): if config['amj_server'] and config['amj_server'] != 'None': amj_server = config['amj_server'] else: raise Exception('医院服务器没有找到.') if config['amj_user'] and config['amj_user'] != 'None': amj_user = config['amj_user'] else: raise Exception('医院用户没有找到.') if config['amj_password'] and config['amj_password'] != 'None': amj_password = config['amj_password'] else: raise Exception('医院 用户密码没有找到.') if config['amj_database'] and config['amj_database'] != 'None': amj_database = config['amj_database'] else: raise Exception('医院数据库没有找到.') conn = pymssql.connect(server=amj_server, user=amj_user, password=amj_password, database=amj_database, charset='utf8') return conn # 关闭数据库连接。 @api.multi def closeConnection(self, conn): conn.close() # 创建发票数据 @api.multi def create_hospital_invoice2(self, conn, type, period): cursor = conn.cursor() if type == 'outpatient': sql = "select vak01,vaa01,fab03,vak08,vak07,vaa07 from VAK1 WHERE vak06='2' and vak13>='%s' and vak13<'%s' ;" else: sql = "select vak01,vaa01,fab03,vak08,vak07,vaa07 from VAK1 WHERE vak06>'2' and vak13>='%s' and vak13<'%s' ;" star_date, end_date = self.env[ 'finance.period'].get_period_month_date_range(period.name) star_date = '%s 00:00:00' % (star_date) end_date = '%s 23:59:59' % (end_date) cursor.execute(sql % (star_date, end_date)) invoice_ids = cursor.fetchall() for invoice in invoice_ids: internal_id, coustorm_id, invoice_name, amount, difference_amount, caseid = invoice name, name_id = self.search_coustorm(conn, coustorm_id) if self.search_pay_type(conn, caseid): pay_type = self.search_pay_type(conn, caseid)[0] else: pay_type = '' invoice_id = self.env['hospital.invoice'].create({ 'internal_id': internal_id, 'name': name.encode('latin-1').decode('gbk'), 'invoice': invoice_name, 'name_id': name_id and name_id.encode('latin-1').decode('gbk') or '', 'pay_type': pay_type.encode('latin-1').decode('gbk'), 'amount': amount, 'difference_amount': difference_amount, 'type': self.type, 'month_id': period.id or '', }) if type == 'outpatient': self.crate_hospital_invoice_cost2(conn, invoice_id, internal_id) else: self.crate_hospital_invoice_cost(conn, invoice_id, internal_id) self.crate_hospital_invoice_pay(conn, invoice_id, internal_id) self.crate_hospital_invoice_line(conn, invoice_id) @api.multi def crate_hospital_invoice_line(self, conn, invoice_id): # 合并正负发票 invoice_line = [] for line in invoice_id.cost_ids: if line.cost_type in invoice_line: old_amount = self.env['hospital.invoice.line'].search( [('invoice_id', '=', invoice_id.id), ('name', '=', line.cost_type)], limit=1) amount = old_amount.amount + line.amount old_amount.write({'amount': amount}) else: self.env['hospital.invoice.line'].create({ 'invoice_id': invoice_id.id, 'name': line.cost_type, 'amount': line.amount, 'type': invoice_id.type, }) invoice_line.append(line.cost_type) @api.multi def crate_hospital_invoice_cost(self, conn, invoice_id, internal_id): cursor = conn.cursor() sql = "select BBY01,VAJ36,VAJ35,VAJ25,VAJ46 from VAJ2 WHERE ACF01 = '2' AND vak01='%s';" cursor.execute(sql % internal_id) cost_ids = cursor.fetchall() for cost_id in cost_ids: m, amount, unit, number, cost_time = cost_id code, name, name2 = self.search_cost_nameall(conn, m) cost_type = self.search_cost_type(conn, code)[0] self.env['hospital.invoice.cost'].create({ 'invoice_id': invoice_id.id, 'cost_type': cost_type.encode('latin-1').decode('gbk'), 'name': name.encode('latin-1').decode('gbk'), 'name2': name2 and name2.encode('latin-1').decode('gbk') or '', 'number': number, 'unit': unit.encode('latin-1').decode('gbk'), 'amount': amount, 'cost_time': cost_time, 'type': self.type, }) @api.multi def crate_hospital_invoice_cost2(self, conn, invoice_id, internal_id): cursor = conn.cursor() sql = "select BBY01,VAJ38,VAJ35,VAJ25,VAJ46 from VAJ1 WHERE VAK01='%s';" cursor.execute(sql % internal_id) cost_ids = cursor.fetchall() for cost_id in cost_ids: m, amount, unit, number, cost_time = cost_id code, name, name2 = self.search_cost_nameall(conn, m) cost_type = self.search_cost_type(conn, code)[0] self.env['hospital.invoice.cost'].create({ 'invoice_id': invoice_id.id, 'cost_type': cost_type.encode('latin-1').decode('gbk'), 'name': name.encode('latin-1').decode('gbk'), 'name2': name2 and name2.encode('latin-1').decode('gbk') or '', 'number': number, 'unit': unit.encode('latin-1').decode('gbk'), 'amount': amount, 'cost_time': cost_time, 'type': self.type, }) @api.multi def crate_hospital_invoice_pay(self, conn, invoice_id, internal_id): cursor = conn.cursor() sql = "select VBL14,VBL13 from VBL1 WHERE vak01='%s';" cursor.execute(sql % internal_id) pay_ids = cursor.fetchall() for pay_id in pay_ids: name, amount = pay_id self.env['hospital.pay.line'].create({ 'invoice_id': invoice_id.id, 'name': name.encode('latin-1').decode('gbk'), 'amount': amount, 'type': self.type, }) return True # 查询病人信息数据 @api.multi def search_coustorm(self, conn, name_id): cursor = conn.cursor() sql = "select VAA05,VAA15 from VAA1 WHERE VAA01='%s';" cursor.execute(sql % name_id) name_code = cursor.fetchone() return name_code # 查询病人信息数据 @api.multi def search_pay_type(self, conn, name_id): cursor = conn.cursor() sql = "select BDP02 from VAE1 WHERE VAE01='%s';" cursor.execute(sql % name_id) name_code = cursor.fetchone() return name_code # 查询药品信息数据 @api.multi def search_cost_nameall(self, conn, name_id): cursor = conn.cursor() sql = "select ABF01,BBY05,bby06 from BBY1 WHERE BBY01='%s';" cursor.execute(sql % name_id) name_code = cursor.fetchone() return name_code # 查询药品费别数据 @api.multi def search_cost_type(self, conn, name_id): cursor = conn.cursor() sql = "select ABF02 from ABF1 WHERE ABF01='%s';" cursor.execute(sql % name_id) name_code = cursor.fetchone() return name_code
class ResConfigSettings(models.TransientModel): _inherit = 'res.config.settings' theme_background_image = fields.Binary( related="company_id.background_image", readonly=False, required=True) theme_color_brand = fields.Char(string="Brand Color") theme_color_primary = fields.Char(string="Primary Color") @api.multi def set_values(self): res = super(ResConfigSettings, self).set_values() self._save_scss_values() return res @api.model def get_values(self): res = super(ResConfigSettings, self).get_values() res.update(self._get_scss_values()) return res def _get_custom_scss_url(self, url, xmlid): return self._build_custom_scss_url(url.rsplit(".", 1), xmlid) def _build_custom_scss_url(self, url_parts, xmlid): return "%s.custom.%s.%s" % (url_parts[0], xmlid, url_parts[1]) def _get_custom_attachment(self, url): return self.env["ir.attachment"].search([("url", '=', url)]) def _get_scss_values(self): custom_url = self._get_custom_scss_url(SCSS_URL, XML_ID) custom_attachment = self._get_custom_attachment(custom_url) if custom_attachment.exists(): content = str(base64.b64decode(custom_attachment.datas)) brand = re.search(r'o-brand-odoo\:?\s(.*?);', content) primary = re.search(r'o-brand-primary\:?\s(.*?);', content) return { 'theme_color_brand': brand and brand.group(1) or "#243742", 'theme_color_primary': primary and primary.group(1) or "#5D8DA8", } else: return { 'theme_color_brand': "#243742", 'theme_color_primary': "#5D8DA8", } def _build_custom_scss_template(self): return TEMPLATE.format(self.theme_color_brand or "#243742", self.theme_color_primary or "#5D8DA8") def _save_scss_values(self): custom_url = self._get_custom_scss_url(SCSS_URL, XML_ID) custom_attachment = self._get_custom_attachment(custom_url) custom_content = self._build_custom_scss_template() datas = base64.b64encode((custom_content).encode("utf-8")) if custom_attachment: custom_attachment.write({"datas": datas}) else: self.env["ir.attachment"].create({ 'name': custom_url, 'type': "binary", 'mimetype': "text/scss", 'datas': datas, 'datas_fname': SCSS_URL.split("/")[-1], 'url': custom_url, }) view_to_xpath = self.env["ir.ui.view"].get_related_views( XML_ID, bundles=True).filtered(lambda v: v.arch.find(SCSS_URL) >= 0) self.env["ir.ui.view"].create({ 'name': custom_url, 'key': 'web_editor.scss_%s' % str(uuid.uuid4())[:6], 'mode': "extension", 'inherit_id': view_to_xpath.id, 'arch': """ <data inherit_id="%(inherit_xml_id)s" name="%(name)s"> <xpath expr="//link[@href='%(url_to_replace)s']" position="attributes"> <attribute name="href">%(new_url)s</attribute> </xpath> </data> """ % { 'inherit_xml_id': view_to_xpath.xml_id, 'name': custom_url, 'url_to_replace': SCSS_URL, 'new_url': custom_url, } }) self.env["ir.qweb"].clear_caches()
class ConfigProduct(models.Model): _name="configurateur.config" total_price = fields.Float("Cout Total", default=0) variant_line_ids = fields.Many2many("configurateur_product.line") config_image = fields.Binary("Image", attachment=True)
class BankReport(models.TransientModel): _name = 'bank.report' _description = 'Reporte para pago banco' stock_quant_id = fields.Char("LFC") data = fields.Binary("Archivo") data_name = fields.Char("nombre del archivo") secuencia = fields.Char("Aplicacion", default='A1') aplicacion = fields.Selection(string="tipo de pago", selection=[('I', 'Inmediata'), ('M', 'Medio dia'), ('N', 'Noche')]) descripcion = fields.Char("Descripcion") journal = fields.Many2one('account.journal', string='Diario') tipo_pago = fields.Selection(string="tipo de pago", selection=[('104', 'Pago a Proveedores'), ('98', 'Pago de Nomina')]) fecha_aplicacion = fields.Date('Fecha de Aplicacion') asientos = fields.Many2many('account.move', string='Asientos', required=True) exist_asientos = fields.Boolean(string='Asientos existentes', compute='get_data_asientos') @api.onchange('asientos') def get_data_asientos(self): if self.asientos: self.exist_asientos = True else: self.exist_asientos = False @api.onchange('journal', 'tipo_pago') def onchange_journal(self): for rec in self: return { 'domain': { 'asientos': [('name', 'like', 'CE'), ('journal_id', '=', rec.journal.id), '|', ('partner_id.category_id.id', '=', int(rec.tipo_pago)), ('partner_id.category_id.parent_id', '=', int(rec.tipo_pago))] } } def do_report(self): _logger.error("INICIA LA FUNCIÓN GENERAR EL REPORTE ") self.make_file() return { 'type': 'ir.actions.act_url', 'url': '/web/binary/download_document?model=bank.report&field=data&id=%s&filename=%s' % (self.id, self.data_name), 'target': 'new', 'nodestroy': False, } def make_file(self): _logger.error("INICIA LA FUNCIÓN CONSTRUIR EL ARCHIVO ") account = self.asientos if not account: raise Warning( _('!No hay resultados para los datos seleccionados¡')) buf = BytesIO() wb = xlsxwriter.Workbook(buf) ws = wb.add_worksheet('Report') # formatos title_head = wb.add_format({ 'bold': 1, 'border': 1, 'align': 'rigth', 'fg_color': '#33CCCC', 'valign': 'vcenter', }) title_head.set_font_name('Arial') title_head.set_font_size(10) title_head.set_font_color('#ffffff') company = self.env['res.company'].search([]) ws.write(0, 0, 'NIT PAGADOR', title_head) ws.write(0, 1, 'TIPO DE PAGO', title_head) ws.write(0, 2, 'APLICACIÓN', title_head) ws.write(0, 3, 'SECUENCIA DE ENVIO', title_head) ws.write(0, 4, 'NRO CUENTA A DEBITAR', title_head) ws.write(0, 5, 'TIPO DE CUENTA A DEBITAR', title_head) ws.write(0, 6, 'DESCRIPCIÓN DEL PAGO', title_head) ws.write(1, 0, '' if not company[0].vat else company[0].vat) ws.write(1, 1, self.tipo_pago) if self.tipo_pago: if self.tipo_pago == '104': ws.write(1, 1, '220') elif self.tipo_pago == '98': ws.write(1, 1, '225') else: ws.write(1, 1, '') else: ws.write(1, 1, '') ws.write(1, 2, self.aplicacion) ws.write(1, 3, self.secuencia) ws.write(1, 4, self.journal.bank_account_id.acc_number) if self.journal.bank_account_id.account_type: if self.journal.bank_account_id.account_type == '1': ws.write(1, 5, 'S') elif self.journal.bank_account_id.account_type == '2': ws.write(1, 5, 'D') else: ws.write(1, 5, '') else: ws.write(1, 5, '') ws.write(1, 6, self.descripcion) ws.write(2, 0, 'Tipo Documento Beneficiario', title_head) ws.write(2, 1, 'Nit Beneficiario', title_head) ws.write(2, 2, 'Nombre Beneficiario ', title_head) ws.write(2, 3, 'Tipo Transaccion', title_head) ws.write(2, 4, 'Código Banco', title_head) ws.write(2, 5, 'No Cuenta Beneficiario', title_head) ws.write(2, 6, 'Email', title_head) ws.write(2, 7, 'Documento Autorizado', title_head) ws.write(2, 8, 'Referencia', title_head) ws.write(2, 9, 'OficinaEntrega', title_head) ws.write(2, 10, 'ValorTransaccion', title_head) ws.write(2, 11, 'Fecha de aplicación', title_head) fila = 3 for ac in account: vat = ac.partner_id.vat if ac.partner_id.l10n_co_document_type: if ac.partner_id.l10n_co_document_type == 'id_document': ws.write(fila, 0, '1') pos = (ac.partner_id.vat).find("-") if pos != -1: vat = ac.partner_id.vat[0:pos] else: vat = ac.partner_id.vat elif ac.partner_id.l10n_co_document_type == 'foreign_id_card': ws.write(fila, 0, '2') elif ac.partner_id.l10n_co_document_type == 'rut': ws.write(fila, 0, '3') pos = (ac.partner_id.vat).find("-") if pos != -1: vat = ac.partner_id.vat[0:pos] else: vat = ac.partner_id.vat elif ac.partner_id.l10n_co_document_type == 'id_card': ws.write(fila, 0, '4') elif ac.partner_id.l10n_co_document_type == 'passport': ws.write(fila, 0, '5') else: ws.write(fila, 0, '') else: ws.write(fila, 0, '') ws.write(fila, 1, '' if not vat else vat.replace(".", "")) ws.write(fila, 2, ac.partner_id.name) if ac.partner_id.bank_ids: if ac.partner_id.bank_ids[0].account_type == '1': ws.write(fila, 3, '27') elif ac.partner_id.bank_ids[0].account_type == '2': ws.write(fila, 3, '37') else: ws.write(fila, 3, '') else: ws.write(fila, 3, '') ws.write(fila, 4, '') if not ac.partner_id.bank_ids else ws.write( fila, 4, ac.partner_id.bank_ids[0].bank_id.code_bank) ws.write(fila, 5, '') if not ac.partner_id.bank_ids else ws.write( fila, 5, ac.partner_id.bank_ids[0].acc_number) ws.write(fila, 6, '') ws.write(fila, 7, '') ws.write(fila, 8, '') ws.write(fila, 9, '') ws.write(fila, 10, "{:.2f}".format(ac.amount_total)) ws.write(fila, 11, str(self.fecha_aplicacion.isoformat()).replace("-", "")) fila += 1 try: wb.close() out = base64.encodestring(buf.getvalue()) buf.close() self.data = out self.data_name = 'Reporte pago bancos' + ".xls" except ValueError: raise Warning('No se pudo generar el archivo')
class MrpWorkorder(models.Model): _name = 'mrp.workorder' _description = 'Work Order' _inherit = ['mail.thread', 'mail.activity.mixin', 'mrp.abstract.workorder'] name = fields.Char( 'Work Order', required=True, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}) workcenter_id = fields.Many2one( 'mrp.workcenter', 'Work Center', required=True, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}) working_state = fields.Selection( 'Workcenter Status', related='workcenter_id.working_state', readonly=False, help='Technical: used in views only') production_availability = fields.Selection( 'Stock Availability', readonly=True, related='production_id.reservation_state', store=True, help='Technical: used in views and domains only.') production_state = fields.Selection( 'Production State', readonly=True, related='production_id.state', help='Technical: used in views only.') qty_production = fields.Float('Original Production Quantity', readonly=True, related='production_id.product_qty') qty_remaining = fields.Float('Quantity To Be Produced', compute='_compute_qty_remaining', digits='Product Unit of Measure') qty_produced = fields.Float( 'Quantity', default=0.0, readonly=True, digits='Product Unit of Measure', help="The number of products already handled by this work order") is_produced = fields.Boolean(string="Has Been Produced", compute='_compute_is_produced') state = fields.Selection([ ('pending', 'Waiting for another WO'), ('ready', 'Ready'), ('progress', 'In Progress'), ('done', 'Finished'), ('cancel', 'Cancelled')], string='Status', default='pending') leave_id = fields.Many2one( 'resource.calendar.leaves', help='Slot into workcenter calendar once planned') date_planned_start = fields.Datetime( 'Scheduled Date Start', compute='_compute_dates_planned', inverse='_set_dates_planned', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, store=True) date_planned_finished = fields.Datetime( 'Scheduled Date Finished', compute='_compute_dates_planned', inverse='_set_dates_planned', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, store=True) date_start = fields.Datetime( 'Effective Start Date', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}) date_finished = fields.Datetime( 'Effective End Date', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}) duration_expected = fields.Float( 'Expected Duration', digits=(16, 2), states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, help="Expected duration (in minutes)") duration = fields.Float( 'Real Duration', compute='_compute_duration', readonly=True, store=True) duration_unit = fields.Float( 'Duration Per Unit', compute='_compute_duration', readonly=True, store=True) duration_percent = fields.Integer( 'Duration Deviation (%)', compute='_compute_duration', group_operator="avg", readonly=True, store=True) operation_id = fields.Many2one( 'mrp.routing.workcenter', 'Operation') # Should be used differently as BoM can change in the meantime worksheet = fields.Binary( 'Worksheet', related='operation_id.worksheet', readonly=True) worksheet_type = fields.Selection( 'Worksheet Type', related='operation_id.worksheet_type', readonly=True) worksheet_google_slide = fields.Char( 'Worksheet URL', related='operation_id.worksheet_google_slide', readonly=True) move_raw_ids = fields.One2many( 'stock.move', 'workorder_id', 'Raw Moves', domain=[('raw_material_production_id', '!=', False), ('production_id', '=', False)]) move_finished_ids = fields.One2many( 'stock.move', 'workorder_id', 'Finished Moves', domain=[('raw_material_production_id', '=', False), ('production_id', '!=', False)]) move_line_ids = fields.One2many( 'stock.move.line', 'workorder_id', 'Moves to Track', help="Inventory moves for which you must scan a lot number at this work order") finished_lot_id = fields.Many2one( 'stock.production.lot', 'Lot/Serial Number', domain="[('product_id', '=', product_id)]", states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}) time_ids = fields.One2many( 'mrp.workcenter.productivity', 'workorder_id') is_user_working = fields.Boolean( 'Is the Current User Working', compute='_compute_working_users', help="Technical field indicating whether the current user is working. ") working_user_ids = fields.One2many('res.users', string='Working user on this work order.', compute='_compute_working_users') last_working_user_id = fields.One2many('res.users', string='Last user that worked on this work order.', compute='_compute_working_users') next_work_order_id = fields.Many2one('mrp.workorder', "Next Work Order") scrap_ids = fields.One2many('stock.scrap', 'workorder_id') scrap_count = fields.Integer(compute='_compute_scrap_move_count', string='Scrap Move') production_date = fields.Datetime('Production Date', related='production_id.date_planned_start', store=True, readonly=False) color = fields.Integer('Color', compute='_compute_color') capacity = fields.Float( 'Capacity', default=1.0, help="Number of pieces that can be produced in parallel.") raw_workorder_line_ids = fields.One2many('mrp.workorder.line', 'raw_workorder_id', string='Components') finished_workorder_line_ids = fields.One2many('mrp.workorder.line', 'finished_workorder_id', string='By-products') allowed_lots_domain = fields.One2many(comodel_name='stock.production.lot', compute="_compute_allowed_lots_domain") # Both `date_planned_start` and `date_planned_finished` are related fields on `leave_id`. Let's say # we slide a workorder on a gantt view, a single call to write is made with both # fields Changes. As the ORM doesn't batch the write on related fields and instead # makes multiple call, the constraint check_dates() is raised. # That's why the compute and set methods are needed. to ensure the dates are updated # in the same time. @api.depends('leave_id') def _compute_dates_planned(self): for workorder in self: workorder.date_planned_start = workorder.leave_id.date_from workorder.date_planned_finished = workorder.leave_id.date_to def _set_dates_planned(self): date_from = self[0].date_planned_start date_to = self[0].date_planned_finished self.mapped('leave_id').write({ 'date_from': date_from, 'date_to': date_to, }) @api.onchange('date_planned_start') def _onchange_date_planned_start(self): if self.duration_expected: time_delta = timedelta(minutes=self.duration_expected) else: time_delta = timedelta(hours=1) self.update({'date_planned_finished': self.date_planned_start + time_delta}) @api.onchange('finished_lot_id') def _onchange_finished_lot_id(self): """When the user changes the lot being currently produced, suggest a quantity to produce consistent with the previous workorders. """ previous_wo = self.env['mrp.workorder'].search([ ('next_work_order_id', '=', self.id) ]) if previous_wo: line = previous_wo.finished_workorder_line_ids.filtered(lambda line: line.product_id == self.product_id and line.lot_id == self.finished_lot_id) if line: self.qty_producing = line.qty_done @api.depends('production_id.workorder_ids.finished_workorder_line_ids', 'production_id.workorder_ids.finished_workorder_line_ids.qty_done', 'production_id.workorder_ids.finished_workorder_line_ids.lot_id') def _compute_allowed_lots_domain(self): """ Check if all the finished products has been assigned to a serial number or a lot in other workorders. If yes, restrict the selectable lot to the lot/sn used in other workorders. """ productions = self.mapped('production_id') treated = self.browse() for production in productions: if production.product_id.tracking == 'none': continue rounding = production.product_uom_id.rounding finished_workorder_lines = production.workorder_ids.mapped('finished_workorder_line_ids').filtered(lambda wl: wl.product_id == production.product_id) qties_done_per_lot = defaultdict(list) for finished_workorder_line in finished_workorder_lines: # It is possible to have finished workorder lines without a lot (eg using the dummy # test type). Ignore them when computing the allowed lots. if finished_workorder_line.lot_id: qties_done_per_lot[finished_workorder_line.lot_id.id].append(finished_workorder_line.qty_done) qty_to_produce = production.product_qty allowed_lot_ids = self.env['stock.production.lot'] qty_produced = sum([max(qty_dones) for qty_dones in qties_done_per_lot.values()]) if float_compare(qty_produced, qty_to_produce, precision_rounding=rounding) < 0: # If we haven't produced enough, all lots are available allowed_lot_ids = self.env['stock.production.lot'].search([('product_id', '=', production.product_id.id)]) else: # If we produced enough, only the already produced lots are available allowed_lot_ids = self.env['stock.production.lot'].browse(qties_done_per_lot.keys()) workorders = production.workorder_ids.filtered(lambda wo: wo.state not in ('done', 'cancel')) for workorder in workorders: if workorder.product_tracking == 'serial': workorder.allowed_lots_domain = allowed_lot_ids - workorder.finished_workorder_line_ids.filtered(lambda wl: wl.product_id == production.product_id).mapped('lot_id') else: workorder.allowed_lots_domain = allowed_lot_ids treated |= workorder (self - treated).allowed_lots_domain = False def name_get(self): return [(wo.id, "%s - %s - %s" % (wo.production_id.name, wo.product_id.name, wo.name)) for wo in self] def unlink(self): # Removes references to workorder to avoid Validation Error (self.mapped('move_raw_ids') | self.mapped('move_finished_ids')).write({'workorder_id': False}) self.mapped('leave_id').unlink() return super(MrpWorkorder, self).unlink() @api.depends('production_id.product_qty', 'qty_produced') def _compute_is_produced(self): for order in self: rounding = order.production_id.product_uom_id.rounding order.is_produced = float_compare(order.qty_produced, order.production_id.product_qty, precision_rounding=rounding) >= 0 @api.depends('time_ids.duration', 'qty_produced') def _compute_duration(self): for order in self: order.duration = sum(order.time_ids.mapped('duration')) order.duration_unit = round(order.duration / max(order.qty_produced, 1), 2) # rounding 2 because it is a time if order.duration_expected: order.duration_percent = 100 * (order.duration_expected - order.duration) / order.duration_expected else: order.duration_percent = 0 def _compute_working_users(self): """ Checks whether the current user is working, all the users currently working and the last user that worked. """ for order in self: order.working_user_ids = [(4, order.id) for order in order.time_ids.filtered(lambda time: not time.date_end).sorted('date_start').mapped('user_id')] if order.working_user_ids: order.last_working_user_id = order.working_user_ids[-1] elif order.time_ids: order.last_working_user_id = order.time_ids.sorted('date_end')[-1].user_id if order.time_ids.filtered(lambda x: (x.user_id.id == self.env.user.id) and (not x.date_end) and (x.loss_type in ('productive', 'performance'))): order.is_user_working = True else: order.is_user_working = False def _compute_scrap_move_count(self): data = self.env['stock.scrap'].read_group([('workorder_id', 'in', self.ids)], ['workorder_id'], ['workorder_id']) count_data = dict((item['workorder_id'][0], item['workorder_id_count']) for item in data) for workorder in self: workorder.scrap_count = count_data.get(workorder.id, 0) @api.depends('date_planned_finished', 'production_id.date_planned_finished') def _compute_color(self): late_orders = self.filtered(lambda x: x.production_id.date_planned_finished and x.date_planned_finished > x.production_id.date_planned_finished) for order in late_orders: order.color = 4 for order in (self - late_orders): order.color = 2 def write(self, values): if list(values.keys()) != ['time_ids'] and any(workorder.state == 'done' for workorder in self): raise UserError(_('You can not change the finished work order.')) if 'date_planned_start' in values or 'date_planned_finished' in values: for workorder in self: start_date = fields.Datetime.to_datetime(values.get('date_planned_start')) or workorder.date_planned_start end_date = fields.Datetime.to_datetime(values.get('date_planned_finished')) or workorder.date_planned_finished if start_date and end_date and start_date > end_date: raise UserError(_('The planned end date of the work order cannot be prior to the planned start date, please correct this to save the work order.')) # Update MO dates if the start date of the first WO or the # finished date of the last WO is update. if workorder == workorder.production_id.workorder_ids[0] and 'date_planned_start' in values: workorder.production_id.with_context(force_date=True).write({ 'date_planned_start': values['date_planned_start'] }) if workorder == workorder.production_id.workorder_ids[-1] and 'date_planned_finished' in values: workorder.production_id.with_context(force_date=True).write({ 'date_planned_finished': values['date_planned_finished'] }) return super(MrpWorkorder, self).write(values) def _generate_wo_lines(self): """ Generate workorder line """ self.ensure_one() moves = (self.move_raw_ids | self.move_finished_ids).filtered( lambda move: move.state not in ('done', 'cancel') ) for move in moves: qty_to_consume = self._prepare_component_quantity(move, self.qty_producing) line_values = self._generate_lines_values(move, qty_to_consume) self.env['mrp.workorder.line'].create(line_values) def _apply_update_workorder_lines(self): """ update existing line on the workorder. It could be trigger manually after a modification of qty_producing. """ self.ensure_one() line_values = self._update_workorder_lines() self.env['mrp.workorder.line'].create(line_values['to_create']) if line_values['to_delete']: line_values['to_delete'].unlink() for line, vals in line_values['to_update'].items(): line.write(vals) def _refresh_wo_lines(self): """ Modify exisiting workorder line in order to match the reservation on stock move line. The strategy is to remove the line that were not processed yet then call _generate_lines_values that recreate workorder line depending the reservation. """ for workorder in self: raw_moves = workorder.move_raw_ids.filtered( lambda move: move.state not in ('done', 'cancel') ) wl_to_unlink = self.env['mrp.workorder.line'] for move in raw_moves: rounding = move.product_uom.rounding qty_already_consumed = 0.0 workorder_lines = workorder.raw_workorder_line_ids.filtered(lambda w: w.move_id == move) for wl in workorder_lines: if not wl.qty_done: wl_to_unlink |= wl continue qty_already_consumed += wl.qty_done qty_to_consume = self._prepare_component_quantity(move, workorder.qty_producing) wl_to_unlink.unlink() if float_compare(qty_to_consume, qty_already_consumed, precision_rounding=rounding) > 0: line_values = workorder._generate_lines_values(move, qty_to_consume - qty_already_consumed) self.env['mrp.workorder.line'].create(line_values) def _defaults_from_finished_workorder_line(self, reference_lot_lines): for r_line in reference_lot_lines: # see which lot we could suggest and its related qty_producing if not r_line.lot_id: continue candidates = self.finished_workorder_line_ids.filtered(lambda line: line.lot_id == r_line.lot_id) rounding = self.product_uom_id.rounding if not candidates: self.write({ 'finished_lot_id': r_line.lot_id.id, 'qty_producing': r_line.qty_done, }) return True elif float_compare(candidates.qty_done, r_line.qty_done, precision_rounding=rounding) < 0: self.write({ 'finished_lot_id': r_line.lot_id.id, 'qty_producing': r_line.qty_done - candidates.qty_done, }) return True return False def record_production(self): if not self: return True self.ensure_one() if float_compare(self.qty_producing, 0, precision_rounding=self.product_uom_id.rounding) <= 0: raise UserError(_('Please set the quantity you are currently producing. It should be different from zero.')) # If last work order, then post lots used if not self.next_work_order_id: self._update_finished_move() # Transfer quantities from temporary to final move line or make them final self._update_moves() # Transfer lot (if present) and quantity produced to a finished workorder line if self.product_tracking != 'none': self._create_or_update_finished_line() # Update workorder quantity produced self.qty_produced += self.qty_producing # Suggest a finished lot on the next workorder if self.next_work_order_id and self.production_id.product_id.tracking != 'none' and not self.next_work_order_id.finished_lot_id: self.next_work_order_id._defaults_from_finished_workorder_line(self.finished_workorder_line_ids) # As we may have changed the quantity to produce on the next workorder, # make sure to update its wokorder lines self.next_work_order_id._apply_update_workorder_lines() # One a piece is produced, you can launch the next work order self._start_nextworkorder() # Test if the production is done rounding = self.production_id.product_uom_id.rounding if float_compare(self.qty_produced, self.production_id.product_qty, precision_rounding=rounding) < 0: previous_wo = self.env['mrp.workorder'] if self.product_tracking != 'none': previous_wo = self.env['mrp.workorder'].search([ ('next_work_order_id', '=', self.id) ]) candidate_found_in_previous_wo = False if previous_wo: candidate_found_in_previous_wo = self._defaults_from_finished_workorder_line(previous_wo.finished_workorder_line_ids) if not candidate_found_in_previous_wo: # self is the first workorder self.qty_producing = self.qty_remaining self.finished_lot_id = False if self.product_tracking == 'serial': self.qty_producing = 1 self._apply_update_workorder_lines() else: self.qty_producing = 0 self.button_finish() return True def _get_byproduct_move_to_update(self): return self.production_id.move_finished_ids.filtered(lambda x: (x.product_id.id != self.production_id.product_id.id) and (x.state not in ('done', 'cancel'))) def _create_or_update_finished_line(self): """ 1. Check that the final lot and the quantity producing is valid regarding other workorders of this production 2. Save final lot and quantity producing to suggest on next workorder """ self.ensure_one() final_lot_quantity = self.qty_production rounding = self.product_uom_id.rounding # Get the max quantity possible for current lot in other workorders for workorder in (self.production_id.workorder_ids - self): # We add the remaining quantity to the produced quantity for the # current lot. For 5 finished products: if in the first wo it # creates 4 lot A and 1 lot B and in the second it create 3 lot A # and it remains 2 units to product, it could produce 5 lot A. # In this case we select 4 since it would conflict with the first # workorder otherwise. line = workorder.finished_workorder_line_ids.filtered(lambda line: line.lot_id == self.finished_lot_id) line_without_lot = workorder.finished_workorder_line_ids.filtered(lambda line: line.product_id == workorder.product_id and not line.lot_id) quantity_remaining = workorder.qty_remaining + line_without_lot.qty_done quantity = line.qty_done + quantity_remaining if line and float_compare(quantity, final_lot_quantity, precision_rounding=rounding) <= 0: final_lot_quantity = quantity elif float_compare(quantity_remaining, final_lot_quantity, precision_rounding=rounding) < 0: final_lot_quantity = quantity_remaining # final lot line for this lot on this workorder. current_lot_lines = self.finished_workorder_line_ids.filtered(lambda line: line.lot_id == self.finished_lot_id) # this lot has already been produced if float_compare(final_lot_quantity, current_lot_lines.qty_done + self.qty_producing, precision_rounding=rounding) < 0: raise UserError(_('You have produced %s %s of lot %s in the previous workorder. You are trying to produce %s in this one') % (final_lot_quantity, self.product_id.uom_id.name, self.finished_lot_id.name, current_lot_lines.qty_done + self.qty_producing)) # Update workorder line that regiter final lot created if not current_lot_lines: current_lot_lines = self.env['mrp.workorder.line'].create({ 'finished_workorder_id': self.id, 'product_id': self.product_id.id, 'lot_id': self.finished_lot_id.id, 'qty_done': self.qty_producing, }) else: current_lot_lines.qty_done += self.qty_producing def _start_nextworkorder(self): rounding = self.product_id.uom_id.rounding if self.next_work_order_id.state == 'pending' and ( (self.operation_id.batch == 'no' and float_compare(self.qty_production, self.qty_produced, precision_rounding=rounding) <= 0) or (self.operation_id.batch == 'yes' and float_compare(self.operation_id.batch_size, self.qty_produced, precision_rounding=rounding) <= 0)): self.next_work_order_id.state = 'ready' def button_start(self): self.ensure_one() # As button_start is automatically called in the new view if self.state in ('done', 'cancel'): return True # Need a loss in case of the real time exceeding the expected timeline = self.env['mrp.workcenter.productivity'] if self.duration < self.duration_expected: loss_id = self.env['mrp.workcenter.productivity.loss'].search([('loss_type','=','productive')], limit=1) if not len(loss_id): raise UserError(_("You need to define at least one productivity loss in the category 'Productivity'. Create one from the Manufacturing app, menu: Configuration / Productivity Losses.")) else: loss_id = self.env['mrp.workcenter.productivity.loss'].search([('loss_type','=','performance')], limit=1) if not len(loss_id): raise UserError(_("You need to define at least one productivity loss in the category 'Performance'. Create one from the Manufacturing app, menu: Configuration / Productivity Losses.")) if self.production_id.state != 'progress': self.production_id.write({ 'date_start': datetime.now(), }) timeline.create({ 'workorder_id': self.id, 'workcenter_id': self.workcenter_id.id, 'description': _('Time Tracking: ')+self.env.user.name, 'loss_id': loss_id[0].id, 'date_start': datetime.now(), 'user_id': self.env.user.id }) return self.write({'state': 'progress', 'date_start': datetime.now(), }) def button_finish(self): self.ensure_one() self.end_all() return self.write({'state': 'done', 'date_finished': fields.Datetime.now()}) def end_previous(self, doall=False): """ @param: doall: This will close all open time lines on the open work orders when doall = True, otherwise only the one of the current user """ # TDE CLEANME timeline_obj = self.env['mrp.workcenter.productivity'] domain = [('workorder_id', 'in', self.ids), ('date_end', '=', False)] if not doall: domain.append(('user_id', '=', self.env.user.id)) not_productive_timelines = timeline_obj.browse() for timeline in timeline_obj.search(domain, limit=None if doall else 1): wo = timeline.workorder_id if wo.duration_expected <= wo.duration: if timeline.loss_type == 'productive': not_productive_timelines += timeline timeline.write({'date_end': fields.Datetime.now()}) else: maxdate = fields.Datetime.from_string(timeline.date_start) + relativedelta(minutes=wo.duration_expected - wo.duration) enddate = datetime.now() if maxdate > enddate: timeline.write({'date_end': enddate}) else: timeline.write({'date_end': maxdate}) not_productive_timelines += timeline.copy({'date_start': maxdate, 'date_end': enddate}) if not_productive_timelines: loss_id = self.env['mrp.workcenter.productivity.loss'].search([('loss_type', '=', 'performance')], limit=1) if not len(loss_id): raise UserError(_("You need to define at least one unactive productivity loss in the category 'Performance'. Create one from the Manufacturing app, menu: Configuration / Productivity Losses.")) not_productive_timelines.write({'loss_id': loss_id.id}) return True def end_all(self): return self.end_previous(doall=True) def button_pending(self): self.end_previous() return True def button_unblock(self): for order in self: order.workcenter_id.unblock() return True def action_cancel(self): return self.write({'state': 'cancel'}) def button_done(self): if any([x.state in ('done', 'cancel') for x in self]): raise UserError(_('A Manufacturing Order is already done or cancelled.')) self.end_all() return self.write({'state': 'done', 'date_finished': datetime.now()}) def button_scrap(self): self.ensure_one() return { 'name': _('Scrap'), 'view_mode': 'form', 'res_model': 'stock.scrap', 'view_id': self.env.ref('stock.stock_scrap_form_view2').id, 'type': 'ir.actions.act_window', 'context': {'default_company_id': self.production_id.company_id.id, 'default_workorder_id': self.id, 'default_production_id': self.production_id.id, 'product_ids': (self.production_id.move_raw_ids.filtered(lambda x: x.state not in ('done', 'cancel')) | self.production_id.move_finished_ids.filtered(lambda x: x.state == 'done')).mapped('product_id').ids}, 'target': 'new', } def action_see_move_scrap(self): self.ensure_one() action = self.env.ref('stock.action_stock_scrap').read()[0] action['domain'] = [('workorder_id', '=', self.id)] return action @api.depends('qty_production', 'qty_produced') def _compute_qty_remaining(self): for wo in self: wo.qty_remaining = float_round(wo.qty_production - wo.qty_produced, precision_rounding=wo.production_id.product_uom_id.rounding)
class IrUiMenu(models.Model): _name = 'ir.ui.menu' _description = 'Menu' _order = "sequence,id" _parent_store = True def __init__(self, *args, **kwargs): super(IrUiMenu, self).__init__(*args, **kwargs) self.pool['ir.model.access'].register_cache_clearing_method( self._name, 'clear_caches') name = fields.Char(string='Menu', required=True, translate=True) active = fields.Boolean(default=True) sequence = fields.Integer(default=10) child_id = fields.One2many('ir.ui.menu', 'parent_id', string='Child IDs') parent_id = fields.Many2one('ir.ui.menu', string='Parent Menu', index=True, ondelete="restrict") parent_path = fields.Char(index=True) groups_id = fields.Many2many('res.groups', 'ir_ui_menu_group_rel', 'menu_id', 'gid', string='Groups', help="If you have groups, the visibility of this menu will be based on these groups. "\ "If this field is empty, Odoo will compute visibility based on the related object's read access.") complete_name = fields.Char(compute='_compute_complete_name', string='Full Path') web_icon = fields.Char(string='Web Icon File') action = fields.Reference(selection=[( 'ir.actions.report', 'ir.actions.report' ), ('ir.actions.act_window', 'ir.actions.act_window'), ( 'ir.actions.act_url', 'ir.actions.act_url'), ( 'ir.actions.server', 'ir.actions.server'), ('ir.actions.client', 'ir.actions.client')]) web_icon_data = fields.Binary(string='Web Icon Image', attachment=True) @api.depends('name', 'parent_id.complete_name') def _compute_complete_name(self): for menu in self: menu.complete_name = menu._get_full_name() def _get_full_name(self, level=6): """ Return the full name of ``self`` (up to a certain level). """ if level <= 0: return '...' if self.parent_id: return self.parent_id._get_full_name( level - 1) + MENU_ITEM_SEPARATOR + (self.name or "") else: return self.name def read_image(self, path): if not path: return False path_info = path.split(',') icon_path = get_module_resource(path_info[0], path_info[1]) icon_image = False if icon_path: with tools.file_open(icon_path, 'rb') as icon_file: icon_image = base64.encodebytes(icon_file.read()) return icon_image @api.constrains('parent_id') def _check_parent_id(self): if not self._check_recursion(): raise ValidationError( _('Error! You cannot create recursive menus.')) @api.model @tools.ormcache('frozenset(self.env.user.groups_id.ids)', 'debug') def _visible_menu_ids(self, debug=False): """ Return the ids of the menu items visible to the user. """ # retrieve all menus, and determine which ones are visible context = {'ir.ui.menu.full_list': True} menus = self.with_context(context).search([]) groups = self.env.user.groups_id if not debug: groups = groups - self.env.ref('base.group_no_one') # first discard all menus with groups the user does not have menus = menus.filtered( lambda menu: not menu.groups_id or menu.groups_id & groups) # take apart menus that have an action action_menus = menus.filtered(lambda m: m.action and m.action.exists()) folder_menus = menus - action_menus visible = self.browse() # process action menus, check whether their action is allowed access = self.env['ir.model.access'] MODEL_GETTER = { 'ir.actions.act_window': lambda action: action.res_model, 'ir.actions.report': lambda action: action.model, 'ir.actions.server': lambda action: action.model_id.model, } for menu in action_menus: get_model = MODEL_GETTER.get(menu.action._name) if not get_model or not get_model(menu.action) or \ access.check(get_model(menu.action), 'read', False): # make menu visible, and its folder ancestors, too visible += menu menu = menu.parent_id while menu and menu in folder_menus and menu not in visible: visible += menu menu = menu.parent_id return set(visible.ids) @api.returns('self') def _filter_visible_menus(self): """ Filter `self` to only keep the menu items that should be visible in the menu hierarchy of the current user. Uses a cache for speeding up the computation. """ visible_ids = self._visible_menu_ids( request.session.debug if request else False) return self.filtered(lambda menu: menu.id in visible_ids) @api.model def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None): menu_ids = super(IrUiMenu, self)._search(args, offset=0, limit=None, order=order, count=False, access_rights_uid=access_rights_uid) menus = self.browse(menu_ids) if menus: # menu filtering is done only on main menu tree, not other menu lists if not self._context.get('ir.ui.menu.full_list'): menus = menus._filter_visible_menus() if offset: menus = menus[offset:] if limit: menus = menus[:limit] return len(menus) if count else menus.ids def name_get(self): return [(menu.id, menu._get_full_name()) for menu in self] @api.model_create_multi def create(self, vals_list): self.clear_caches() for values in vals_list: if 'web_icon' in values: values['web_icon_data'] = self._compute_web_icon_data( values.get('web_icon')) return super(IrUiMenu, self).create(vals_list) def write(self, values): self.clear_caches() if 'web_icon' in values: values['web_icon_data'] = self._compute_web_icon_data( values.get('web_icon')) return super(IrUiMenu, self).write(values) def _compute_web_icon_data(self, web_icon): """ Returns the image associated to `web_icon`. `web_icon` can either be: - an image icon [module, path] - a built icon [icon_class, icon_color, background_color] and it only has to call `read_image` if it's an image. """ if web_icon and len(web_icon.split(',')) == 2: return self.read_image(web_icon) def unlink(self): # Detach children and promote them to top-level, because it would be unwise to # cascade-delete submenus blindly. We also can't use ondelete=set null because # that is not supported when _parent_store is used (would silently corrupt it). # TODO: ideally we should move them under a generic "Orphans" menu somewhere? extra = {'ir.ui.menu.full_list': True, 'active_test': False} direct_children = self.with_context(**extra).search([('parent_id', 'in', self.ids)]) direct_children.write({'parent_id': False}) self.clear_caches() return super(IrUiMenu, self).unlink() def copy(self, default=None): record = super(IrUiMenu, self).copy(default=default) match = NUMBER_PARENS.search(record.name) if match: next_num = int(match.group(1)) + 1 record.name = NUMBER_PARENS.sub('(%d)' % next_num, record.name) else: record.name = record.name + '(1)' return record @api.model @api.returns('self') def get_user_roots(self): """ Return all root menu ids visible for the user. :return: the root menu ids :rtype: list(int) """ return self.search([('parent_id', '=', False)]) @api.model @tools.ormcache_context('self._uid', keys=('lang', )) def load_menus_root(self): fields = ['name', 'sequence', 'parent_id', 'action', 'web_icon_data'] menu_roots = self.get_user_roots() menu_roots_data = menu_roots.read(fields) if menu_roots else [] menu_root = { 'id': False, 'name': 'root', 'parent_id': [-1, ''], 'children': menu_roots_data, 'all_menu_ids': menu_roots.ids, } menu_roots._set_menuitems_xmlids(menu_root) return menu_root @api.model @tools.ormcache_context('self._uid', 'debug', keys=('lang', )) def load_menus(self, debug): """ Loads all menu items (all applications and their sub-menus). :return: the menu root :rtype: dict('children': menu_nodes) """ fields = [ 'name', 'sequence', 'parent_id', 'action', 'web_icon', 'web_icon_data' ] menu_roots = self.get_user_roots() menu_roots_data = menu_roots.read(fields) if menu_roots else [] menu_root = { 'id': False, 'name': 'root', 'parent_id': [-1, ''], 'children': menu_roots_data, 'all_menu_ids': menu_roots.ids, } if not menu_roots_data: return menu_root # menus are loaded fully unlike a regular tree view, cause there are a # limited number of items (752 when all 6.1 addons are installed) menus = self.search([('id', 'child_of', menu_roots.ids)]) menu_items = menus.read(fields) # add roots at the end of the sequence, so that they will overwrite # equivalent menu items from full menu read when put into id:item # mapping, resulting in children being correctly set on the roots. menu_items.extend(menu_roots_data) menu_root['all_menu_ids'] = menus.ids # includes menu_roots! # make a tree using parent_id menu_items_map = { menu_item["id"]: menu_item for menu_item in menu_items } for menu_item in menu_items: parent = menu_item['parent_id'] and menu_item['parent_id'][0] if parent in menu_items_map: menu_items_map[parent].setdefault('children', []).append(menu_item) # sort by sequence a tree using parent_id for menu_item in menu_items: menu_item.setdefault('children', []).sort(key=operator.itemgetter('sequence')) (menu_roots + menus)._set_menuitems_xmlids(menu_root) return menu_root def _set_menuitems_xmlids(self, menu_root): menuitems = self.env['ir.model.data'].sudo().search([ ('res_id', 'in', self.ids), ('model', '=', 'ir.ui.menu') ]) xmlids = {menu.res_id: menu.complete_name for menu in menuitems} def _set_xmlids(tree, xmlids): tree['xmlid'] = xmlids.get(tree['id'], '') if 'children' in tree: for child in tree['children']: _set_xmlids(child, xmlids) _set_xmlids(menu_root, xmlids)
class Company(models.Model): _name = "res.company" _description = 'Companies' _order = 'sequence, name' @api.multi def copy(self, default=None): raise UserError( _('Duplicating a company is not allowed. Please create a new company instead.' )) def _get_logo(self): return base64.b64encode( open( os.path.join(tools.config['root_path'], 'addons', 'base', 'static', 'img', 'res_company_logo.png'), 'rb').read()) @api.model def _get_euro(self): return self.env['res.currency.rate'].search([('rate', '=', 1)], limit=1).currency_id @api.model def _get_user_currency(self): currency_id = self.env['res.users'].browse( self._uid).company_id.currency_id return currency_id or self._get_euro() def _get_default_favicon(self, original=False): img_path = get_resource_path('web', 'static/src/img/favicon.ico') with tools.file_open(img_path, 'rb') as f: if original: return base64.b64encode(f.read()) # Modify the source image to change the color of the 'O'. # This could seem overkill to modify the pixels 1 by 1, but # Pillow doesn't provide an easy way to do it, and this # is acceptable for a 16x16 image. color = (randrange(32, 224, 24), randrange(32, 224, 24), randrange(32, 224, 24)) original = Image.open(f) new_image = Image.new('RGBA', original.size) for y in range(original.size[1]): for x in range(original.size[0]): pixel = original.getpixel((x, y)) if pixel[0] == 0 and pixel[1] == 0 and pixel[2] == 0: new_image.putpixel((x, y), (0, 0, 0, 0)) else: new_image.putpixel( (x, y), (color[0], color[1], color[2], pixel[3])) stream = io.BytesIO() new_image.save(stream, format="ICO") return base64.b64encode(stream.getvalue()) name = fields.Char(related='partner_id.name', string='Company Name', required=True, store=True, readonly=False) sequence = fields.Integer( help='Used to order Companies in the company switcher', default=10) parent_id = fields.Many2one('res.company', string='Parent Company', index=True) child_ids = fields.One2many('res.company', 'parent_id', string='Child Companies') partner_id = fields.Many2one('res.partner', string='Partner', required=True) report_header = fields.Text( string='Company Tagline', help= "Appears by default on the top right corner of your printed documents (report header)." ) report_footer = fields.Text( string='Report Footer', translate=True, help="Footer text displayed at the bottom of all reports.") logo = fields.Binary(related='partner_id.image', default=_get_logo, string="Company Logo", readonly=False) # logo_web: do not store in attachments, since the image is retrieved in SQL for # performance reasons (see addons/web/controllers/main.py, Binary.company_logo) logo_web = fields.Binary(compute='_compute_logo_web', store=True, attachment=False) currency_id = fields.Many2one( 'res.currency', string='Currency', required=True, default=lambda self: self._get_user_currency()) user_ids = fields.Many2many('res.users', 'res_company_users_rel', 'cid', 'user_id', string='Accepted Users') account_no = fields.Char(string='Account No.') street = fields.Char(compute='_compute_address', inverse='_inverse_street') street2 = fields.Char(compute='_compute_address', inverse='_inverse_street2') zip = fields.Char(compute='_compute_address', inverse='_inverse_zip') city = fields.Char(compute='_compute_address', inverse='_inverse_city') state_id = fields.Many2one('res.country.state', compute='_compute_address', inverse='_inverse_state', string="Fed. State") bank_ids = fields.One2many('res.partner.bank', 'company_id', string='Bank Accounts', help='Bank accounts related to this company') country_id = fields.Many2one('res.country', compute='_compute_address', inverse='_inverse_country', string="Country") email = fields.Char(related='partner_id.email', store=True, readonly=False) phone = fields.Char(related='partner_id.phone', store=True, readonly=False) website = fields.Char(related='partner_id.website', readonly=False) vat = fields.Char(related='partner_id.vat', string="Tax ID", readonly=False) company_registry = fields.Char() paperformat_id = fields.Many2one( 'report.paperformat', 'Paper format', default=lambda self: self.env.ref('base.paperformat_euro', raise_if_not_found=False)) external_report_layout_id = fields.Many2one('ir.ui.view', 'Document Template') base_onboarding_company_state = fields.Selection( [('not_done', "Not done"), ('just_done', "Just done"), ('done', "Done")], string="State of the onboarding company step", default='not_done') favicon = fields.Binary( string="Company Favicon", help= "This field holds the image used to display a favicon for a given company.", default=_get_default_favicon) _sql_constraints = [('name_uniq', 'unique (name)', 'The company name must be unique !')] @api.model_cr def init(self): for company in self.search([('paperformat_id', '=', False)]): paperformat_euro = self.env.ref('base.paperformat_euro', False) if paperformat_euro: company.write({'paperformat_id': paperformat_euro.id}) sup = super(Company, self) if hasattr(sup, 'init'): sup.init() def _get_company_address_fields(self, partner): return { 'street': partner.street, 'street2': partner.street2, 'city': partner.city, 'zip': partner.zip, 'state_id': partner.state_id, 'country_id': partner.country_id, } # TODO @api.depends(): currently now way to formulate the dependency on the # partner's contact address def _compute_address(self): for company in self.filtered(lambda company: company.partner_id): address_data = company.partner_id.sudo().address_get( adr_pref=['contact']) if address_data['contact']: partner = company.partner_id.browse( address_data['contact']).sudo() company.update(company._get_company_address_fields(partner)) def _inverse_street(self): for company in self: company.partner_id.street = company.street def _inverse_street2(self): for company in self: company.partner_id.street2 = company.street2 def _inverse_zip(self): for company in self: company.partner_id.zip = company.zip def _inverse_city(self): for company in self: company.partner_id.city = company.city def _inverse_state(self): for company in self: company.partner_id.state_id = company.state_id def _inverse_country(self): for company in self: company.partner_id.country_id = company.country_id @api.depends('partner_id', 'partner_id.image') def _compute_logo_web(self): for company in self: company.logo_web = tools.image_process(company.partner_id.image, (180, None)) @api.onchange('state_id') def _onchange_state(self): self.country_id = self.state_id.country_id @api.multi def on_change_country(self, country_id): # This function is called from account/models/chart_template.py, hence decorated with `multi`. self.ensure_one() currency_id = self._get_user_currency() if country_id: currency_id = self.env['res.country'].browse( country_id).currency_id return {'value': {'currency_id': currency_id.id}} @api.onchange('country_id') def _onchange_country_id_wrapper(self): res = {'domain': {'state_id': []}} if self.country_id: res['domain']['state_id'] = [('country_id', '=', self.country_id.id)] values = self.on_change_country(self.country_id.id)['value'] for fname, value in values.items(): setattr(self, fname, value) return res @api.model def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None): context = dict(self.env.context) newself = self if context.pop('user_preference', None): # We browse as superuser. Otherwise, the user would be able to # select only the currently visible companies (according to rules, # which are probably to allow to see the child companies) even if # she belongs to some other companies. companies = self.env.user.company_ids args = (args or []) + [('id', 'in', companies.ids)] newself = newself.sudo() return super(Company, newself.with_context(context))._name_search( name=name, args=args, operator=operator, limit=limit, name_get_uid=name_get_uid) @api.model @api.returns('self', lambda value: value.id) def _company_default_get(self, object=False, field=False): """ Returns the user's company - Deprecated """ _logger.warning( _("The method '_company_default_get' on res.company is deprecated and shouldn't be used anymore" )) return self.env.company_id # deprecated, use clear_caches() instead def cache_restart(self): self.clear_caches() @api.model def create(self, vals): if not vals.get('favicon'): vals['favicon'] = self._get_default_favicon() if not vals.get('name') or vals.get('partner_id'): self.clear_caches() return super(Company, self).create(vals) partner = self.env['res.partner'].create({ 'name': vals['name'], 'is_company': True, 'image': vals.get('logo'), 'customer': False, 'email': vals.get('email'), 'phone': vals.get('phone'), 'website': vals.get('website'), 'vat': vals.get('vat'), }) vals['partner_id'] = partner.id self.clear_caches() company = super(Company, self).create(vals) # The write is made on the user to set it automatically in the multi company group. self.env.user.write({'company_ids': [(4, company.id)]}) partner.write({'company_id': company.id}) # Make sure that the selected currency is enabled if vals.get('currency_id'): currency = self.env['res.currency'].browse(vals['currency_id']) if not currency.active: currency.write({'active': True}) return company @api.multi def write(self, values): self.clear_caches() # Make sure that the selected currency is enabled if values.get('currency_id'): currency = self.env['res.currency'].browse(values['currency_id']) if not currency.active: currency.write({'active': True}) return super(Company, self).write(values) @api.constrains('parent_id') def _check_parent_id(self): if not self._check_recursion(): raise ValidationError(_('You cannot create recursive companies.')) @api.multi def open_company_edit_report(self): self.ensure_one() return self.env['res.config.settings'].open_company() @api.multi def write_company_and_print_report(self, values): res = self.write(values) report_name = values.get('default_report_name') active_ids = values.get('active_ids') active_model = values.get('active_model') if report_name and active_ids and active_model: docids = self.env[active_model].browse(active_ids) return (self.env['ir.actions.report'].search( [('report_name', '=', report_name)], limit=1).with_context(values).report_action(docids)) else: return res @api.model def action_open_base_onboarding_company(self): """ Onboarding step for company basic information. """ action = self.env.ref( 'base.action_open_base_onboarding_company').read()[0] action['res_id'] = self.env.company_id.id return action def set_onboarding_step_done(self, step_name): if self[step_name] == 'not_done': self[step_name] = 'just_done' def get_and_update_onbarding_state(self, onboarding_state, steps_states): """ Needed to display onboarding animations only one time. """ old_values = {} all_done = True for step_state in steps_states: old_values[step_state] = self[step_state] if self[step_state] == 'just_done': self[step_state] = 'done' all_done = all_done and self[step_state] == 'done' if all_done: if self[onboarding_state] == 'not_done': # string `onboarding_state` instead of variable name is not an error old_values['onboarding_state'] = 'just_done' else: old_values['onboarding_state'] = 'done' self[onboarding_state] = 'done' return old_values @api.multi def action_save_onboarding_company_step(self): if bool(self.street): self.set_onboarding_step_done('base_onboarding_company_state') @api.model def _get_main_company(self): try: main_company = self.sudo().env.ref('base.main_company') except ValueError: main_company = self.env['res.company'].sudo().search([], limit=1, order="id") return main_company
class attendance_raw_logs(models.Model): _name = 'attendance.raw.main' _description = 'Attendance Dat File Uploader' name = fields.Char( string='Sequence', default=lambda self: _('New Attendance Dat File'), size=64, ) import_file = fields.Binary(string='Import Dat File') attendace_ids = fields.One2many('hr.attendance', 'raw_attendance_id', string='Attendances', ondelete='restrict') raw_attendance_ids = fields.One2many('attendance.raw.list', 'raw_attendance_id', string='Attendances') @api.model def create(self, values): if values.get( 'name', _('New Attendance Dat File')) == _('New Attendance Dat File'): values['name'] = self.env['ir.sequence'].next_by_code( 'attendance.raw.main') res = super(attendance_raw_logs, self).create(values) return res @api.one def create_raw_attendance(self): #, raw_main_id, uid if not self.import_file: raise UserError('No File Found. Please Upload the Log File.') elif len(self.import_file) == 0: raise UserError('No File Found. Please Upload the Log File.') FILENAME = "/opt/Log_File/newfile_dat_file_" + str(self._uid) + ".dat" with open(FILENAME, "wb") as f: text = self.import_file f.write(base64.b64decode(text)) #raise Warning(f.readline()) f.close() with open(FILENAME, "r") as f: lines = f.readlines() user_tz = self.env.user.tz local = pytz.timezone(user_tz) if lines: raw_list_obj = self.env['attendance.raw.list'].search([ ('raw_attendance_id', '=', self.id) ]) #if raw_list_obj: #for lst in raw_list_obj: # if lst.name == '1': # _logger.info(lst.date_time_logs) res = raw_list_obj.unlink() #if res: raw_list_obj_1 = self.env['attendance.raw.list'] for line in lines: if line: line_list = line.split() datetime_log = fields.Datetime.from_string( line_list[1] + ' ' + line_list[2] ) #datetime.strptime(line_list[1] + ' ' + line_list[2], '%Y-%m-%d %H:%M:%S').replace(tzinfo=local) #if line_list[0] == '1' and line_list[1] == '2011-08-16': #_logger.info(line.split()) #_logger.info(datetime_log) #raise Warning(datetime_log.replace(tzinfo=local)) hour_date = timedelta(hours=8) _logger.info(datetime_log) _logger.info(datetime_log - hour_date) raw_list_obj_1.create({ 'raw_attendance_id': self.id, 'name': line_list[0], 'date_time_logs': datetime_log - hour_date, 'date_logs': line_list[1] }) f.close() os.remove(FILENAME) @api.one def create_attendance(self): user_tz = self.env.user.tz local = pytz.timezone(user_tz) if not self.import_file: raise UserError('No File Found. Please Upload the Log File.') elif len(self.import_file) == 0: raise UserError('No File Found. Please Upload the Log File.') raw_list_obj = self.env['attendance.raw.list'].search([ ('raw_attendance_id', '=', self.id) ]) obj_employees = self.env['hr.employee'].search([('biometric_number', '!=', False)]) raw_list_model = self.env['attendance.raw.list'] if not raw_list_obj: raise UserError( 'No Raw Attendance Found. Please Upload the Log File and Submit to Generate the Raw Attendance.' ) elif len(raw_list_obj) == 0: raise UserError( 'No Raw Attendance Found. Please Upload the Log File and Submit to Generate the Raw Attendance.' ) #To Get the Distinct Dates in the Logs dates_lst = [] if raw_list_obj: for raw in raw_list_obj: if len(dates_lst) > 0: if raw.date_logs not in dates_lst: dates_lst.append(raw.date_logs) else: dates_lst.append(raw.date_logs) if obj_employees: for employee in obj_employees: for dt_lst in dates_lst: #Due to Looping and Distinction of Records by Date, Some Logs have no Log in Dates #This will handle the Logs that does not Exists raw_list_obj_asc_rec = raw_list_model.search([ ('date_logs', '=', dt_lst), ('name', '=', employee.biometric_number) ]) if raw_list_obj_asc_rec: raw_list_obj_asc_rec = False raw_list_obj_desc_rec = False raw_list_obj_asc_rec = raw_list_model.search( [('date_logs', '=', dt_lst), ('name', '=', employee.biometric_number), ('raw_attendance_id', '=', self.id)], limit=1, order='date_time_logs asc') raw_list_obj_desc_rec = raw_list_model.search( [('date_logs', '=', dt_lst), ('name', '=', employee.biometric_number), ('raw_attendance_id', '=', self.id)], limit=1, order='date_time_logs desc') #FIRST Criteria hr_attendance_add = self.env['hr.attendance'] hr_attendance = self.env['hr.attendance'].search([ ('employee_id', '=', employee.id), ('check_in', '=', raw_list_obj_asc_rec.date_time_logs), ('check_out', '=', raw_list_obj_desc_rec.date_time_logs), ]) if not hr_attendance: hr_attendance_add.create({ 'raw_attendance_id': self.id, 'employee_id': employee.id, 'check_in': raw_list_obj_asc_rec.date_time_logs, 'check_out': raw_list_obj_desc_rec.date_time_logs })
class view_gmcreport2(models.TransientModel): _name = 'view.gmcreport2' _rec_name = 'datas_fname' datas_fname=fields.Char('File Name', size=256) file_name=fields.Binary('Report')
class kardex_productos_inventario(models.TransientModel): _name = 'kardex.productos.mov' _description = "kardex productos" @api.model def get_default_date_model(self): return pytz.UTC.localize(datetime.now()).astimezone( timezone(self.env.user.tz or 'UTC')) @api.model def _get_from_date(self): company = self.env.user.company_id current_date = datetime.date.today() from_date = company.compute_fiscalyear_dates(current_date)['date_from'] return from_date excel_binary = fields.Binary('Field') file_name = fields.Char('Report_Name', readonly=True) product = fields.Many2one('product.product', string='Product', required=True) company = fields.Many2one('res.company', required=True, default=lambda self: self.env.user.company_id, string='Current Company') ubicacion = fields.Many2one('stock.location', domain=[('usage', '=', "internal")], string='Location') date_from = fields.Date(string='Date from', default=_get_from_date) date_to = fields.Date(string='Date to', default=fields.Date.today) revisio = fields.Char(string='revision') cantidad_inicial = fields.Float('Duantity Ini:', readonly=True) costo_promedio_inicial = fields.Float('Cost Ini:', readonly=True) costo_total_inicial = fields.Float('Cost Total Ini:', readonly=True) cantidad_final = fields.Float('Quantity End :', readonly=True) costo_promedio = fields.Float('Cost End:', readonly=True) costo_total = fields.Float('Costo Total End', readonly=True) aplica = fields.Selection([('todas', 'All '), ('ubicacion', 'By location')], required=True, default='todas', string='Selection location') currency_id = fields.Many2one( 'res.currency', string='Company currency', required=True, default=lambda self: self.env.user.company_id.currency_id, readonly=True) obj_kardex = fields.One2many(comodel_name='kardex.productos.mov.detalle', inverse_name='obj_kardex_mostrarmovi') def _action_imprimir_excel(self, ): workbook = xlwt.Workbook() column_heading_style = easyxf('font:height 200;font:bold True;') worksheet = workbook.add_sheet('Kardex report') date_format = xlwt.XFStyle() date_format.num_format_str = 'dd/mm/yyyy' number_format = xlwt.XFStyle() number_format.num_format_str = '#,##0.00' #Ponemos los primeros encabezados worksheet.write(0, 0, _('Kardex report product'), column_heading_style) query_rorte = """ select Max(id) as id from kardex_productos_mov """ self.env.cr.execute(query_rorte, ) tr = self.env.cr.dictfetchall() for tr_t in tr: todo_reporte = self.env['kardex.productos.mov'].search([ ('id', '=', int(tr_t['id'])) ]) tf = 0 for todfact in todo_reporte: worksheet.write(1, 0, "Date from:", column_heading_style) worksheet.write(1, 1, todfact.date_from, date_format) worksheet.write(2, 0, "Date to:", column_heading_style) worksheet.write(2, 1, todfact.date_to, date_format) worksheet.write(1, 2, "Product:", column_heading_style) worksheet.write(1, 3, todfact.product.name) worksheet.write(2, 2, "Current Company:", column_heading_style) worksheet.write(2, 3, todfact.company.name) worksheet.write(1, 4, "Location:", column_heading_style) ubi_hijo = todfact.ubicacion.name ubi_pad = todfact.ubicacion.location_id.name worksheet.write(1, 5, str(ubi_pad) + "/" + str(ubi_hijo)) #Ponemos los primeros encabezados del detalle worksheet.write(4, 0, _('Date'), column_heading_style) worksheet.write(4, 1, _('Concept'), column_heading_style) worksheet.write(4, 2, _('U_In'), column_heading_style) worksheet.write(4, 3, _('U_Out'), column_heading_style) worksheet.write(4, 4, _('U_balance'), column_heading_style) worksheet.write(4, 5, _("Costo_Uni"), column_heading_style) worksheet.write(4, 6, _('V_In'), column_heading_style) worksheet.write(4, 7, _('V_Out'), column_heading_style) worksheet.write(4, 8, _('V_balance'), column_heading_style) worksheet.write(4, 9, _('Costo_Prom'), column_heading_style) worksheet.write(4, 10, _('Origin'), column_heading_style) worksheet.write(4, 11, _('Pickin'), column_heading_style) worksheet.write(4, 12, _('Invoice'), column_heading_style) worksheet.write(4, 13, _('Inventory'), column_heading_style) heading = "Product Kardex Detail" # worksheet.write_merge(5, 0, 5,13, heading, easyxf('font:height 200; align: horiz center;pattern: pattern solid, fore_color black; font: color white; font:bold True;' "borders: top thin,bottom thin")) # Se tiene que hacer de ultimo para saber cuanto mide todo # se recorre el reporte todo_reporte = self.env['kardex.productos.mov.detalle'].search([ ('obj_kardex_mostrarmovi', '=', int(tr_t['id'])) ]) tf = 0 for todfact in todo_reporte: tf += 1 ini = 5 worksheet.write(tf + ini, 0, todfact.date, date_format) worksheet.write(tf + ini, 1, todfact.concepto) worksheet.write(tf + ini, 2, todfact.u_entrada, number_format) worksheet.write(tf + ini, 3, todfact.u_salida, number_format) worksheet.write(tf + ini, 4, todfact.u_saldo, number_format) worksheet.write(tf + ini, 5, todfact.costo_unit, number_format) worksheet.write(tf + ini, 6, todfact.v_entrada, number_format) worksheet.write(tf + ini, 7, todfact.v_salida, number_format) worksheet.write(tf + ini, 8, todfact.v_saldo, number_format) worksheet.write(tf + ini, 9, todfact.costo_prom, number_format) worksheet.write(tf + ini, 10, todfact.origin, number_format) worksheet.write(tf + ini, 11, todfact.picking_id.name) worksheet.write(tf + ini, 12, todfact.account_invoice.name) worksheet.write(tf + ini, 13, todfact.inventario.name) fp = io.BytesIO() workbook.save(fp) excel_file = base64.encodestring(fp.getvalue()) self.excel_binary = excel_file nombre_tabla = "Kardex Report.xls" self.file_name = nombre_tabla fp.close() # @api.depends('company') # def _actualizar_compania(self): # self.company=domain=[('company_id', '=', self.company.id)] @api.onchange('company') def _cambio_company(self): # # Set ubucacion ID if self.company: return { 'domain': { 'ubicacion': [('company_id', '=', self.company.id), ('usage', '=', "internal")] } } #@api.one def _borracampos(self): self.cantidad_inicial = "" self.cantidad_final = "" self.costo_promedio = "" self.costo_total = "" self.aplica = "todas" self.company = "" def buscar_producto(self): if self.date_from > self.date_to: raise UserError( _("The Start date cannot be less than the end date ")) else: self._borra_datos_tabla() def _borra_datos_tabla(self): query_rorte = """ select Max(id) as id from kardex_productos_mov """ self.env.cr.execute(query_rorte, ) tr = self.env.cr.dictfetchall() for tr_t in tr: todo = self.env['kardex.productos.mov'].search([('id', '<', int(tr_t['id']))]) for tod in todo: tod.unlink() todo_reporte = self.env['kardex.productos.mov.detalle'].search([ ('id', '<', int(tr_t['id'])) ]) for tod in todo_reporte: tod.unlink() for karde in self: karde.obj_kardex.unlink() #Empezamos a realizar el saldo self._saldo_anterior() def _saldo_anterior(self): query_total = self._moviento_completo() query_saldo_anterior = """ select (SUM(u_entrada)-SUM(u_salida))as u_ante , (SUM(v_entrada)-SUM(v_salida))as v_ante from ( """ + query_total + """ )as saldo_ante where date < %s --) estes espara obteber el saldo anterior """ producto = 0 producto = self.product.id ubicacion = self.ubicacion.id date_from = self.date_from if self.aplica == "todas": query_saldo_anterior_param = (producto, producto, date_from) if self.aplica == "ubicacion": query_saldo_anterior_param = (producto, ubicacion, producto, ubicacion, date_from) self.env.cr.execute(query_saldo_anterior, query_saldo_anterior_param) saldo_anterior = self.env.cr.dictfetchall() for linea in saldo_anterior: self.cantidad_inicial = linea['u_ante'] self.costo_total_inicial = linea['v_ante'] if self.costo_total_inicial == 0: self.costo_promedio_inicial = 0 if self.costo_total_inicial > 0: self.costo_promedio_inicial = self.costo_total_inicial / self.cantidad_inicial #Ponemos el saldo anteririor en la tabla self._saldo_anterior_tabla() def _saldo_anterior_tabla(self): for kardex in self: concepto = "Previous balance" u_saldo = self.cantidad_inicial costo_uni = self.costo_promedio_inicial v_saldo = self.costo_total_inicial line = ({ 'concepto': concepto, 'u_saldo': u_saldo, 'costo_unit': costo_uni, 'v_saldo': v_saldo, }) lines = [(0, 0, line)] kardex.write({'obj_kardex': lines}) self._movimiento_producto() def _movimiento_producto(self): query_total = self._moviento_completo() query_movimiento = """ select * from ( """ + query_total + """ ) as mov where date >=%s and date <=%s """ producto = 0 producto = self.product.id ubicacion = self.ubicacion.id date_from = self.date_from date_to = self.date_to if self.aplica == "todas": query_movimiento_param = (producto, producto, date_from, date_to) if self.aplica == "ubicacion": query_movimiento_param = (producto, ubicacion, producto, ubicacion, date_from, date_to) self.env.cr.execute(query_movimiento, query_movimiento_param) movim = self.env.cr.dictfetchall() for mov in movim: for kardex in self: fecha = mov['date'] concepto = mov['reference'] u_entrada = mov['u_entrada'] u_salida = mov['u_salida'] u_saldo = mov['u_saldo'] costo_unit = mov['costo_unit'] v_entrada = mov['v_entrada'] v_salida = mov['v_salida'] v_saldo = mov['v_saldo'] origin = mov['origin'] picking_id = mov['picking_id'] inventario = mov['inventory_id'] line = ({ 'date': fecha, 'concepto': concepto, 'u_entrada': u_entrada, 'u_salida': u_salida, 'u_saldo': u_saldo, 'costo_unit': costo_unit, 'v_entrada': v_entrada, 'v_salida': v_salida, 'v_saldo': v_saldo, 'origin': origin, 'picking_id': picking_id, 'inventario': inventario, }) lines = [(0, 0, line)] kardex.write({'obj_kardex': lines}) self._saldo_final() def _saldo_final(self): query_total = self._moviento_completo() query_saldo_final = """ select (SUM(u_entrada)-SUM(u_salida))as u_saldo , (SUM(v_entrada)-SUM(v_salida))as v_saldo from ( """ + query_total + """ )as saldo_ante where date <= %s --) estes espara obteber el saldo final """ producto = 0 producto = self.product.id ubicacion = self.ubicacion.id date_to = self.date_to if self.aplica == "todas": query_saldo_final_param = (producto, producto, date_to) if self.aplica == "ubicacion": query_saldo_final_param = (producto, ubicacion, producto, ubicacion, date_to) self.env.cr.execute(query_saldo_final, query_saldo_final_param) saldo_final = self.env.cr.dictfetchall() for linea in saldo_final: self.cantidad_final = linea['u_saldo'] self.costo_total = linea['v_saldo'] if self.costo_total > 0: self.costo_promedio = self.costo_total / self.cantidad_final #buscamos las facturas self._buscar_factura() def _moviento_completo(self): local_des = "" location_id = "" if self.aplica == "todas": local_des = "sm.location_dest_id > 0" location_id = "sm.location_id > 0" if self.aplica == "ubicacion": local_des = "sm.location_dest_id=%s" location_id = "sm.location_id=%s" query_movimiento = """ select id,CAST(date AS date),company_id, product,nombre,u_entrada, u_salida,u_saldo,costo_unit,v_entrada,v_salida, v_saldo,state,origin,reference,usage,complete_name,ubicacion,inventory_id ,picking_id from ( -------------3)Comienza el segundo select select id,date_expected as date,company_id ,product_id as product, name as nombre,u_entrada,u_salida, SUM(u_entrada-u_salida)over (order by date_expected asc,id asc)as u_saldo, costo_unit,v_entrada,v_salida, SUM(v_entrada-v_salida)over (order by date_expected asc,id asc)as v_saldo,state ,origin,reference,usage, complete_name,ubicacion,inventory_id ,picking_id from ( --------------- EMPIEZA LA UNION --- unimos entradas select id,date_expected,product_id,name,company_id, u_entrada,u_salida, costo_unit , v_entrada,v_salida,v_saldo,state,origin,reference, usage,complete_name,ubicacion,create_uid,inventory_id,picking_id from ( select sm.id,sm.date_expected,sm.product_id,sm.name,sm.company_id,sm.product_qty as u_entrada,(sm.product_qty * 0)u_salida, (sm.price_unit) as costo_unit ,(sm.product_qty * sm.price_unit) as v_entrada,(sm.product_qty * 0)v_salida,(sm.product_qty *0)v_saldo,sm.state,sm.origin,sm.reference, sl.usage,sl.complete_name,(sm.location_dest_id)as ubicacion,sm.create_uid,sm.inventory_id,sm.picking_id from stock_move sm inner join stock_location sl on sm.location_dest_id=sl.id where sl.usage='internal' and sm.product_id=%s and sm.state='done' and """ + local_des + """ order by date_expected asc )as sl ---- para las entrada UNION -------------unimos salidas select id,date_expected,product_id,name,company_id, u_entrada,u_salida,costo_unit ,v_entrada,v_salida,v_saldo,state,origin,reference, usage,complete_name,ubicacion,create_uid,inventory_id,picking_id from ( select sm.id,sm.date_expected,sm.product_id,sm.name,sm.company_id,(sm.product_qty * 0) as u_entrada, sm.product_qty as u_salida,(am.amount_total_signed/sm.product_qty ) as costo_unit ,(sm.product_qty *0)as v_entrada,(am.amount_total_signed )v_salida,(sm.product_qty *0)v_saldo, sm.state,sm.origin,sm.reference ,sl.usage,sl.complete_name,(sm.location_id)as ubicacion,sm.create_uid,sm.inventory_id,sm.picking_id from stock_move sm inner join stock_location sl on sm.location_id=sl.id inner join account_move am on am.stock_move_id= sm.id where sl.usage='internal' and sm.product_id=%s and sm.state='done' and """ + location_id + """ order by date_expected asc )sl -------------- para las salidas ) as kardex order by date asc -------2)TERMINA EL 2DO SELECT )as kardex2 ------1)TERMINA EL PRIMER SELECT """ return query_movimiento def _buscar_factura(self): for fact in self.obj_kardex: if fact.origin: query_origen = """ select Min(id) as id from account_move where invoice_origin = %s """ query_origen_param = (fact.origin, ) self.env.cr.execute(query_origen, query_origen_param) movim = self.env.cr.dictfetchall() for mov in movim: # # facturas=self.env['account.invoice'].search([('origin','=',fact.origin)]) fact.account_invoice = mov['id'] self._action_imprimir_excel()
class ShopeeProductPreset(models.Model): _name = 'shopee.product.preset' _inherit = 'ecommerce.product.preset' product_tmpl_ids = fields.One2many('product.template', 'shopee_product_preset_id', readonly=True) name = fields.Char("Name") # category_id = fields.Integer(related='ecomm_categ_id.platform_categ_idn', store=True, readonly=True) # category_name = fields.Char(string=_("Shopee Category"), related='ecomm_categ_id.complete_name', readonly=True) description = fields.Text(string=_("Description")) weight = fields.Float(string=_("Package Weight (kg)")) package_length = fields.Float(string=_("Package Lenghth (cm)")) package_width = fields.Float(string=_("Package Width (cm)")) package_height = fields.Float(string=_("Package Height (cm)")) size_chart = fields.Binary(string=_("Size Chart")) condition = fields.Selection(selection=[('NEW', _("New")), ('USED', _("Used"))], string=_("Shopee Condition")) status = fields.Selection(selection=[('NORMAL', _("Normal")), ('DELETED', _("Deleted")), ('BANNED', _("Banned")), ('UNLIST', _("Unlisted"))], string=_("Status"), readonly=True) is_pre_order = fields.Boolean(string=_("Is Pre-order")) days_to_ship = fields.Integer(string=_("Days To Ship")) def format_attr_values(self): self.ensure_one() return [{ 'idn': line.attr_id.platform_attr_idn, 'value': line.value_id.name } for line in self.ecomm_attribute_lines] #modify this if use stock delivery (logistic_id, enabled, shipping_fee, size_id, is_free) # shopee_wholesales(min, max, unit_price) > later # shopee_name, shopee_description, shopee_price, shopee_stock, shopee_item_sku, shopee_variations(name, stock, price, variation_sku), images(url) def _onchange_ecomm_categ_id_shopee(self): shop = self.env['ecommerce.shop'].search([('platform_id.platform', '=', 'shopee')])[:1] shop.ensure_one() attrs = shop._py_client_shopee().item.get_attributes( category_id=self.ecomm_categ_id.platform_categ_idn).get( 'attributes') triplets = [(5, _, _)] for attr in attrs: ecomm_attrs = self.env['ecommerce.attribute'].search([ ('platform_id', '=', self.platform_id.id), ('platform_attr_idn', '=', attr['attribute_id']) ]) ecomm_attr = ecomm_attrs and ecomm_attrs[0] or self.env[ 'ecommerce.attribute'].create({ 'name': attr['attribute_name'], 'platform_id': self.platform_id.id, 'platform_attr_idn': attr['attribute_id'], 'mandatory': attr['is_mandatory'], 'attr_type': attr['attribute_type'], 'input_type': attr['input_type'], 'value_ids': [(0, _, { 'name': option, }) for option in attr['options']], }) triplets += [(0, _, { 'attr_id': ecomm_attr.id, 'res_id': self.id, 'res_model': self._name })] self.update({'ecomm_attribute_lines': triplets}) _logger.info(self.ecomm_attribute_lines.mapped('res_model'))
class Partner(models.Model): _description = 'Contact' _inherit = ['format.address.mixin'] _name = "res.partner" _order = "display_name" def _default_category(self): return self.env['res.partner.category'].browse(self._context.get('category_id')) def _default_company(self): return self.env['res.company']._company_default_get('res.partner') name = fields.Char(index=True) display_name = fields.Char(compute='_compute_display_name', store=True, index=True) date = fields.Date(index=True) title = fields.Many2one('res.partner.title') parent_id = fields.Many2one('res.partner', string='Related Company', index=True) parent_name = fields.Char(related='parent_id.name', readonly=True, string='Parent name') child_ids = fields.One2many('res.partner', 'parent_id', string='Contacts', domain=[('active', '=', True)]) # force "active_test" domain to bypass _search() override ref = fields.Char(string='Internal Reference', index=True) lang = fields.Selection(_lang_get, string='Language', default=lambda self: self.env.lang, help="If the selected language is loaded in the system, all documents related to " "this contact will be printed in this language. If not, it will be English.") tz = fields.Selection(_tz_get, string='Timezone', default=lambda self: self._context.get('tz'), help="The partner's timezone, used to output proper date and time values " "inside printed reports. It is important to set a value for this field. " "You should use the same timezone that is otherwise used to pick and " "render date and time values: your computer's timezone.") tz_offset = fields.Char(compute='_compute_tz_offset', string='Timezone offset', invisible=True) user_id = fields.Many2one('res.users', string='Salesperson', help='The internal user that is in charge of communicating with this contact if any.') vat = fields.Char(string='TIN', help="Tax Identification Number. " "Fill it if the company is subjected to taxes. " "Used by the some of the legal statements.") bank_ids = fields.One2many('res.partner.bank', 'partner_id', string='Banks') website = fields.Char(help="Website of Partner or Company") comment = fields.Text(string='Notes') category_id = fields.Many2many('res.partner.category', column1='partner_id', column2='category_id', string='Tags', default=_default_category) credit_limit = fields.Float(string='Credit Limit') barcode = fields.Char(oldname='ean13') active = fields.Boolean(default=True) customer = fields.Boolean(string='Is a Customer', default=True, help="Check this box if this contact is a customer.") supplier = fields.Boolean(string='Is a Vendor', help="Check this box if this contact is a vendor. " "If it's not checked, purchase people will not see it when encoding a purchase order.") employee = fields.Boolean(help="Check this box if this contact is an Employee.") function = fields.Char(string='Job Position') type = fields.Selection( [('contact', 'Contact'), ('invoice', 'Invoice address'), ('delivery', 'Shipping address'), ('other', 'Other address')], string='Address Type', default='contact', help="Used to select automatically the right address according to the context in sales and purchases documents.") street = fields.Char() street2 = fields.Char() zip = fields.Char(change_default=True) city = fields.Char() state_id = fields.Many2one("res.country.state", string='State', ondelete='restrict') country_id = fields.Many2one('res.country', string='Country', ondelete='restrict') email = fields.Char() email_formatted = fields.Char( 'Formatted Email', compute='_compute_email_formatted', help='Format email address "Name <email@domain>"') phone = fields.Char() mobile = fields.Char() is_company = fields.Boolean(string='Is a Company', default=False, help="Check if the contact is a company, otherwise it is a person") industry_id = fields.Many2one('res.partner.industry', 'Sector of Activity') # company_type is only an interface field, do not use it in business logic company_type = fields.Selection(string='Company Type', selection=[('person', 'Individual'), ('company', 'Company')], compute='_compute_company_type', readonly=False) company_id = fields.Many2one('res.company', 'Company', index=True, default=_default_company) color = fields.Integer(string='Color Index', default=0) user_ids = fields.One2many('res.users', 'partner_id', string='Users', auto_join=True) partner_share = fields.Boolean( 'Share Partner', compute='_compute_partner_share', store=True, help="Either customer (no user), either shared user. Indicated the current partner is a customer without " "access or with a limited access created for sharing data.") contact_address = fields.Char(compute='_compute_contact_address', string='Complete Address') # technical field used for managing commercial fields commercial_partner_id = fields.Many2one('res.partner', compute='_compute_commercial_partner', string='Commercial Entity', store=True, index=True) commercial_partner_country_id = fields.Many2one('res.country', related='commercial_partner_id.country_id', store=True) commercial_company_name = fields.Char('Company Name Entity', compute='_compute_commercial_company_name', store=True) company_name = fields.Char('Company Name') # image: all image fields are base64 encoded and PIL-supported image = fields.Binary("Image", attachment=True, help="This field holds the image used as avatar for this contact, limited to 1024x1024px",) image_medium = fields.Binary("Medium-sized image", attachment=True, help="Medium-sized image of this contact. It is automatically "\ "resized as a 128x128px image, with aspect ratio preserved. "\ "Use this field in form views or some kanban views.") image_small = fields.Binary("Small-sized image", attachment=True, help="Small-sized image of this contact. It is automatically "\ "resized as a 64x64px image, with aspect ratio preserved. "\ "Use this field anywhere a small image is required.") # hack to allow using plain browse record in qweb views, and used in ir.qweb.field.contact self = fields.Many2one(comodel_name=_name, compute='_compute_get_ids') _sql_constraints = [ ('check_name', "CHECK( (type='contact' AND name IS NOT NULL) or (type!='contact') )", 'Contacts require a name.'), ] @api.depends('is_company', 'name', 'parent_id.name', 'type', 'company_name') def _compute_display_name(self): diff = dict(show_address=None, show_address_only=None, show_email=None) names = dict(self.with_context(**diff).name_get()) for partner in self: partner.display_name = names.get(partner.id) @api.depends('tz') def _compute_tz_offset(self): for partner in self: partner.tz_offset = datetime.datetime.now(pytz.timezone(partner.tz or 'GMT')).strftime('%z') @api.depends('user_ids.share') def _compute_partner_share(self): for partner in self: partner.partner_share = not partner.user_ids or any(user.share for user in partner.user_ids) @api.depends(lambda self: self._display_address_depends()) def _compute_contact_address(self): for partner in self: partner.contact_address = partner._display_address() @api.one def _compute_get_ids(self): self.self = self.id @api.depends('is_company', 'parent_id.commercial_partner_id') def _compute_commercial_partner(self): for partner in self: if partner.is_company or not partner.parent_id: partner.commercial_partner_id = partner else: partner.commercial_partner_id = partner.parent_id.commercial_partner_id @api.depends('company_name', 'parent_id.is_company', 'commercial_partner_id.name') def _compute_commercial_company_name(self): for partner in self: p = partner.commercial_partner_id partner.commercial_company_name = p.is_company and p.name or partner.company_name @api.model def _fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False): if (not view_id) and (view_type == 'form') and self._context.get('force_email'): view_id = self.env.ref('base.view_partner_simple_form').id res = super(Partner, self)._fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu) if view_type == 'form': res['arch'] = self._fields_view_get_address(res['arch']) return res @api.constrains('parent_id') def _check_parent_id(self): if not self._check_recursion(): raise ValidationError(_('You cannot create recursive Partner hierarchies.')) @api.multi def copy(self, default=None): self.ensure_one() default = dict(default or {}, name=_('%s (copy)') % self.name) return super(Partner, self).copy(default) @api.onchange('parent_id') def onchange_parent_id(self): # return values in result, as this method is used by _fields_sync() if not self.parent_id: return result = {} partner = getattr(self, '_origin', self) if partner.parent_id and partner.parent_id != self.parent_id: result['warning'] = { 'title': _('Warning'), 'message': _('Changing the company of a contact should only be done if it ' 'was never correctly set. If an existing contact starts working for a new ' 'company then a new contact should be created under that new ' 'company. You can use the "Discard" button to abandon this change.')} if partner.type == 'contact' or self.type == 'contact': # for contacts: copy the parent address, if set (aka, at least one # value is set in the address: otherwise, keep the one from the # contact) address_fields = self._address_fields() if any(self.parent_id[key] for key in address_fields): def convert(value): return value.id if isinstance(value, models.BaseModel) else value result['value'] = {key: convert(self.parent_id[key]) for key in address_fields} return result @api.onchange('country_id') def _onchange_country_id(self): if self.country_id: return {'domain': {'state_id': [('country_id', '=', self.country_id.id)]}} else: return {'domain': {'state_id': []}} @api.onchange('email') def onchange_email(self): if not self.image and self._context.get('gravatar_image') and self.email: self.image = self._get_gravatar_image(self.email) @api.depends('name', 'email') def _compute_email_formatted(self): for partner in self: partner.email_formatted = formataddr((partner.name, partner.email)) @api.depends('is_company') def _compute_company_type(self): for partner in self: partner.company_type = 'company' if partner.is_company else 'person' @api.onchange('company_type') def onchange_company_type(self): self.is_company = (self.company_type == 'company') @api.multi def _update_fields_values(self, fields): """ Returns dict of write() values for synchronizing ``fields`` """ values = {} for fname in fields: field = self._fields[fname] if field.type == 'many2one': values[fname] = self[fname].id elif field.type == 'one2many': raise AssertionError(_('One2Many fields cannot be synchronized as part of `commercial_fields` or `address fields`')) elif field.type == 'many2many': values[fname] = [(6, 0, self[fname].ids)] else: values[fname] = self[fname] return values @api.model def _address_fields(self): """Returns the list of address fields that are synced from the parent.""" return list(ADDRESS_FIELDS) @api.multi def update_address(self, vals): addr_vals = {key: vals[key] for key in self._address_fields() if key in vals} if addr_vals: return super(Partner, self).write(addr_vals) @api.model def _commercial_fields(self): """ Returns the list of fields that are managed by the commercial entity to which a partner belongs. These fields are meant to be hidden on partners that aren't `commercial entities` themselves, and will be delegated to the parent `commercial entity`. The list is meant to be extended by inheriting classes. """ return ['vat', 'credit_limit'] @api.multi def _commercial_sync_from_company(self): """ Handle sync of commercial fields when a new parent commercial entity is set, as if they were related fields """ commercial_partner = self.commercial_partner_id if commercial_partner != self: sync_vals = commercial_partner._update_fields_values(self._commercial_fields()) self.write(sync_vals) @api.multi def _commercial_sync_to_children(self): """ Handle sync of commercial fields to descendants """ commercial_partner = self.commercial_partner_id sync_vals = commercial_partner._update_fields_values(self._commercial_fields()) sync_children = self.child_ids.filtered(lambda c: not c.is_company) for child in sync_children: child._commercial_sync_to_children() sync_children._compute_commercial_partner() return sync_children.write(sync_vals) @api.multi def _fields_sync(self, values): """ Sync commercial fields and address fields from company and to children after create/update, just as if those were all modeled as fields.related to the parent """ # 1. From UPSTREAM: sync from parent if values.get('parent_id') or values.get('type', 'contact'): # 1a. Commercial fields: sync if parent changed if values.get('parent_id'): self._commercial_sync_from_company() # 1b. Address fields: sync if parent or use_parent changed *and* both are now set if self.parent_id and self.type == 'contact': onchange_vals = self.onchange_parent_id().get('value', {}) self.update_address(onchange_vals) # 2. To DOWNSTREAM: sync children if self.child_ids: # 2a. Commercial Fields: sync if commercial entity if self.commercial_partner_id == self: commercial_fields = self._commercial_fields() if any(field in values for field in commercial_fields): self._commercial_sync_to_children() for child in self.child_ids.filtered(lambda c: not c.is_company): if child.commercial_partner_id != self.commercial_partner_id : self._commercial_sync_to_children() break # 2b. Address fields: sync if address changed address_fields = self._address_fields() if any(field in values for field in address_fields): contacts = self.child_ids.filtered(lambda c: c.type == 'contact') contacts.update_address(values) @api.multi def _handle_first_contact_creation(self): """ On creation of first contact for a company (or root) that has no address, assume contact address was meant to be company address """ parent = self.parent_id address_fields = self._address_fields() if (parent.is_company or not parent.parent_id) and len(parent.child_ids) == 1 and \ any(self[f] for f in address_fields) and not any(parent[f] for f in address_fields): addr_vals = self._update_fields_values(address_fields) parent.update_address(addr_vals) def _clean_website(self, website): url = urls.url_parse(website) if not url.scheme: if not url.netloc: url = url.replace(netloc=url.path, path='') website = url.replace(scheme='http').to_url() return website @api.multi def write(self, vals): # res.partner must only allow to set the company_id of a partner if it # is the same as the company of all users that inherit from this partner # (this is to allow the code from res_users to write to the partner!) or # if setting the company_id to False (this is compatible with any user # company) if vals.get('website'): vals['website'] = self._clean_website(vals['website']) if vals.get('parent_id'): vals['company_name'] = False if vals.get('company_id'): company = self.env['res.company'].browse(vals['company_id']) for partner in self: if partner.user_ids: companies = set(user.company_id for user in partner.user_ids) if len(companies) > 1 or company not in companies: raise UserError(_("You can not change the company as the partner/user has multiple user linked with different companies.")) tools.image_resize_images(vals) result = True # To write in SUPERUSER on field is_company and avoid access rights problems. if 'is_company' in vals and self.user_has_groups('base.group_partner_manager') and not self.env.uid == SUPERUSER_ID: result = super(Partner, self).sudo().write({'is_company': vals.get('is_company')}) del vals['is_company'] result = result and super(Partner, self).write(vals) for partner in self: if any(u.has_group('base.group_user') for u in partner.user_ids if u != self.env.user): self.env['res.users'].check_access_rights('write') partner._fields_sync(vals) return result @api.model def create(self, vals): if vals.get('website'): vals['website'] = self._clean_website(vals['website']) if vals.get('parent_id'): vals['company_name'] = False tools.image_resize_images(vals) partner = super(Partner, self).create(vals) partner._fields_sync(vals) partner._handle_first_contact_creation() return partner @api.multi def create_company(self): self.ensure_one() if self.company_name: # Create parent company values = dict(name=self.company_name, is_company=True) values.update(self._update_fields_values(self._address_fields())) new_company = self.create(values) # Set new company as my parent self.write({ 'parent_id': new_company.id, 'child_ids': [(1, partner_id, dict(parent_id=new_company.id)) for partner_id in self.child_ids.ids] }) return True @api.multi def open_commercial_entity(self): """ Utility method used to add an "Open Company" button in partner views """ self.ensure_one() return {'type': 'ir.actions.act_window', 'res_model': 'res.partner', 'view_mode': 'form', 'res_id': self.commercial_partner_id.id, 'target': 'current', 'flags': {'form': {'action_buttons': True}}} @api.multi def open_parent(self): """ Utility method used to add an "Open Parent" button in partner views """ self.ensure_one() address_form_id = self.env.ref('base.view_partner_address_form').id return {'type': 'ir.actions.act_window', 'res_model': 'res.partner', 'view_mode': 'form', 'views': [(address_form_id, 'form')], 'res_id': self.parent_id.id, 'target': 'new', 'flags': {'form': {'action_buttons': True}}} @api.multi def name_get(self): res = [] for partner in self: name = partner.name or '' if partner.company_name or partner.parent_id: if not name and partner.type in ['invoice', 'delivery', 'other']: name = dict(self.fields_get(['type'])['type']['selection'])[partner.type] if not partner.is_company: name = "%s, %s" % (partner.commercial_company_name or partner.parent_id.name, name) if self._context.get('show_address_only'): name = partner._display_address(without_company=True) if self._context.get('show_address'): name = name + "\n" + partner._display_address(without_company=True) name = name.replace('\n\n', '\n') name = name.replace('\n\n', '\n') if self._context.get('show_email') and partner.email: name = "%s <%s>" % (name, partner.email) if self._context.get('html_format'): name = name.replace('\n', '<br/>') res.append((partner.id, name)) return res def _parse_partner_name(self, text, context=None): """ Supported syntax: - 'Raoul <*****@*****.**>': will find name and email address - otherwise: default, everything is set as the name """ emails = tools.email_split(text.replace(' ', ',')) if emails: email = emails[0] name = text[:text.index(email)].replace('"', '').replace('<', '').strip() else: name, email = text, '' return name, email @api.model def name_create(self, name): """ Override of orm's name_create method for partners. The purpose is to handle some basic formats to create partners using the name_create. If only an email address is received and that the regex cannot find a name, the name will have the email value. If 'force_email' key in context: must find the email address. """ name, email = self._parse_partner_name(name) if self._context.get('force_email') and not email: raise UserError(_("Couldn't create contact without email address!")) if not name and email: name = email partner = self.create({self._rec_name: name or email, 'email': email or self.env.context.get('default_email', False)}) return partner.name_get()[0] @api.model def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None): """ Override search() to always show inactive children when searching via ``child_of`` operator. The ORM will always call search() with a simple domain of the form [('parent_id', 'in', [ids])]. """ # a special ``domain`` is set on the ``child_ids`` o2m to bypass this logic, as it uses similar domain expressions if len(args) == 1 and len(args[0]) == 3 and args[0][:2] == ('parent_id','in') \ and args[0][2] != [False]: self = self.with_context(active_test=False) return super(Partner, self)._search(args, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid) @api.model def name_search(self, name, args=None, operator='ilike', limit=100): if args is None: args = [] if name and operator in ('=', 'ilike', '=ilike', 'like', '=like'): self.check_access_rights('read') where_query = self._where_calc(args) self._apply_ir_rules(where_query, 'read') from_clause, where_clause, where_clause_params = where_query.get_sql() where_str = where_clause and (" WHERE %s AND " % where_clause) or ' WHERE ' # search on the name of the contacts and of its company search_name = name if operator in ('ilike', 'like'): search_name = '%%%s%%' % name if operator in ('=ilike', '=like'): operator = operator[1:] unaccent = get_unaccent_wrapper(self.env.cr) query = """SELECT id FROM res_partner {where} ({email} {operator} {percent} OR {display_name} {operator} {percent} OR {reference} {operator} {percent} OR {vat} {operator} {percent}) -- don't panic, trust postgres bitmap ORDER BY {display_name} {operator} {percent} desc, {display_name} """.format(where=where_str, operator=operator, email=unaccent('email'), display_name=unaccent('display_name'), reference=unaccent('ref'), percent=unaccent('%s'), vat=unaccent('vat'),) where_clause_params += [search_name]*5 if limit: query += ' limit %s' where_clause_params.append(limit) self.env.cr.execute(query, where_clause_params) partner_ids = [row[0] for row in self.env.cr.fetchall()] if partner_ids: return self.browse(partner_ids).name_get() else: return [] return super(Partner, self).name_search(name, args, operator=operator, limit=limit) @api.model def find_or_create(self, email): """ Find a partner with the given ``email`` or use :py:method:`~.name_create` to create one :param str email: email-like string, which should contain at least one email, e.g. ``"Raoul Grosbedon <*****@*****.**>"``""" assert email, 'an email is required for find_or_create to work' emails = tools.email_split(email) if emails: email = emails[0] partners = self.search([('email', '=ilike', email)], limit=1) return partners.id or self.name_create(email)[0] def _get_gravatar_image(self, email): email_hash = hashlib.md5(email.lower()).hexdigest() url = "https://www.gravatar.com/avatar/" + email_hash try: res = requests.get(url, params={'d': '404', 's': '128'}, timeout=5) if res.status_code != requests.codes.ok: return False except requests.exceptions.ConnectionError as e: return False return base64.b64encode(res.content) @api.multi def _email_send(self, email_from, subject, body, on_error=None): for partner in self.filtered('email'): tools.email_send(email_from, [partner.email], subject, body, on_error) return True @api.multi def address_get(self, adr_pref=None): """ Find contacts/addresses of the right type(s) by doing a depth-first-search through descendants within company boundaries (stop at entities flagged ``is_company``) then continuing the search at the ancestors that are within the same company boundaries. Defaults to partners of type ``'default'`` when the exact type is not found, or to the provided partner itself if no type ``'default'`` is found either. """ adr_pref = set(adr_pref or []) if 'contact' not in adr_pref: adr_pref.add('contact') result = {} visited = set() for partner in self: current_partner = partner while current_partner: to_scan = [current_partner] # Scan descendants, DFS while to_scan: record = to_scan.pop(0) visited.add(record) if record.type in adr_pref and not result.get(record.type): result[record.type] = record.id if len(result) == len(adr_pref): return result to_scan = [c for c in record.child_ids if c not in visited if not c.is_company] + to_scan # Continue scanning at ancestor if current_partner is not a commercial entity if current_partner.is_company or not current_partner.parent_id: break current_partner = current_partner.parent_id # default to type 'contact' or the partner itself default = result.get('contact', self.id or False) for adr_type in adr_pref: result[adr_type] = result.get(adr_type) or default return result @api.model def view_header_get(self, view_id, view_type): res = super(Partner, self).view_header_get(view_id, view_type) if res: return res if not self._context.get('category_id'): return False return _('Partners: ') + self.env['res.partner.category'].browse(self._context['category_id']).name @api.model @api.returns('self') def main_partner(self): ''' Return the main partner ''' return self.env.ref('base.main_partner') @api.model def _get_default_address_format(self): return "%(street)s\n%(street2)s\n%(city)s %(state_code)s %(zip)s\n%(country_name)s" @api.multi def _display_address(self, without_company=False): ''' The purpose of this function is to build and return an address formatted accordingly to the standards of the country where it belongs. :param address: browse record of the res.partner to format :returns: the address formatted in a display that fit its country habits (or the default ones if not country is specified) :rtype: string ''' # get the information that will be injected into the display format # get the address format address_format = self.country_id.address_format or \ self._get_default_address_format() args = { 'state_code': self.state_id.code or '', 'state_name': self.state_id.name or '', 'country_code': self.country_id.code or '', 'country_name': self.country_id.name or '', 'company_name': self.commercial_company_name or '', } for field in self._address_fields(): args[field] = getattr(self, field) or '' if without_company: args['company_name'] = '' elif self.commercial_company_name: address_format = '%(company_name)s\n' + address_format return address_format % args def _display_address_depends(self): # field dependencies of method _display_address() return self._address_fields() + [ 'country_id.address_format', 'country_id.code', 'country_id.name', 'company_name', 'state_id.code', 'state_id.name', ]
class LabTest(models.Model): _name = "lab.test" name = fields.Char(string="Name", readonly=True) master_id = fields.Many2one(comodel_name="lab.master", string="Lab-Test") price = fields.Float(string="Price") template = fields.Html(string="Template") report = fields.Html(string="Report") value_ids = fields.One2many(comodel_name="lab.test.detail.value", inverse_name="lab_id") image_ids = fields.One2many(comodel_name="lab.test.detail.image", inverse_name="lab_id") laboratory_id = fields.Many2one(comodel_name="arc.lab", string="Laboratory") report_pdf = fields.Binary(string="Report") file_name = fields.Char(string="File Name", default="report.pdf") progress = fields.Selection(selection=PROGRESS_INFO, string="Progress", default="draft") comment = fields.Text(string="Comment") company_id = fields.Many2one( comodel_name="res.company", string="Company", default=lambda self: self.env.user.company_id.id) @api.multi def trigger_report(self): value_recs = self.value_ids image_recs = self.image_ids data = {} for rec in value_recs: if rec.value: data[rec.sequence] = rec.value else: data[rec.sequence] = "" for rec in image_recs: if rec.image: data[rec.sequence] = rec.image else: data[rec.sequence] = "" data[0] = self.laboratory_id.name data[1] = self.laboratory_id.patient_id.name data[2] = self.laboratory_id.patient_id.patient_uid data[3] = self.laboratory_id.patient_id.address data[4] = self.laboratory_id.patient_id.mobile data[5] = self.laboratory_id.patient_id.email print data template = self.template if self.paramsi == 13: report = template.format(data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12], data[13]) if self.paramsi == 9: report = template.format(data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8]) if self.paramsi == 7: report = template.format(data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]) self.report = report pdfkit.from_string(report, '/opt/kon/out.pdf') pdf_file = open('/opt/kon/out.pdf', 'rb') out = pdf_file.read() pdf_file.close() gentextfile = base64.b64encode(out) self.report_pdf = gentextfile self.progress = "completed" @api.model def create(self, vals): master_rec = self.env["lab.master"].search([("id", "=", vals["master_id"])]) value_recs = self.env["lab.master.detail.value"].search([ ("lab_id", "=", vals["master_id"]) ]) image_recs = self.env["lab.master.detail.image"].search([ ("lab_id", "=", vals["master_id"]) ]) vals["price"] = master_rec.price vals["template"] = master_rec.template vals["paramsi"] = master_rec.paramsi vals["name"] = self.env["ir.sequence"].next_by_code(self._name) lab_id = super(LabTest, self).create(vals) for rec in value_recs: self.env["lab.test.detail.value"].create({ "sequence": rec.sequence, "name": rec.name, "lab_id": lab_id.id }) for rec in image_recs: self.env["lab.test.detail.image"].create({ "sequence": rec.sequence, "name": rec.name, "lab_id": lab_id.id }) return lab_id
class IrActionsActWindow(models.Model): _name = 'ir.actions.act_window' _description = 'Action Window' _table = 'ir_act_window' _inherit = 'ir.actions.actions' _sequence = 'ir_actions_id_seq' _order = 'name' @api.constrains('res_model', 'src_model') def _check_model(self): for action in self: if action.res_model not in self.env: raise ValidationError( _('Invalid model name %r in action definition.') % action.res_model) if action.src_model and action.src_model not in self.env: raise ValidationError( _('Invalid model name %r in action definition.') % action.src_model) @api.depends('view_ids.view_mode', 'view_mode', 'view_id.type') def _compute_views(self): """ Compute an ordered list of the specific view modes that should be enabled when displaying the result of this action, along with the ID of the specific view to use for each mode, if any were required. This function hides the logic of determining the precedence between the view_modes string, the view_ids o2m, and the view_id m2o that can be set on the action. """ for act in self: act.views = [(view.view_id.id, view.view_mode) for view in act.view_ids] got_modes = [view.view_mode for view in act.view_ids] all_modes = act.view_mode.split(',') missing_modes = [ mode for mode in all_modes if mode not in got_modes ] if missing_modes: if act.view_id.type in missing_modes: # reorder missing modes to put view_id first if present missing_modes.remove(act.view_id.type) act.views.append((act.view_id.id, act.view_id.type)) act.views.extend([(False, mode) for mode in missing_modes]) @api.depends('res_model', 'search_view_id') def _compute_search_view(self): for act in self: fvg = self.env[act.res_model].fields_view_get( act.search_view_id.id, 'search') act.search_view = str(fvg) name = fields.Char(string='Action Name', translate=True) type = fields.Char(default="ir.actions.act_window") view_id = fields.Many2one('ir.ui.view', string='View Ref.', ondelete='set null') domain = fields.Char( string='Domain Value', help= "Optional domain filtering of the destination data, as a Python expression" ) context = fields.Char( string='Context Value', default={}, required=True, help= "Context dictionary as Python expression, empty by default (Default: {})" ) res_id = fields.Integer( string='Record ID', help= "Database ID of record to open in form view, when ``view_mode`` is set to 'form' only" ) res_model = fields.Char( string='Destination Model', required=True, help="Model name of the object to open in the view window") src_model = fields.Char( string='Source Model', help= "Optional model name of the objects on which this action should be visible" ) target = fields.Selection([('current', 'Current Window'), ('new', 'New Window'), ('inline', 'Inline Edit'), ('fullscreen', 'Full Screen'), ('main', 'Main action of Current Window')], default="current", string='Target Window') view_mode = fields.Char( required=True, default='tree,form', help= "Comma-separated list of allowed view modes, such as 'form', 'tree', 'calendar', etc. (Default: tree,form)" ) view_type = fields.Selection( [('tree', 'Tree'), ('form', 'Form')], default="form", string='View Type', required=True, help= "View type: Tree type to use for the tree view, set to 'tree' for a hierarchical tree view, or 'form' for a regular list view" ) usage = fields.Char( string='Action Usage', help="Used to filter menu and home actions from the user form.") view_ids = fields.One2many('ir.actions.act_window.view', 'act_window_id', string='No of Views') views = fields.Binary(compute='_compute_views', help="This function field computes the ordered list of views that should be enabled " \ "when displaying the result of an action, federating view mode, views and " \ "reference view. The result is returned as an ordered list of pairs (view_id,view_mode).") limit = fields.Integer(default=80, help='Default limit for the list view') groups_id = fields.Many2many('res.groups', 'ir_act_window_group_rel', 'act_id', 'gid', string='Groups') search_view_id = fields.Many2one('ir.ui.view', string='Search View Ref.') filter = fields.Boolean() auto_search = fields.Boolean(default=True) search_view = fields.Text(compute='_compute_search_view') multi = fields.Boolean( string='Restrict to lists', help= "If checked and the action is bound to a model, it will only appear in the More menu on list views" ) @api.multi def read(self, fields=None, load='_classic_read'): """ call the method get_empty_list_help of the model and set the window action help message """ result = super(IrActionsActWindow, self).read(fields, load=load) if not fields or 'help' in fields: for values in result: model = values.get('res_model') if model in self.env: eval_ctx = dict(self.env.context) try: ctx = safe_eval(values.get('context', '{}'), eval_ctx) except: ctx = {} values['help'] = self.with_context( **ctx).env[model].get_empty_list_help( values.get('help', '')) return result @api.model def for_xml_id(self, module, xml_id): """ Returns the act_window object created for the provided xml_id :param module: the module the act_window originates in :param xml_id: the namespace-less id of the action (the @id attribute from the XML file) :return: A read() view of the ir.actions.act_window """ record = self.env.ref("%s.%s" % (module, xml_id)) return record.read()[0] @api.model_create_multi def create(self, vals_list): self.clear_caches() return super(IrActionsActWindow, self).create(vals_list) @api.multi def unlink(self): self.clear_caches() return super(IrActionsActWindow, self).unlink() @api.multi def exists(self): ids = self._existing() existing = self.filtered(lambda rec: rec.id in ids) if len(existing) < len(self): # mark missing records in cache with a failed value exc = MissingError(_("Record does not exist or has been deleted.")) for record in (self - existing): record._cache.set_failed(self._fields, exc) return existing @api.model @tools.ormcache() def _existing(self): self._cr.execute("SELECT id FROM %s" % self._table) return set(row[0] for row in self._cr.fetchall())
class HrEmployeePrivate(models.Model): """ NB: Any field only available on the model hr.employee (i.e. not on the hr.employee.public model) should have `groups="hr.group_hr_user"` on its definition to avoid being prefetched when the user hasn't access to the hr.employee model. Indeed, the prefetch loads the data for all the fields that are available according to the group defined on them. """ _name = "hr.employee" _description = "Employee" _order = 'name' _inherit = [ 'hr.employee.base', 'mail.thread', 'mail.activity.mixin', 'resource.mixin', 'avatar.mixin' ] _mail_post_access = 'read' # resource and user # required on the resource, make sure required="True" set in the view name = fields.Char(string="Employee Name", related='resource_id.name', store=True, readonly=False, tracking=True) user_id = fields.Many2one('res.users', 'User', related='resource_id.user_id', store=True, readonly=False) user_partner_id = fields.Many2one(related='user_id.partner_id', related_sudo=False, string="User's partner") active = fields.Boolean('Active', related='resource_id.active', default=True, store=True, readonly=False) company_id = fields.Many2one('res.company', required=True) company_country_id = fields.Many2one('res.country', 'Company Country', related='company_id.country_id', readonly=True) company_country_code = fields.Char(related='company_country_id.code', readonly=True) # private partner address_home_id = fields.Many2one( 'res.partner', 'Address', help= 'Enter here the private address of the employee, not the one linked to your company.', groups="hr.group_hr_user", tracking=True, domain= "['|', ('company_id', '=', False), ('company_id', '=', company_id)]") is_address_home_a_company = fields.Boolean( 'The employee address has a company linked', compute='_compute_is_address_home_a_company', ) private_email = fields.Char(related='address_home_id.email', string="Private Email", groups="hr.group_hr_user") lang = fields.Selection(related='address_home_id.lang', string="Lang", groups="hr.group_hr_user", readonly=False) country_id = fields.Many2one('res.country', 'Nationality (Country)', groups="hr.group_hr_user", tracking=True) gender = fields.Selection([('male', 'Male'), ('female', 'Female'), ('other', 'Other')], groups="hr.group_hr_user", tracking=True) marital = fields.Selection([('single', 'Single'), ('married', 'Married'), ('cohabitant', 'Legal Cohabitant'), ('widower', 'Widower'), ('divorced', 'Divorced')], string='Marital Status', groups="hr.group_hr_user", default='single', tracking=True) spouse_complete_name = fields.Char(string="Spouse Complete Name", groups="hr.group_hr_user", tracking=True) spouse_birthdate = fields.Date(string="Spouse Birthdate", groups="hr.group_hr_user", tracking=True) children = fields.Integer(string='Number of Children', groups="hr.group_hr_user", tracking=True) place_of_birth = fields.Char('Place of Birth', groups="hr.group_hr_user", tracking=True) country_of_birth = fields.Many2one('res.country', string="Country of Birth", groups="hr.group_hr_user", tracking=True) birthday = fields.Date('Date of Birth', groups="hr.group_hr_user", tracking=True) ssnid = fields.Char('SSN No', help='Social Security Number', groups="hr.group_hr_user", tracking=True) sinid = fields.Char('SIN No', help='Social Insurance Number', groups="hr.group_hr_user", tracking=True) identification_id = fields.Char(string='Identification No', groups="hr.group_hr_user", tracking=True) passport_id = fields.Char('Passport No', groups="hr.group_hr_user", tracking=True) bank_account_id = fields.Many2one( 'res.partner.bank', 'Bank Account Number', domain= "[('partner_id', '=', address_home_id), '|', ('company_id', '=', False), ('company_id', '=', company_id)]", groups="hr.group_hr_user", tracking=True, help='Employee bank salary account') permit_no = fields.Char('Work Permit No', groups="hr.group_hr_user", tracking=True) visa_no = fields.Char('Visa No', groups="hr.group_hr_user", tracking=True) visa_expire = fields.Date('Visa Expire Date', groups="hr.group_hr_user", tracking=True) work_permit_expiration_date = fields.Date('Work Permit Expiration Date', groups="hr.group_hr_user", tracking=True) has_work_permit = fields.Binary(string="Work Permit", groups="hr.group_hr_user", tracking=True) work_permit_scheduled_activity = fields.Boolean(default=False, groups="hr.group_hr_user") additional_note = fields.Text(string='Additional Note', groups="hr.group_hr_user", tracking=True) certificate = fields.Selection([ ('graduate', 'Graduate'), ('bachelor', 'Bachelor'), ('master', 'Master'), ('doctor', 'Doctor'), ('other', 'Other'), ], 'Certificate Level', default='other', groups="hr.group_hr_user", tracking=True) study_field = fields.Char("Field of Study", groups="hr.group_hr_user", tracking=True) study_school = fields.Char("School", groups="hr.group_hr_user", tracking=True) emergency_contact = fields.Char("Emergency Contact", groups="hr.group_hr_user", tracking=True) emergency_phone = fields.Char("Emergency Phone", groups="hr.group_hr_user", tracking=True) km_home_work = fields.Integer(string="Home-Work Distance", groups="hr.group_hr_user", tracking=True) job_id = fields.Many2one(tracking=True) phone = fields.Char(related='address_home_id.phone', related_sudo=False, readonly=False, string="Private Phone", groups="hr.group_hr_user") # employee in company child_ids = fields.One2many('hr.employee', 'parent_id', string='Direct subordinates') category_ids = fields.Many2many('hr.employee.category', 'employee_category_rel', 'emp_id', 'category_id', groups="hr.group_hr_manager", string='Tags') # misc notes = fields.Text('Notes', groups="hr.group_hr_user") color = fields.Integer('Color Index', default=0, groups="hr.group_hr_user") barcode = fields.Char(string="Badge ID", help="ID used for employee identification.", groups="hr.group_hr_user", copy=False) pin = fields.Char( string="PIN", groups="hr.group_hr_user", copy=False, help= "PIN used to Check In/Out in the Kiosk Mode of the Attendance application (if enabled in Configuration) and to change the cashier in the Point of Sale application." ) departure_reason_id = fields.Many2one("hr.departure.reason", string="Departure Reason", groups="hr.group_hr_user", copy=False, tracking=True, ondelete='restrict') departure_description = fields.Text(string="Additional Information", groups="hr.group_hr_user", copy=False, tracking=True) departure_date = fields.Date(string="Departure Date", groups="hr.group_hr_user", copy=False, tracking=True) message_main_attachment_id = fields.Many2one(groups="hr.group_hr_user") id_card = fields.Binary(string="ID Card Copy", groups="hr.group_hr_user") driving_license = fields.Binary(string="Driving License", groups="hr.group_hr_user") _sql_constraints = [ ('barcode_uniq', 'unique (barcode)', "The Badge ID must be unique, this one is already assigned to another employee." ), ('user_uniq', 'unique (user_id, company_id)', "A user cannot be linked to multiple employees in the same company.") ] @api.depends('name', 'user_id.avatar_1920', 'image_1920') def _compute_avatar_1920(self): super()._compute_avatar_1920() @api.depends('name', 'user_id.avatar_1024', 'image_1024') def _compute_avatar_1024(self): super()._compute_avatar_1024() @api.depends('name', 'user_id.avatar_512', 'image_512') def _compute_avatar_512(self): super()._compute_avatar_512() @api.depends('name', 'user_id.avatar_256', 'image_256') def _compute_avatar_256(self): super()._compute_avatar_256() @api.depends('name', 'user_id.avatar_128', 'image_128') def _compute_avatar_128(self): super()._compute_avatar_128() def _compute_avatar(self, avatar_field, image_field): for employee in self: avatar = employee[image_field] if not avatar: if employee.user_id: avatar = employee.user_id[avatar_field] else: avatar = employee._avatar_get_placeholder() employee[avatar_field] = avatar def name_get(self): if self.check_access_rights('read', raise_exception=False): return super(HrEmployeePrivate, self).name_get() return self.env['hr.employee.public'].browse(self.ids).name_get() def _read(self, fields): if self.check_access_rights('read', raise_exception=False): return super(HrEmployeePrivate, self)._read(fields) res = self.env['hr.employee.public'].browse(self.ids).read(fields) for r in res: record = self.browse(r['id']) record._update_cache({k: v for k, v in r.items() if k in fields}, validate=False) @api.model def _cron_check_work_permit_validity(self): # Called by a cron # Schedule an activity 1 month before the work permit expires outdated_days = fields.Date.today() + relativedelta(months=+1) nearly_expired_work_permits = self.search([ ('work_permit_scheduled_activity', '=', False), ('work_permit_expiration_date', '<', outdated_days) ]) employees_scheduled = self.env['hr.employee'] for employee in nearly_expired_work_permits.filtered( lambda employee: employee.parent_id): responsible_user_id = employee.parent_id.user_id.id if responsible_user_id: employees_scheduled |= employee lang = self.env['res.partner'].browse(responsible_user_id).lang formated_date = format_date( employee.env, employee.work_permit_expiration_date, date_format="dd MMMM y", lang_code=lang) employee.activity_schedule( 'mail.mail_activity_data_todo', note=_( 'The work permit of %(employee)s expires at %(date)s.', employee=employee.name, date=formated_date), user_id=responsible_user_id) employees_scheduled.write({'work_permit_scheduled_activity': True}) def read(self, fields, load='_classic_read'): if self.check_access_rights('read', raise_exception=False): return super(HrEmployeePrivate, self).read(fields, load=load) private_fields = set(fields).difference( self.env['hr.employee.public']._fields.keys()) if private_fields: raise AccessError( _('The fields "%s" you try to read is not available on the public employee profile.' ) % (','.join(private_fields))) return self.env['hr.employee.public'].browse(self.ids).read(fields, load=load) @api.model def load_views(self, views, options=None): if self.check_access_rights('read', raise_exception=False): return super(HrEmployeePrivate, self).load_views(views, options=options) return self.env['hr.employee.public'].load_views(views, options=options) @api.model def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None): """ We override the _search because it is the method that checks the access rights This is correct to override the _search. That way we enforce the fact that calling search on an hr.employee returns a hr.employee recordset, even if you don't have access to this model, as the result of _search (the ids of the public employees) is to be browsed on the hr.employee model. This can be trusted as the ids of the public employees exactly match the ids of the related hr.employee. """ if self.check_access_rights('read', raise_exception=False): return super(HrEmployeePrivate, self)._search(args, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid) ids = self.env['hr.employee.public']._search( args, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid) if not count and isinstance(ids, Query): # the result is expected from this table, so we should link tables ids = super(HrEmployeePrivate, self.sudo())._search([('id', 'in', ids)]) return ids def get_formview_id(self, access_uid=None): """ Override this method in order to redirect many2one towards the right model depending on access_uid """ if access_uid: self_sudo = self.with_user(access_uid) else: self_sudo = self if self_sudo.check_access_rights('read', raise_exception=False): return super(HrEmployeePrivate, self).get_formview_id(access_uid=access_uid) # Hardcode the form view for public employee return self.env.ref('hr.hr_employee_public_view_form').id def get_formview_action(self, access_uid=None): """ Override this method in order to redirect many2one towards the right model depending on access_uid """ res = super(HrEmployeePrivate, self).get_formview_action(access_uid=access_uid) if access_uid: self_sudo = self.with_user(access_uid) else: self_sudo = self if not self_sudo.check_access_rights('read', raise_exception=False): res['res_model'] = 'hr.employee.public' return res @api.constrains('pin') def _verify_pin(self): for employee in self: if employee.pin and not employee.pin.isdigit(): raise ValidationError( _("The PIN must be a sequence of digits.")) @api.onchange('user_id') def _onchange_user(self): if self.user_id: self.update(self._sync_user(self.user_id, (bool(self.image_1920)))) if not self.name: self.name = self.user_id.name @api.onchange('resource_calendar_id') def _onchange_timezone(self): if self.resource_calendar_id and not self.tz: self.tz = self.resource_calendar_id.tz def _sync_user(self, user, employee_has_image=False): vals = dict( work_email=user.email, user_id=user.id, ) if not employee_has_image: vals['image_1920'] = user.image_1920 if user.tz: vals['tz'] = user.tz return vals @api.model def create(self, vals): if vals.get('user_id'): user = self.env['res.users'].browse(vals['user_id']) vals.update(self._sync_user(user, bool(vals.get('image_1920')))) vals['name'] = vals.get('name', user.name) employee = super(HrEmployeePrivate, self).create(vals) if employee.department_id: self.env['mail.channel'].sudo().search([ ('subscription_department_ids', 'in', employee.department_id.id) ])._subscribe_users_automatically() # Launch onboarding plans if not employee._launch_plans_from_trigger( trigger='employee_creation'): # Keep the recommend message if no plans are launched url = '/web#%s' % url_encode( { 'action': 'hr.plan_wizard_action', 'active_id': employee.id, 'active_model': 'hr.employee', 'menu_id': self.env.ref('hr.menu_hr_root').id, }) employee._message_log(body=_( '<b>Congratulations!</b> May I recommend you to setup an <a href="%s">onboarding plan?</a>' ) % (url)) return employee def write(self, vals): if 'address_home_id' in vals: account_id = vals.get('bank_account_id') or self.bank_account_id.id if account_id: self.env['res.partner.bank'].browse( account_id).partner_id = vals['address_home_id'] if vals.get('user_id'): # Update the profile pictures with user, except if provided vals.update( self._sync_user(self.env['res.users'].browse(vals['user_id']), (bool(self.image_1920)))) if 'work_permit_expiration_date' in vals: vals['work_permit_scheduled_activity'] = False res = super(HrEmployeePrivate, self).write(vals) if vals.get('department_id') or vals.get('user_id'): department_id = vals['department_id'] if vals.get( 'department_id') else self[:1].department_id.id # When added to a department or changing user, subscribe to the channels auto-subscribed by department self.env['mail.channel'].sudo().search([ ('subscription_department_ids', 'in', department_id) ])._subscribe_users_automatically() return res def unlink(self): resources = self.mapped('resource_id') super(HrEmployeePrivate, self).unlink() return resources.unlink() def _get_employee_m2o_to_empty_on_archived_employees(self): return ['parent_id', 'coach_id'] def _get_user_m2o_to_empty_on_archived_employees(self): return [] def toggle_active(self): res = super(HrEmployeePrivate, self).toggle_active() unarchived_employees = self.filtered(lambda employee: employee.active) unarchived_employees.write({ 'departure_reason_id': False, 'departure_description': False, 'departure_date': False }) archived_addresses = unarchived_employees.mapped( 'address_home_id').filtered(lambda addr: not addr.active) archived_addresses.toggle_active() archived_employees = self.filtered(lambda e: not e.active) if archived_employees: # Empty links to this employees (example: manager, coach, time off responsible, ...) employee_fields_to_empty = self._get_employee_m2o_to_empty_on_archived_employees( ) user_fields_to_empty = self._get_user_m2o_to_empty_on_archived_employees( ) employee_domain = [[(field, 'in', archived_employees.ids)] for field in employee_fields_to_empty] user_domain = [[(field, 'in', archived_employees.user_id.ids) for field in user_fields_to_empty]] employees = self.env['hr.employee'].search( expression.OR(employee_domain + user_domain)) for employee in employees: for field in employee_fields_to_empty: if employee[field] in archived_employees: employee[field] = False for field in user_fields_to_empty: if employee[field] in archived_employees.user_id: employee[field] = False # Launch automatic offboarding plans archived_employees._launch_plans_from_trigger( trigger='employee_archive') if len(self) == 1 and not self.active: return { 'type': 'ir.actions.act_window', 'name': _('Register Departure'), 'res_model': 'hr.departure.wizard', 'view_mode': 'form', 'target': 'new', 'context': { 'active_id': self.id }, 'views': [[False, 'form']] } return res @api.onchange('company_id') def _onchange_company_id(self): if self._origin: return { 'warning': { 'title': _("Warning"), 'message': _("To avoid multi company issues (loosing the access to your previous contracts, leaves, ...), you should create another employee in the new company instead." ) } } def generate_random_barcode(self): for employee in self: employee.barcode = '041' + "".join( choice(digits) for i in range(9)) @api.depends('address_home_id.parent_id') def _compute_is_address_home_a_company(self): """Checks that chosen address (res.partner) is not linked to a company. """ for employee in self: try: employee.is_address_home_a_company = employee.address_home_id.parent_id.id is not False except AccessError: employee.is_address_home_a_company = False def _get_tz(self): # Finds the first valid timezone in his tz, his work hours tz, # the company calendar tz or UTC and returns it as a string self.ensure_one() return self.tz or\ self.resource_calendar_id.tz or\ self.company_id.resource_calendar_id.tz or\ 'UTC' def _get_tz_batch(self): # Finds the first valid timezone in his tz, his work hours tz, # the company calendar tz or UTC # Returns a dict {employee_id: tz} return {emp.id: emp._get_tz() for emp in self} # --------------------------------------------------------- # Business Methods # --------------------------------------------------------- @api.model def get_import_templates(self): return [{ 'label': _('Import Template for Employees'), 'template': '/hr/static/xls/hr_employee.xls' }] def _post_author(self): """ When a user updates his own employee's data, all operations are performed by super user. However, tracking messages should not be posted as OdooBot but as the actual user. This method is used in the overrides of `_message_log` and `message_post` to post messages as the correct user. """ real_user = self.env.context.get('binary_field_real_user') if self.env.is_superuser() and real_user: self = self.with_user(real_user) return self def _launch_plans_from_trigger(self, trigger): ''' Launches all plans for given trigger Returns False if no plans are launched, True otherwise ''' plan_ids = self.env['hr.plan'].search([('trigger', '=', trigger)]) if not plan_ids or not self: return False #Group plans and employees by company id plans_per_company = defaultdict(lambda: self.env['hr.plan']) for plan_id in plan_ids: plans_per_company[plan_id.company_id.id] |= plan_id employees_per_company = defaultdict(lambda: self.env['hr.employee']) for employee_id in self: employees_per_company[employee_id.company_id.id] |= employee_id #Launch the plans for company_id in employees_per_company: employees_per_company[company_id]._launch_plan( plans_per_company[company_id]) return True def _launch_plan(self, plan_ids): ''' Launch all given plans ''' for employee_id in self: for plan_id in plan_ids: employee_id._message_log(body=_('Plan %s has been launched.', plan_id.name), ) errors = [] for activity_type in plan_id.plan_activity_type_ids: responsible = False try: responsible = activity_type.get_responsible_id( employee_id) except UserError as error: errors.append( _( 'Warning ! The step "%(name)s: %(summary)s" assigned to %(responsible)s ' 'could not be started because: "%(error)s"', name=activity_type.activity_type_id.name, summary=activity_type.summary, responsible=activity_type.responsible, error=str(error))) continue if self.env['hr.employee'].with_user( responsible).check_access_rights( 'read', raise_exception=False): if activity_type.deadline_type == 'default': date_deadline = self.env[ 'mail.activity']._calculate_date_deadline( activity_type.activity_type_id) elif activity_type.deadline_type == 'plan_active': date_deadline = fields.Date.context_today(self) elif activity_type.deadline_type == 'trigger_offset': date_deadline = fields.Date.add( fields.Date.context_today(self), days=activity_type.deadline_days) employee_id.activity_schedule( activity_type_id=activity_type.activity_type_id.id, summary=activity_type.summary, note=activity_type.note, user_id=responsible.id, date_deadline=date_deadline, ) employee_id._message_log(body='<br/>'.join(errors), ) def _get_unusual_days(self, date_from, date_to=None): # Checking the calendar directly allows to not grey out the leaves taken # by the employee self.ensure_one() calendar = self.resource_calendar_id if not calendar: return {} dfrom = datetime.combine(fields.Date.from_string(date_from), time.min).replace(tzinfo=pytz.UTC) dto = datetime.combine(fields.Date.from_string(date_to), time.max).replace(tzinfo=pytz.UTC) works = { d[0].date() for d in calendar._work_intervals_batch(dfrom, dto)[False] } return { fields.Date.to_string(day.date()): (day.date() not in works) for day in rrule(DAILY, dfrom, until=dto) } # --------------------------------------------------------- # Messaging # --------------------------------------------------------- def _message_log(self, **kwargs): return super(HrEmployeePrivate, self._post_author())._message_log(**kwargs) @api.returns('mail.message', lambda value: value.id) def message_post(self, **kwargs): return super(HrEmployeePrivate, self._post_author()).message_post(**kwargs) def _sms_get_partner_fields(self): return ['user_partner_id'] def _sms_get_number_fields(self): return ['mobile_phone']
class PurchaseOrder(models.Model): _inherit = 'purchase.order' name = fields.Char('Order Reference', required=True, index=True, copy=False, default='Nueva orden') @api.multi def action_rfq_send(self): """ Le cambiamos el estado a SDP enviada :return: """ res = super(PurchaseOrder, self).action_rfq_send() self.write({ 'state': 'sent' }) return res @api.multi def to_approve(self): """ Solicitar aprobación de orden de compra """ self.update({'state': 'to approve'}) # Enviar correo a usuarios para aprobación self.env['eliterp.managerial.helps'].send_mail(self.id, self._name, 'eliterp_approve_purchase_order_mail') @api.multi def button_confirm(self): """ Agregamos confirmación de OC :return: object """ res = super(PurchaseOrder, self).button_confirm() self.write({ 'state': 'purchase', 'approval_user': self._uid }) return res @api.multi def approve(self): """ Aprobar orden de compra """ self.update({ 'state': 'approve', }) reference = fields.Char('Referencia', track_visibility='onchange') attach_order = fields.Binary('Adjuntar documento', attachment=True, track_visibility='onchange') # CM invoice_status = fields.Selection([ ('no', 'Pendiente'), ('to invoice', 'Para facturar'), ('invoiced', 'Facturado'), ], string='Estado de facturación', compute='_get_invoiced', store=True, readonly=True, copy=False, default='no', track_visibility='onchange') approval_user = fields.Many2one('res.users', 'Aprobado por') state = fields.Selection([ ('draft', 'SDP Borrador'), ('sent', 'SDP Enviada'), ('approve', 'Orden de compra'), ('to approve', 'OCS por aprobar'), ('purchase', 'OCS Aprobado'), ('done', 'Bloqueado'), ('cancel', 'Negado') ], string='Status', readonly=True, index=True, copy=False, default='draft', track_visibility='onchange') # Campos modifcada la trazabilidad READONLY_STATES = { 'purchase': [('readonly', True)], 'done': [('readonly', True)], 'cancel': [('readonly', True)], } partner_id = fields.Many2one('res.partner', string='Vendor', required=True, states=READONLY_STATES, change_default=True, track_visibility='onchange') amount_untaxed = fields.Monetary(string='Untaxed Amount', store=True, readonly=True, compute='_amount_all', track_visibility='onchange')
class ProductProduct(models.Model): _name = "product.product" _description = "Product" _inherits = {'product.template': 'product_tmpl_id'} _inherit = ['mail.thread'] _order = 'default_code, name, id' price = fields.Float('Price', compute='_compute_product_price', digits=dp.get_precision('Product Price'), inverse='_set_product_price') price_extra = fields.Float( 'Variant Price Extra', compute='_compute_product_price_extra', digits=dp.get_precision('Product Price'), help="This is the sum of the extra price of all attributes") lst_price = fields.Float( 'Sale Price', compute='_compute_product_lst_price', digits=dp.get_precision('Product Price'), inverse='_set_product_lst_price', help= "The sale price is managed from the product template. Click on the 'Variant Prices' button to set the extra attribute prices." ) default_code = fields.Char('Internal Reference', index=True) code = fields.Char('Internal Reference', compute='_compute_product_code') partner_ref = fields.Char('Customer Ref', compute='_compute_partner_ref') active = fields.Boolean( 'Active', default=True, help= "If unchecked, it will allow you to hide the product without removing it." ) product_tmpl_id = fields.Many2one('product.template', 'Product Template', auto_join=True, index=True, ondelete="cascade", required=True) barcode = fields.Char( 'Barcode', copy=False, oldname='ean13', help="International Article Number used for product identification.") attribute_value_ids = fields.Many2many('product.attribute.value', string='Attributes', ondelete='restrict') # image: all image fields are base64 encoded and PIL-supported image_variant = fields.Binary( "Variant Image", attachment=True, help= "This field holds the image used as image for the product variant, limited to 1024x1024px." ) image = fields.Binary( "Big-sized image", compute='_compute_images', inverse='_set_image', help= "Image of the product variant (Big-sized image of product template if false). It is automatically " "resized as a 1024x1024px image, with aspect ratio preserved.") image_small = fields.Binary( "Small-sized image", compute='_compute_images', inverse='_set_image_small', help= "Image of the product variant (Small-sized image of product template if false)." ) image_medium = fields.Binary( "Medium-sized image", compute='_compute_images', inverse='_set_image_medium', help= "Image of the product variant (Medium-sized image of product template if false)." ) standard_price = fields.Float( 'Cost', company_dependent=True, digits=dp.get_precision('Product Price'), groups="base.group_user", help= "Cost of the product template used for standard stock valuation in accounting and used as a base price on purchase orders. " "Expressed in the default unit of measure of the product.") volume = fields.Float('Volume', help="The volume in m3.") weight = fields.Float( 'Weight', digits=dp.get_precision('Stock Weight'), help= "The weight of the contents in Kg, not including any packaging, etc.") pricelist_item_ids = fields.Many2many('product.pricelist.item', 'Pricelist Items', compute='_get_pricelist_items') _sql_constraints = [ ('barcode_uniq', 'unique(barcode)', _("A barcode can only be assigned to one product !")), ] def _compute_product_price(self): prices = {} pricelist_id_or_name = self._context.get('pricelist') if pricelist_id_or_name: pricelist = None partner = self._context.get('partner', False) quantity = self._context.get('quantity', 1.0) # Support context pricelists specified as display_name or ID for compatibility if isinstance(pricelist_id_or_name, basestring): pricelist_name_search = self.env[ 'product.pricelist'].name_search(pricelist_id_or_name, operator='=', limit=1) if pricelist_name_search: pricelist = self.env['product.pricelist'].browse( [pricelist_name_search[0][0]]) elif isinstance(pricelist_id_or_name, (int, long)): pricelist = self.env['product.pricelist'].browse( pricelist_id_or_name) if pricelist: quantities = [quantity] * len(self) partners = [partner] * len(self) prices = pricelist.get_products_price(self, quantities, partners) for product in self: product.price = prices.get(product.id, 0.0) def _set_product_price(self): for product in self: if self._context.get('uom'): value = self.env['product.uom'].browse( self._context['uom'])._compute_price( product.price, product.uom_id) else: value = product.price value -= product.price_extra product.write({'list_price': value}) def _set_product_lst_price(self): for product in self: if self._context.get('uom'): value = self.env['product.uom'].browse( self._context['uom'])._compute_price( product.lst_price, product.uom_id) else: value = product.lst_price value -= product.price_extra product.write({'list_price': value}) @api.depends('attribute_value_ids.price_ids.price_extra', 'attribute_value_ids.price_ids.product_tmpl_id') def _compute_product_price_extra(self): # TDE FIXME: do a real multi and optimize a bit ? for product in self: price_extra = 0.0 for attribute_price in product.mapped( 'attribute_value_ids.price_ids'): if attribute_price.product_tmpl_id == product.product_tmpl_id: price_extra += attribute_price.price_extra product.price_extra = price_extra @api.depends('list_price', 'price_extra') def _compute_product_lst_price(self): to_uom = None if 'uom' in self._context: to_uom = self.env['product.uom'].browse([self._context['uom']]) for product in self: if to_uom: list_price = product.uom_id._compute_price( product.list_price, to_uom) else: list_price = product.list_price product.lst_price = list_price + product.price_extra @api.one def _compute_product_code(self): for supplier_info in self.seller_ids: if supplier_info.name.id == self._context.get('partner_id'): self.code = supplier_info.product_code or self.default_code break else: self.code = self.default_code @api.one def _compute_partner_ref(self): for supplier_info in self.seller_ids: if supplier_info.name.id == self._context.get('partner_id'): product_name = supplier_info.product_name or self.default_code break else: product_name = self.name self.partner_ref = '%s%s' % (self.code and '[%s] ' % self.code or '', product_name) @api.one @api.depends('image_variant', 'product_tmpl_id.image') def _compute_images(self): if self._context.get('bin_size'): self.image_medium = self.image_variant self.image_small = self.image_variant self.image = self.image_variant else: resized_images = tools.image_get_resized_images( self.image_variant, return_big=True, avoid_resize_medium=True) self.image_medium = resized_images['image_medium'] self.image_small = resized_images['image_small'] self.image = resized_images['image'] if not self.image_medium: self.image_medium = self.product_tmpl_id.image_medium if not self.image_small: self.image_small = self.product_tmpl_id.image_small if not self.image: self.image = self.product_tmpl_id.image @api.one def _set_image(self): self._set_image_value(self.image) @api.one def _set_image_medium(self): self._set_image_value(self.image_medium) @api.one def _set_image_small(self): self._set_image_value(self.image_small) @api.one def _set_image_value(self, value): image = tools.image_resize_image_big(value) if self.product_tmpl_id.image: self.image_variant = image else: self.product_tmpl_id.image = image @api.one def _get_pricelist_items(self): self.pricelist_item_ids = self.env['product.pricelist.item'].search([ '|', ('product_id', '=', self.id), ('product_tmpl_id', '=', self.product_tmpl_id.id) ]).ids @api.constrains('attribute_value_ids') def _check_attribute_value_ids(self): for product in self: attributes = self.env['product.attribute'] for value in product.attribute_value_ids: if value.attribute_id in attributes: raise ValidationError( _('Error! It is not allowed to choose more than one value for a given attribute.' )) if value.attribute_id.create_variant: attributes |= value.attribute_id return True @api.onchange('uom_id', 'uom_po_id') def _onchange_uom(self): if self.uom_id and self.uom_po_id and self.uom_id.category_id != self.uom_po_id.category_id: self.uom_po_id = self.uom_id @api.model def create(self, vals): product = super( ProductProduct, self.with_context(create_product_product=True)).create(vals) # When a unique variant is created from tmpl then the standard price is set by _set_standard_price if not (self.env.context.get('create_from_tmpl') and len(product.product_tmpl_id.product_variant_ids) == 1): product._set_standard_price(vals.get('standard_price') or 0.0) return product @api.multi def write(self, values): ''' Store the standard price change in order to be able to retrieve the cost of a product for a given date''' res = super(ProductProduct, self).write(values) if 'standard_price' in values: self._set_standard_price(values['standard_price']) return res @api.multi def unlink(self): unlink_products = self.env['product.product'] unlink_templates = self.env['product.template'] for product in self: # Check if product still exists, in case it has been unlinked by unlinking its template if not product.exists(): continue # Check if the product is last product of this template other_products = self.search([('product_tmpl_id', '=', product.product_tmpl_id.id), ('id', '!=', product.id)]) if not other_products: unlink_templates |= product.product_tmpl_id unlink_products |= product res = super(ProductProduct, unlink_products).unlink() # delete templates after calling super, as deleting template could lead to deleting # products due to ondelete='cascade' unlink_templates.unlink() return res @api.multi def copy(self, default=None): # TDE FIXME: clean context / variant brol if default is None: default = {} if self._context.get('variant'): # if we copy a variant or create one, we keep the same template default['product_tmpl_id'] = self.product_tmpl_id.id elif 'name' not in default: default['name'] = self.name return super(ProductProduct, self).copy(default=default) @api.model def search(self, args, offset=0, limit=None, order=None, count=False): # TDE FIXME: strange if self._context.get('search_default_categ_id'): args.append((('categ_id', 'child_of', self._context['search_default_categ_id']))) return super(ProductProduct, self).search(args, offset=offset, limit=limit, order=order, count=count) @api.multi def name_get(self): # TDE: this could be cleaned a bit I think def _name_get(d): name = d.get('name', '') code = self._context.get('display_default_code', True) and d.get( 'default_code', False) or False if code: name = '[%s] %s' % (code, name) return (d['id'], name) partner_id = self._context.get('partner_id') if partner_id: partner_ids = [ partner_id, self.env['res.partner'].browse( partner_id).commercial_partner_id.id ] else: partner_ids = [] # all user don't have access to seller and partner # check access and use superuser self.check_access_rights("read") self.check_access_rule("read") result = [] for product in self.sudo(): # display only the attributes with multiple possible values on the template variable_attributes = product.attribute_line_ids.filtered( lambda l: len(l.value_ids) > 1).mapped('attribute_id') variant = product.attribute_value_ids._variant_name( variable_attributes) name = variant and "%s (%s)" % (product.name, variant) or product.name sellers = [] if partner_ids: sellers = [ x for x in product.seller_ids if (x.name.id in partner_ids) and (x.product_id == product) ] if not sellers: sellers = [ x for x in product.seller_ids if (x.name.id in partner_ids) and not x.product_id ] if sellers: for s in sellers: seller_variant = s.product_name and ( variant and "%s (%s)" % (s.product_name, variant) or s.product_name) or False mydict = { 'id': product.id, 'name': seller_variant or name, 'default_code': s.product_code or product.default_code, } temp = _name_get(mydict) if temp not in result: result.append(temp) else: mydict = { 'id': product.id, 'name': name, 'default_code': product.default_code, } result.append(_name_get(mydict)) return result @api.model def name_search(self, name='', args=None, operator='ilike', limit=100): if not args: args = [] if name: positive_operators = ['=', 'ilike', '=ilike', 'like', '=like'] products = self.env['product.product'] if operator in positive_operators: products = self.search([('default_code', '=', name)] + args, limit=limit) if not products: products = self.search([('barcode', '=', name)] + args, limit=limit) if not products and operator not in expression.NEGATIVE_TERM_OPERATORS: # Do not merge the 2 next lines into one single search, SQL search performance would be abysmal # on a database with thousands of matching products, due to the huge merge+unique needed for the # OR operator (and given the fact that the 'name' lookup results come from the ir.translation table # Performing a quick memory merge of ids in Python will give much better performance products = self.search(args + [('default_code', operator, name)], limit=limit) if not limit or len(products) < limit: # we may underrun the limit because of dupes in the results, that's fine limit2 = (limit - len(products)) if limit else False products += self.search(args + [('name', operator, name), ('id', 'not in', products.ids)], limit=limit2) elif not products and operator in expression.NEGATIVE_TERM_OPERATORS: domain = expression.OR([ [ '&', ('default_code', operator, name), ('name', operator, name) ], [ '&', ('default_code', '=', False), ('name', operator, name) ], ]) domain = expression.AND([args, domain]) products = self.search(domain, limit=limit) if not products and operator in positive_operators: ptrn = re.compile('(\[(.*?)\])') res = ptrn.search(name) if res: products = self.search( [('default_code', '=', res.group(2))] + args, limit=limit) # still no results, partner in context: search on supplier info as last hope to find something if not products and self._context.get('partner_id'): suppliers = self.env['product.supplierinfo'].search([ ('name', '=', self._context.get('partner_id')), '|', ('product_code', operator, name), ('product_name', operator, name) ]) if suppliers: products = self.search( [('product_tmpl_id.seller_ids', 'in', suppliers.ids)], limit=limit) else: products = self.search(args, limit=limit) return products.name_get() @api.model def view_header_get(self, view_id, view_type): res = super(ProductProduct, self).view_header_get(view_id, view_type) if self._context.get('categ_id'): return _('Products: ') + self.env['product.category'].browse( self._context['categ_id']).name return res @api.multi def open_product_template(self): """ Utility method used to add an "Open Template" button in product views """ self.ensure_one() return { 'type': 'ir.actions.act_window', 'res_model': 'product.template', 'view_mode': 'form', 'res_id': self.product_tmpl_id.id, 'target': 'new' } @api.multi def _select_seller(self, partner_id=False, quantity=0.0, date=None, uom_id=False): self.ensure_one() if date is None: date = fields.Date.today() res = self.env['product.supplierinfo'] for seller in self.seller_ids: # Set quantity in UoM of seller quantity_uom_seller = quantity if quantity_uom_seller and uom_id and uom_id != seller.product_uom: quantity_uom_seller = uom_id._compute_quantity( quantity_uom_seller, seller.product_uom) if seller.date_start and seller.date_start > date: continue if seller.date_end and seller.date_end < date: continue if partner_id and seller.name not in [ partner_id, partner_id.parent_id ]: continue if quantity_uom_seller < seller.min_qty: continue if seller.product_id and seller.product_id != self: continue res |= seller break return res @api.multi def price_compute(self, price_type, uom=False, currency=False, company=False): # TDE FIXME: delegate to template or not ? fields are reencoded here ... # compatibility about context keys used a bit everywhere in the code if not uom and self._context.get('uom'): uom = self.env['product.uom'].browse(self._context['uom']) if not currency and self._context.get('currency'): currency = self.env['res.currency'].browse( self._context['currency']) products = self if price_type == 'standard_price': # standard_price field can only be seen by users in base.group_user # Thus, in order to compute the sale price from the cost for users not in this group # We fetch the standard price as the superuser products = self.with_context( force_company=company and company.id or self._context.get( 'force_company', self.env.user.company_id.id)).sudo() prices = dict.fromkeys(self.ids, 0.0) for product in products: prices[product.id] = product[price_type] or 0.0 if price_type == 'list_price': prices[product.id] += product.price_extra if uom: prices[product.id] = product.uom_id._compute_price( prices[product.id], uom) # Convert from current user company currency to asked one # This is right cause a field cannot be in more than one currency if currency: prices[product.id] = product.currency_id.compute( prices[product.id], currency) return prices # compatibility to remove after v10 - DEPRECATED @api.multi def price_get(self, ptype='list_price'): return self.price_compute(ptype) @api.multi def _set_standard_price(self, value): ''' Store the standard price change in order to be able to retrieve the cost of a product for a given date''' PriceHistory = self.env['product.price.history'] for product in self: PriceHistory.create({ 'product_id': product.id, 'cost': value, 'company_id': self._context.get('force_company', self.env.user.company_id.id), }) @api.multi def get_history_price(self, company_id, date=None): history = self.env['product.price.history'].search( [('company_id', '=', company_id), ('product_id', 'in', self.ids), ('datetime', '<=', date or fields.Datetime.now())], order='datetime desc,id desc', limit=1) return history.cost or 0.0 def _need_procurement(self): # When sale/product is installed alone, there is no need to create procurements. Only # sale_stock and sale_service need procurements return False
class MuskathlonRegistration(models.Model): _name = 'muskathlon.registration' _inherit = ['website.published.mixin'] _description = 'Muskathlon registration' _rec_name = 'partner_preferred_name' _order = 'id desc' event_id = fields.Many2one( 'crm.event.compassion', 'Muskathlon event', required=True, domain="[('type', '=', 'sport')]" ) partner_id = fields.Many2one( 'res.partner', 'Muskathlon participant' ) lead_id = fields.Many2one( 'crm.lead', 'Lead' ) ambassador_details_id = fields.Many2one( 'ambassador.details', related='partner_id.ambassador_details_id') # The 4 following fields avoid giving read access to the public on the # res.partner participating in the muskathlon. partner_id_id = fields.Integer(related="partner_id.id", readonly=True) partner_display_name = fields.Char(related="partner_id.display_name", readonly=True) partner_preferred_name = fields.Char(related="partner_id.preferred_name", readonly=True) partner_name = fields.Char(related="partner_id.name", readonly=True) ambassador_picture_1 = fields.Binary( related='partner_id.image', readonly=True) ambassador_picture_2 = fields.Binary( related='ambassador_details_id.picture_large', readonly=True) ambassador_description = fields.Text( related='ambassador_details_id.description', readonly=True) ambassador_quote = fields.Text( related='ambassador_details_id.quote', readonly=True) partner_firstname = fields.Char( related='partner_id.firstname', readonly=True ) partner_lastname = fields.Char( related='partner_id.lastname', readonly=True ) partner_gender = fields.Selection(related='partner_id.title.gender', readonly=True) sport_discipline_id = fields.Many2one( 'sport.discipline', 'Sport discipline', required=True ) sport_level = fields.Selection([ ('beginner', 'Beginner'), ('average', 'Average'), ('advanced', 'Advanced') ]) sport_level_description = fields.Text('Describe your sport experience') amount_objective = fields.Integer('Raise objective', default=10000, required=True) amount_raised = fields.Integer(readonly=True, compute='_compute_amount_raised') amount_raised_percents = fields.Integer(readonly=True, compute='_compute_amount_raised') muskathlon_participant_id = fields.Char( related='partner_id.muskathlon_participant_id') muskathlon_event_id = fields.Char( related='event_id.muskathlon_event_id') website_published = fields.Boolean( compute='_compute_website_published', store=True) website_url = fields.Char( compute='_compute_website_url' ) reg_id = fields.Char(string='Muskathlon registration ID', size=128) host = fields.Char(compute='_compute_host', readonly=True) _sql_constraints = [ ('reg_unique', 'unique(event_id,partner_id)', 'Only one registration per participant/event is allowed!') ] @api.multi def _compute_website_url(self): for registration in self: registration.website_url = "/event/{}/{}".format( slug(registration.event_id), slug(registration) ) def _compute_amount_raised(self): muskathlon_report = self.env['muskathlon.report'] for registration in self: amount_raised = int(sum( item.amount for item in muskathlon_report.search([ ('user_id', '=', registration.partner_id.id) ]) )) registration.amount_raised = amount_raised registration.amount_raised_percents = int( amount_raised * 100 / (registration.amount_objective or 10000)) def _compute_host(self): host = config.get('wordpress_host') if not host: raise MissingError(_('Missing wordpress_host in odoo config file')) for registration in self: registration.host = host @api.multi @api.depends( 'partner_id', 'partner_id.image', 'ambassador_details_id', 'ambassador_details_id.quote', 'ambassador_details_id.description') def _compute_website_published(self): required_fields = [ 'partner_preferred_name', 'ambassador_quote', 'ambassador_description', 'ambassador_picture_1', ] for registration in self: published = True for field in required_fields: if not getattr(registration, field): published = False break registration.website_published = published def get_sport_discipline_name(self): return self.sport_discipline_id.get_label() @api.onchange('event_id') def onchange_event_id(self): return { 'domain': {'sport_discipline_id': [ ('id', 'in', self.event_id.sport_discipline_ids.ids)]} } @api.onchange('sport_discipline_id') def onchange_sport_discipline(self): if self.sport_discipline_id and self.sport_discipline_id not in \ self.event_id.sport_discipline_ids: self.sport_discipline_id = False return { 'warning': { 'title': _('Invalid sport'), 'message': _('This sport is not in muskathlon') } }
class ResCompany(models.Model): _inherit = "res.company" signature = fields.Binary('Signature') accreditation = fields.Text('Accreditation') approval_authority = fields.Text('Approval Authority')