class res_partner(models.Model): _inherit = 'res.partner' @api.model def name_search(self, name, args=None, operator='ilike', limit=100): """ name_search(name='', args=None, operator='ilike', limit=100) -> records Search for records that have a display name matching the given ``name`` pattern when compared with the given ``operator``, while also matching the optional search domain (``args``). This is used for example to provide suggestions based on a partial value for a relational field. Sometimes be seen as the inverse function of :meth:`~.name_get`, but it is not guaranteed to be. This method is equivalent to calling :meth:`~.search` with a search domain based on ``display_name`` and then :meth:`~.name_get` on the result of the search. Extends the original method!!! If eq_filter_prod_sup in the context is true, then only the suppliers which sell all of the products from the purchase order lines (eq_order_line in context) are returned. :param str name: the name pattern to match :param list args: optional search domain (see :meth:`~.search` for syntax), specifying further restrictions :param str operator: domain operator for matching ``name``, such as ``'like'`` or ``'='``. :param int limit: optional max number of records to return :rtype: list :return: list of pairs ``(id, text_repr)`` for all matching records. """ if self._context.get('eq_filter_prod_sup') and self._context.get('eq_order_line'): product_ids = [] purchase_line_obj = self.env['purchase.order.line'] for line_data in self._context.get('eq_order_line'): if line_data[0] == 6: product_ids.append(line_data[2]['product_id']) elif line_data[0] == 4: purchase_order_line = purchase_line_obj.browse(line_data[1]) product_ids.append(purchase_order_line.product_id.id) elif line_data[0] == 1: if 'product_id' in line_data[2]: product_ids.append(line_data[2]['product_id']) else: purchase_order_line = purchase_line_obj.browse(line_data[1]) product_ids.append(purchase_order_line.product_id.id) if product_ids: sql_query = """select product_tmpl_id, name from product_supplierinfo where product_tmpl_id in %s""" % (str(tuple(product_ids))) self._cr.execute(sql_query) supplierinfo = self._cr.fetchall() prod_sup_mapping = {} for product_id, sup_id in supplierinfo: if sup_id in prod_sup_mapping: prod_sup_mapping[sup_id].append(product_id) else: prod_sup_mapping[sup_id] = [product_id] suppliers = [] for supplier_id, prod_list in prod_sup_mapping.iteritems(): if set(product_ids) <= set(prod_list): suppliers.append(supplier_id) if args == None: args = [] args.append(['id', 'in', suppliers]) res = super(res_partner, self).name_search(name, args=args, operator=operator, limit=limit) return res eq_delivery_date_type_purchase = fields.Selection([('cw', 'Calendar week'), ('date', 'Date')], string="Delivery Date Purchase", help="If nothing is selected, the default from the settings will be used.") eq_delivery_date_type_sale = fields.Selection([('cw', 'Calendar week'), ('date', 'Date')], string="Delivery Date Sale", help="If nothing is selected, the default from the settings will be used.") eq_complete_description = fields.Char(compute='_generate_complete_description', store=True) eq_prospective_customer = fields.Boolean(string="Prospective user",required=False, default=False) eq_unlocked_for_webshop = fields.Boolean(string="Unlocked for webshop",required=False, default=False) @api.one @api.depends('name', 'eq_firstname') def _generate_complete_description(self): for record in self: if record.is_company is False: result = "" if record.name is not False: result = record.name if record.eq_firstname is not False: if len(result) > 0: result += ", " + record.eq_firstname else: result = record.eq_firstname record.eq_complete_description = result else: record.eq_complete_description = record.name
class ingram_config(models.Model): _name = "ingram_config" _description = "Configuration Management Produces Ingram" name = fields.Char(string="Name", help="Name associated with the configuration", required=True) xml_address = fields.Char(string='Server Xml address', help="server Xml address") xml_login = fields.Char(string='Login', help="Login for Xml request ") xml_passwd = fields.Char(string='Password', help="Password for Xml Request") xml_active = fields.Boolean(string='XMl Request', help="Active the Xml Request") server_address = fields.Char(string='Server address', help="server ip address", required=True) file_cat = fields.Char(string='Products Categories file name', default='PCAT_GENERIC.TXT', help="Name of the file for the products categories", required=True) file_prod = fields.Char( string='Products File name', default='Price2.txt', help= "Name of the file for the products. Must be based on this header: 'Ingram Part Number,Vendor Part Number,EANUPC Code,Plant,Vendor Number,Vendor Name,Weight,Category ID,Customer Price,Retail Price,Availability Flag,BOM Flag,Warranty Flag,Material Long Description,Material Creation Reason code,Material Language Code,Music Copyright Fees,Recycling Fees,Document Copyright Fees,Battery Fees,Availability (Local Stock),Availability (Central Stock),Creation Reason Type,Creation Reason Value,Local Stock Backlog Quantity,Local Stock Backlog ETA,Central Stock Backlog Quantity,Central Stock Backlog ETA'", required=True) server_login = fields.Char(string='Login', help="Login database") server_passwd = fields.Char(string='Password', help="Password database") date_synchro = fields.Datetime( string='Date of last manually synchronization', readonly=True) date_import = fields.Datetime(string='Date of last manually importation', readonly=True) date_cron = fields.Datetime(string='Date of last cronjob synchronization', readonly=True) chemin = fields.Char(string="Path", help="Path where the files is stored", required=True) mailto = fields.Char( string="Warning Mail", help= "Encode the adresses e-mail separated by ';'.\nThose e-mail will receive the warnings", required=True) pricelist = fields.Many2one( 'product.pricelist', string='Pricelist for the sales price', ) id_synchro = fields.Many2one( 'ir.cron', string='Cronjob', required=True, help= "Cronjob in OpenERP for automatic synchronization. To bind the Cronjob with the configuration, click the button" ) categorie_name = fields.Char(string='Category', help="Name of the product categorie") location_id = fields.Many2one('stock.location', string='Location', required=True, domain=[('usage', '=', 'internal')], help="Location of new product") country_id = fields.Many2one('res.country', string='Country', required=True, help=" Country of Ingram supplier") categorie_id = fields.Many2one( 'product.category', string='Category', required=True, change_default=True, domain=[('type', '=', 'normal')], help="Select category for the current product") supplier_id = fields.Many2one('res.partner', string='Supplier', required=True, domain=[('supplier', '=', True)], ondelete='cascade', help="Supplier of this product") taxes_ventes = fields.Many2many('account.tax', "ingram_taxe_sales", 'ingram_config', "taxe_id", string='Sales Taxes', domain=[('parent_id', '=', False), ('type_tax_use', 'in', ['sale', 'all'])]) taxes_achats = fields.Many2many('account.tax', "ingram_taxe_achat", 'ingram_config2', "taxe_id2", string='Purchases Taxes', domain=[('parent_id', '=', False), ('type_tax_use', 'in', ['purchase', 'all'])]) @api.onchange('supplier_id') def onchange_supplier_id(self): if self.supplier_id: self.country_id = self.supplier_id.country_id.id @api.one def check_ftp(self): ip = self.server_address login = self.server_login passwd = self.server_passwd try: ftp = ftplib.FTP() except: raise Warning(_('FTP was not started!')) return False ip = ip.split('/') txt = "" for i in range(len(ip)): if i > 0: txt += "/" + ip[i] try: ftp.connect(ip[0]) if login: ftp.login(login, passwd) else: ftp.login() except: raise Warning( _('Username/password FTP connection was not successfully!')) return False ftp.close() raise Warning(_('FTP connection was successfully!')) return True @api.model def create(self, vals): config = self.env['ingram_config'].search([]) if config: raise Warning(_('You can have only one configuration!')) res = super(ingram_config, self).create(vals) return res @api.one def sendTextMail(self, ids, title, mess): _from = 'Ingram Error <*****@*****.**>' to = ids.mailto to = to.replace(';', ',') txt = '' if mess: txt += "\r\n" txt += mess txt += "\r\n" mail_obj = self.env['mail.mail'] res = mail_obj.create({ 'subject': title, 'email_from': '*****@*****.**', 'email_to': to, 'body_html': txt }) @api.multi def write(self, values): idtaxevente = self.taxes_ventes idtaxeAchat = self.taxes_achats super(ingram_config, self).write(values) prod_tmpl = self.env['product.template'] prod_prod = self.env['product.product'] if ('taxes_ventes' in values): tab = [] tab2 = [] tab3 = [] for i in idtaxevente: tab.append(i.id) for j in values['taxes_ventes'][0][2]: if not (j in tab): tab2.append(j) for i in idtaxevente: if not (i.id in values['taxes_ventes'][0][2]): tab3.append(i.id) if tab2: idProd = prod_tmpl.search([('ingram', '=', True)]) for j in idProd: empl = prod_prod.search([('product_tmpl_id', '=', j.id)]) for k in values['taxes_ventes'][0][2]: empl.taxes_id = [(4, k)] if tab3: idProd = prod_tmpl.search([('ingram', '=', True)]) for j in idProd: empl = prod_prod.search([('product_tmpl_id', '=', j.id)]) for i in tab3: empl.taxes_id = [(3, i)] if ('taxes_achats' in values): tab = [] tab2 = [] tab3 = [] for i in idtaxeAchat: tab.append(i.id) for j in values['taxes_achats'][0][2]: if not (j in tab): tab2.append(j) for i in idtaxeAchat: if not (i.id in values['taxes_achats'][0][2]): tab3.append(i.id) if (tab2): idProd = prod_tmpl.search([('ingram', '=', True)]) for j in idProd: empl = prod_prod.search([('product_tmpl_id', '=', j.id)]) for i in values['taxes_achats'][0][2]: empl.supplier_taxes_id = [(4, i)] if tab3: idProd = prod_tmpl.search([('ingram', '=', True)]) for j in idProd: empl = prod_prod.search([('product_tmpl_id', '=', j.id)]) for i in tab3: empl.taxes_id = [(3, i)] @api.model def cron_function(self): id_config = self.search([('xml_active', '=', True)]) if not id_config: _logger.info('No config!') return False _logger.info(_('Download started')) result = id_config.import_data(id_config) if result[0] == True: _logger.info(_('Download ended')) else: _logger.info(_('Download error')) return False _logger.info(_('Synchronization started')) result2 = id_config.synchro_categ(id_config) if result2[0] == True: _logger.info(_('products categories synchronization ended')) else: _logger.info(_('products categories synchronization error')) return False _logger.info(_('Products synchronization started')) result3 = id_config.synchronisation(id_config) _logger.info(result3) if result3[0] == True: _logger.info(_('Products synchronization ended')) else: _logger.info(_('Products synchronization error')) return False _logger.info(_('Clean product started')) result4 = id_config.clean_data() if result4[0] == True: _logger.info(_('Clean product ended')) else: _logger.info(_('Clean product error')) return False id_config.clean_categ() _logger.info(_('end synchronization')) try: id_config.date_cron = time.strftime("%Y-%m-%d %H:%M:%S") except: try: id_config.date_cron = time.strftime("%Y-%m-%d %H:%M:%S") except: pass _logger.info('Done') return True @api.one def button_import_data(self): _logger.info(_('Download started')) config_id = self result = self.import_data(config_id) if result[0] == True: _logger.info(_('Download ended')) try: self.date_import = time.strftime("%Y-%m-%d %H:%M:%S") except: try: self.date_import = time.strftime("%Y-%m-%d %H:%M:%S") except: pass else: _logger.info(_('Download error')) return False return True @api.one def import_data(self, config_id): config = config_id ip = config.server_address login = config.server_login passwd = config.server_passwd chm = str(config.chemin) try: ftp = ftplib.FTP() except: _logger.error(_('connexion error')) self.sendTextMail( config_id, "Connecion error", "An error occured during the connection to the server.\n\nDetails: \n\t %s" % (sys.exc_info()[0])) return False ip = ip.split('/') txt = "" for i in range(len(ip)): if i > 0: txt += "/" + ip[i] try: ftp.connect(ip[0]) if login: ftp.login(login, passwd) else: ftp.login() ftp.retrlines('LIST') ftp.cwd(txt) ftp.retrlines('LIST') self.download(config_id, '.', chm, ftp) ftp.close() return True except: _logger.error(_('Download error')) self.sendTextMail( config_id, "Import error", "An error occured during the importation.\n\nDetails: \n\t %s" % (sys.exc_info()[0])) return False @api.one def download(self, config_id, pathsrc, pathdst, ftp): idss = config_id try: lenpathsrc = len(pathsrc) l = ftp.nlst(pathsrc) for i in l: tailleinit = ftp.size(i) if ((str(i) == str(idss.file_cat)) or str(i) == str(idss.file_prod)): try: ftp.size(i) ftp.retrbinary('RETR ' + i, open(pathdst + os.sep + i, 'wb').write) except: try: os.makedirs(pathdst + os.sep + os.path.dirname(i[lenpathsrc:])) except: pass return False if os.path.isfile(pathdst + '/' + i): taille = os.path.getsize(pathdst + '/' + i) if (tailleinit != taille): os.remove(pathdst + '/' + i) return True except: return False @api.one def clean_data(self): _logger.info("clean_data") try: product_product = self.env['product.product'] sale_order_line = self.env['sale.order.line'] purchase_order_line = self.env['purchase.order.line'] procurement_order = self.env['procurement.order'] stock_move = self.env['stock.move'] account_invoice_line = self.env['account.invoice.line'] aujourdhui = datetime.today() semaine = timedelta(weeks=1) date = aujourdhui - semaine idProd = product_product.search([ '|', ('active', '=', True), ('active', '=', False), ('product_tmpl_id.ingram', '=', True), ('product_tmpl_id.last_synchro_ingram', '<', date) ], order='id') delete = 0 undelete = 0 use = 0 for i in idProd: ids1 = sale_order_line.search([('product_id', '=', i.id)]) ids2 = purchase_order_line.search([('product_id', '=', i.id)]) ids3 = procurement_order.search([('product_id', '=', i.id)]) ids4 = stock_move.search([('product_id', '=', i.id)]) ids5 = account_invoice_line.search([('product_id', '=', i.id)]) if not ids1 and not ids2 and not ids3 and not ids4 and not ids5: try: i.unlink() delete += 1 except: _logger.info('Delete impossible') undelete += 1 else: i.active = False use += 1 _logger.info('Products deleted : %s' % (delete)) _logger.info('Products non deleted : %s' % (use)) _logger.info('product cleaned') return True except: _logger.error("Erreur Clean_data") self.sendTextMail( self, "Error products cleaning", "An error occured during the cleaning.\n\nDetails: \n\t %s" % (sys.exc_info()[0])) return False @api.one def delete_data(self): _logger.info("delete_data") try: product_product = self.env['product.product'] sale_order_line = self.env['sale.order.line'] purchase_order_line = self.env['purchase.order.line'] procurement_order = self.env['procurement.order'] stock_move = self.env['stock.move'] account_invoice_line = self.env['account.invoice.line'] idProd = product_product.search([ '|', ('active', '=', True), ('active', '=', False), ('product_tmpl_id.ingram', '=', True) ], order='id') delete = 0 undelete = 0 use = 0 cpt = 0 for i in idProd: cpt += 1 ids1 = sale_order_line.search([('product_id', '=', i.id)]) ids2 = purchase_order_line.search([('product_id', '=', i.id)]) ids3 = procurement_order.search([('product_id', '=', i.id)]) ids4 = stock_move.search([('product_id', '=', i.id)]) ids5 = account_invoice_line.search([('product_id', '=', i.id)]) if not ids1 and not ids2 and not ids3 and not ids4 and not ids5: try: i.unlink() delete += 1 except: _logger.info('Delete impossible') undelete += 1 else: i.active = False use += 1 _logger.info('Products deleted : %s' % (delete)) _logger.info('Products non deleted : %s' % (use)) _logger.info('product cleaned') return True except: _logger.error("Erreur delete_data") self.sendTextMail( self, "Error products deleting", "An error occured during the cleaning.\n\nDetails: \n\t %s" % (sys.exc_info()[0])) return False @api.one def synchro_data(self): config_id = self _logger.info(_('Products categories synchronization started')) result = self.synchro_categ(config_id) if result[0] == True: _logger.info(_('products categories synchronization ended')) else: _logger.info(('products categories synchronization error')) return False _logger.info(_('products synchronization started')) result3 = self.synchronisation(config_id) _logger.info(result3) if result3[0] == True: _logger.info(_('products synchronization ended')) else: _logger.info(_('products synchronization error')) return False self.clean_categ() try: self.date_synchro = time.strftime("%Y-%m-%d %H:%M:%S") except: try: self.date_synchro = time.strftime("%Y-%m-%d %H:%M:%S") except: pass return True @api.one def clean_categ(self): _logger.info("Clean_categ") product_categ = self.env['product.category'] product_tmpl = self.env['product.template'] tab = [] idss_cat = product_categ.search([('code_categ_ingram', '=', '-1')]) for i in idss_cat: id_child = product_categ.search([('parent_id', '=', i.id)]) for j in id_child: if j in idss_cat and j.id not in tab: id_child2 = product_categ.search([('parent_id', '=', j.id) ]) for z in id_child2: if z in idss_cat and z.id not in tab: tab.append(z.id) tab.append(j.id) tab.append(i.id) if 1 == 1: for j in tab: id_prod = product_tmpl.search([('categ_id', '=', j)]) id_cat = product_categ.search([('id', 'in', tab), ('parent_id', '=', j)]) if not id_prod and not id_cat: try: k = product_categ.browse(j) k.unlink() except: pass _logger.info("End Clean Categ") return True @api.one def synchronisation(self, config_id): categ = config_id.categorie_id supplier = config_id.supplier_id chm = str(config_id.chemin) file_prod = config_id.file_prod listefich = os.listdir(chm + '/') product_product = self.env['product.product'] product_categ = self.env['product.category'] product_tmpl = self.env['product.template'] pricelist_supplier = self.env['pricelist.partnerinfo'] product_supplier = self.env['product.supplierinfo'] product_routes = self.env['stock.location.route'] taxes_a = [] taxes_v = [] for a in config_id.taxes_achats: taxes_a.append(a.id) for a in config_id.taxes_ventes: taxes_v.append(a.id) try: compteur = 0 for i in listefich: if str(i) == str(file_prod): fichier = open(chm + i, 'rb') fichiercsv = csv.reader(fichier, delimiter=',', quotechar='|') for ligne in fichiercsv: if ligne[0] != "Ingram Part Number": i = 0 nom = ligne[13] name = '' lgt = len(nom) while (i < lgt): try: nom[i].decode('latin-1') name += nom[i] i += 1 except: i += 1 nom = name[0:127] desc = name print "n:", compteur _logger.info(compteur) empl = product_product.search([('default_code', '=', ligne[0])]) if empl: idprod = empl.product_tmpl_id categ_ingram = product_categ.search([ ('code_categ_ingram', '=', ligne[7]) ]) if not categ_ingram: categ_ingram = categ elif len(categ_ingram) > 1: categ_ingram = categ_ingram[0] if ligne[8] == 'X' or not ligne[8]: ligne[8] = '0.0' idprod.name = nom idprod.standard_price = float(ligne[8]) idprod.weight_net = float(ligne[6]) idprod.description = desc idprod.valuation = 'real_time' idprod.description_sale = desc idprod.categ_id = categ_ingram.id idprod.last_synchro_ingram = time.strftime( "%Y-%m-%d") suppinfo_id = idprod.seller_ids exist_line = False exist = False for b in suppinfo_id: if b.name.id == supplier.id: exist = b.id if not b.product_name or not b.product_code: b.product_name = nom b.product_code = ligne[0] for c in b.pricelist_ids: exist_line = True if c.name == 'INGRAM' and c.min_quantity == 1: c.price = float(ligne[8]) if exist and exist_line == False: pricelist_supplier.create({ 'min_quantity': '1', 'price': float(ligne[8]), 'suppinfo_id': exist, 'name': 'INGRAM' }) if (len(ligne[2]) == 12): ligne[2] = "0" + ligne[2] if len(ligne[2]) == 13: empl.name_template = nom empl.active = True empl.ean13 = ligne[2] empl.vpn = ligne[1] empl.manufacturer = ligne[5] else: empl.name_template = nom empl.active = True empl.vpn = ligne[1] empl.manufacturer = ligne[5] else: categ_ingram = product_categ.search([ ('code_categ_ingram', '=', ligne[7]) ]) if not categ_ingram: categ_ingram = categ elif len(categ_ingram) > 1: categ_ingram = categ_ingram[0] if ligne[8] == 'X' or not ligne[8]: ligne[8] = '0.0' mto_ids = product_routes.search([ '|', ('name', '=', 'Make To Order'), ('name', '=', 'Buy') ]) route = [] for d in mto_ids: route.append(d.id) tmpl_id = product_tmpl.create({ 'valuation': 'real_time', 'name': nom, 'standard_price': float(ligne[8]), 'weight_net': float(ligne[6]), 'description': desc, 'description_sale': desc, 'categ_id': categ_ingram.id, 'route_ids': [(6, 0, route)], 'ingram': True, 'type': 'product', 'last_synchro_ingram': time.strftime("%Y-%m-%d") }) id_prod = product_product.search([ ('product_tmpl_id', '=', tmpl_id.id) ]) if (len(ligne[2]) == 12): ligne[2] = "0" + ligne[2] if len(ligne[2]) == 13: id_prod.default_code = ligne[0] id_prod.name_template = nom id_prod.taxes_id = [(6, 0, taxes_v)] id_prod.supplier_taxes_id = [(6, 0, taxes_a)] id_prod.price_extra = 0.00 id_prod.active = True id_prod.product_tmpl_id = tmpl_id.id id_prod.ean13 = ligne[2] id_prod.vpn = ligne[1] id_prod.manufacturer = ligne[5] else: id_prod.default_code = ligne[0] id_prod.name_template = nom id_prod.taxes_id = [(6, 0, taxes_v)] id_prod.supplier_taxes_id = [(6, 0, taxes_a)] id_prod.price_extra = 0.00 id_prod.active = True id_prod.product_tmpl_id = tmpl_id.id id_prod.vpn = ligne[1] id_prod.manufacturer = ligne[5] supp_info = product_supplier.create({ 'name': supplier.id, 'min_qty': 0, 'product_tmpl_id': tmpl_id.id, 'product_name': nom, 'product_code': ligne[0] }) pricelist_supplier.create({ 'min_quantity': '1', 'price': float(ligne[8]), 'suppinfo_id': supp_info.id, 'name': 'INGRAM' }) compteur += 1 fichier.close() return True except: _logger.error("Erreur Synchro_produit") self.sendTextMail( config_id, "Error Synchronization", "An error occured during the synchronization.\n \nDetails: \n\t %s" % (sys.exc_info()[0])) return False @api.one def synchro_categ(self, config_id): categ = config_id.categorie_id file_cat = config_id.file_cat chm = str(config_id.chemin) listefich = os.listdir(chm + '/') product_categ = self.env['product.category'] compteur = 0 for i in listefich: if str(i) == str(file_cat): fichier = open(chm + '/' + i, 'rb') fichiercsv = csv.reader(fichier, delimiter=';') cat = [] for ligne in fichiercsv: ligne_un = product_categ.search([('code_categ_ingram', '=', ligne[1]), ('name', '=', ligne[2])]) if ligne_un: ligne_trois = product_categ.search([ ('code_categ_ingram', '=', ligne[3]), ('name', '=', ligne[4]), ('parent_id', '=', ligne_un.id) ]) else: ligne_trois = product_categ.search([ ('code_categ_ingram', '=', ligne[3]), ('name', '=', ligne[4]) ]) if ligne_trois: ligne_cinq = product_categ.search([ ('code_categ_ingram', '=', ligne[5]), ('name', '=', ligne[6]), ('parent_id', '=', ligne_trois.id) ]) else: ligne_cinq = product_categ.search([ ('code_categ_ingram', '=', ligne[5]), ('name', '=', ligne[6]) ]) _logger.info(compteur) if not ligne_un: ligne_un = product_categ.create({ 'name': ligne[2], 'parent_id': categ.id, 'code_categ_ingram': ligne[1], 'type': 'view' }) if ligne_un not in cat: cat.append(ligne_un) else: ligne_un.code_categ_ingram = ligne[1] if ligne_un not in cat: cat.append(ligne_un) if not ligne_trois: ligne_trois = product_categ.create({ 'name': ligne[4], 'parent_id': ligne_un.id, 'code_categ_ingram': ligne[3], 'type': 'view' }) if ligne_trois not in cat: cat.append(ligne_trois) else: if ligne_trois.parent_id != ligne_un: ligne_trois.parent_id = ligne_un.id ligne_trois.code_categ_ingram = ligne[3] else: ligne_trois.code_categ_ingram = ligne[3] if ligne_trois not in cat: cat.append(ligne_trois) if not ligne_cinq: ligne_cinq = product_categ.create({ 'name': ligne[6], 'parent_id': ligne_trois.id, 'code_categ_ingram': ligne[5] }) if ligne_cinq not in cat: cat.append(ligne_cinq) else: if ligne_cinq.parent_id != ligne_trois: ligne_cinq.parent_id = ligne_trois.id ligne_cinq.code_categ_ingram = ligne[5] else: ligne_cinq.code_categ_ingram = ligne[5] if ligne_cinq not in cat: cat.append(ligne_cinq[0]) compteur += 1 fichier.close() idss = product_categ.search([('code_categ_ingram', '!=', False) ]) tab = [] for i in idss: if i not in cat: tab.append(i) i.code_categ_ingram = '-1' return True
class EventTrackPresenceReport(models.Model): _name = "event.track.presence.report" _description = "Event track presence report" _auto = False employee_id = fields.Many2one(comodel_name='hr.employee', string='Employee', readonly=True) customer_id = fields.Many2one(comodel_name='res.partner', string='Customer', readonly=True) city = fields.Char(string='City', readonly=True) street = fields.Char(string='Street', readonly=True) event_id = fields.Many2one(comodel_name='event.event', string='Event', readonly=True) start_hour = fields.Char(string='Start hour', readonly=True) end_hour = fields.Char(string='End hour', readonly=True) session_duration = fields.Float(string='Session duration', readonly=True) days = fields.Char(string='Days', readonly=True) session_name = fields.Char(string='Job', readonly=True) def _select(self): select_str = """ select p.employee_id as employee_id, p.customer_id as customer_id, COALESCE(r.street2,r.street) as street, r.city as city, p.event as event_id, p.start_hour as start_hour, p.end_hour as end_hour, (p.session_duration * (select count(distinct(p2.session_day)) from event_track_presence p2 where p2.event = p.event and p2.partner = p.partner and p2.start_hour = p.start_hour and p2.end_hour = p.end_hour)) as session_duration, array_to_string(array_agg(distinct(replace(replace(replace( replace(replace(replace(replace(p.session_day,'0','L'),'1','M') ,'2','X'),'3','J'),'4','V'),'5','S'),'6','D'))),',') as days, COALESCE(e.notes, (select t.name from event_track t where t.event_id = p.event and t.id = (select min(t2.id) from event_track t2 where t2.event_id = p.event))) as session_name, min(p.id) as id """ return select_str def _from(self): from_str = """ from event_track_presence p inner join res_partner r on r.id = p.customer_id inner join event_event e on e.id = p.event """ return from_str def _where(self): where_str = """ where p.state != 'canceled' and p.analytic_account_state not in ('canceled','close') and p.customer_id is not null and p.employee_id is not null """ return where_str def _where2(self): res = "{} and p.employee_id = {}".format( self._where(), self.env.context.get('employee_id')) res = "{} and p.session_date_without_hour >= '{}'".format( res, self.env.context.get('from_date')) res = "{} and p.session_date_without_hour <= '{}'".format( res, self.env.context.get('to_date')) return res def _group_by(self): group_by_str = """ group by 1, 2, 3, 4, 5, 6, 7, 8, 10 """ return group_by_str def _order_by(self): order_by_str = """ order by 1, 2, 5, 9 """ return order_by_str def init(self, cr): tools.drop_view_if_exists(cr, self._table) cr.execute("""CREATE or REPLACE VIEW %s as (%s %s %s %s %s) """ % (self._table, self._select(), self._from(), self._where(), self._group_by(), self._order_by())) @api.multi def presence_analysis_from_employee(self): tools.drop_view_if_exists(self.env.cr, self._table) self.env.cr.execute("""CREATE or REPLACE VIEW %s as (%s %s %s %s %s) """ % (self._table, self._select(), self._from(), self._where2(), self._group_by(), self._order_by()))
class AccountJournal(models.Model): _name = "account.journal" _description = "Journal" _order = 'sequence, type, code' def _default_inbound_payment_methods(self): return self.env.ref('account.account_payment_method_manual_in') def _default_outbound_payment_methods(self): return self.env.ref('account.account_payment_method_manual_out') name = fields.Char(string='Journal Name', required=True) code = fields.Char( string='Short Code', size=5, required=True, help= "The journal entries of this journal will be named using this prefix.") type = fields.Selection([ ('sale', 'Sale'), ('purchase', 'Purchase'), ('cash', 'Cash'), ('bank', 'Bank'), ('general', 'Miscellaneous'), ], required=True, help="Select 'Sale' for customer invoices journals."\ " Select 'Purchase' for vendor bills journals."\ " Select 'Cash' or 'Bank' for journals that are used in customer or vendor payments."\ " Select 'General' for miscellaneous operations journals."\ " Select 'Opening/Closing Situation' for entries generated for new fiscal years.") type_control_ids = fields.Many2many('account.account.type', 'account_journal_type_rel', 'journal_id', 'type_id', string='Account Types Allowed') account_control_ids = fields.Many2many('account.account', 'account_account_type_rel', 'journal_id', 'account_id', string='Accounts Allowed', domain=[('deprecated', '=', False)]) default_credit_account_id = fields.Many2one( 'account.account', string='Default Credit Account', domain=[('deprecated', '=', False)], help="It acts as a default account for credit amount") default_debit_account_id = fields.Many2one( 'account.account', string='Default Debit Account', domain=[('deprecated', '=', False)], help="It acts as a default account for debit amount") update_posted = fields.Boolean( string='Allow Cancelling Entries', help= "Check this box if you want to allow the cancellation the entries related to this journal or of the invoice related to this journal" ) group_invoice_lines = fields.Boolean( string='Group Invoice Lines', help= "If this box is checked, the system will try to group the accounting lines when generating them from invoices." ) sequence_id = fields.Many2one( 'ir.sequence', string='Entry Sequence', help= "This field contains the information related to the numbering of the journal entries of this journal.", required=True, copy=False) refund_sequence_id = fields.Many2one( 'ir.sequence', string='Refund Entry Sequence', help= "This field contains the information related to the numbering of the refund entries of this journal.", copy=False) sequence = fields.Integer( help='Used to order Journals in the dashboard view') #groups_id = fields.Many2many('res.groups', 'account_journal_group_rel', 'journal_id', 'group_id', string='Groups') currency_id = fields.Many2one('res.currency', help='The currency used to enter statement', string="Currency", oldname='currency') company_id = fields.Many2one('res.company', string='Company', required=True, index=True, default=lambda self: self.env.user.company_id, help="Company related to this journal") refund_sequence = fields.Boolean( string='Dedicated Refund Sequence', help= "Check this box if you don't want to share the same sequence for invoices and refunds made from this journal", default=False) inbound_payment_method_ids = fields.Many2many( 'account.payment.method', 'account_journal_inbound_payment_method_rel', 'journal_id', 'inbound_payment_method', domain=[('payment_type', '=', 'inbound')], string='Debit Methods', default=lambda self: self._default_inbound_payment_methods(), help= "Means of payment for collecting money. Odoo modules offer various payments handling facilities, " "but you can always use the 'Manual' payment method in order to manage payments outside of the software." ) outbound_payment_method_ids = fields.Many2many( 'account.payment.method', 'account_journal_outbound_payment_method_rel', 'journal_id', 'outbound_payment_method', domain=[('payment_type', '=', 'outbound')], string='Payment Methods', default=lambda self: self._default_outbound_payment_methods(), help= "Means of payment for sending money. Odoo modules offer various payments handling facilities, " "but you can always use the 'Manual' payment method in order to manage payments outside of the software." ) at_least_one_inbound = fields.Boolean(compute='_methods_compute', store=True) at_least_one_outbound = fields.Boolean(compute='_methods_compute', store=True) profit_account_id = fields.Many2one( 'account.account', string='Profit Account', domain=[('deprecated', '=', False)], help= "Used to register a profit when the ending balance of a cash register differs from what the system computes" ) loss_account_id = fields.Many2one( 'account.account', string='Loss Account', domain=[('deprecated', '=', False)], help= "Used to register a loss when the ending balance of a cash register differs from what the system computes" ) # Bank journals fields bank_account_id = fields.Many2one('res.partner.bank', string="Bank Account", ondelete='restrict', copy=False) display_on_footer = fields.Boolean( "Show in Invoices Footer", help= "Display this bank account on the footer of printed documents like invoices and sales orders." ) bank_statements_source = fields.Selection([('manual', 'Record Manually')], string='Bank Feeds') bank_acc_number = fields.Char(related='bank_account_id.acc_number') bank_id = fields.Many2one('res.bank', related='bank_account_id.bank_id') _sql_constraints = [ ('code_company_uniq', 'unique (code, name, company_id)', 'The code and name of the journal must be unique per company !'), ] @api.one @api.constrains('currency_id', 'default_credit_account_id', 'default_debit_account_id') def _check_currency(self): if self.currency_id: if self.default_credit_account_id and not self.default_credit_account_id.currency_id.id == self.currency_id.id: raise UserError( _('Configuration error!\nThe currency of the journal should be the same than the default credit account.' )) if self.default_debit_account_id and not self.default_debit_account_id.currency_id.id == self.currency_id.id: raise UserError( _('Configuration error!\nThe currency of the journal should be the same than the default debit account.' )) @api.one @api.constrains('type', 'bank_account_id') def _check_bank_account(self): if self.type == 'bank' and self.bank_account_id: if self.bank_account_id.company_id != self.company_id: raise ValidationError( _('The bank account of a bank journal must belong to the same company (%s).' ) % self.company_id.name) # A bank account can belong to a customer/supplier, in which case their partner_id is the customer/supplier. # Or they are part of a bank journal and their partner_id must be the company's partner_id. if self.bank_account_id.partner_id != self.company_id.partner_id: raise ValidationError( _('The holder of a journal\'s bank account must be the company (%s).' ) % self.company_id.name) @api.onchange('default_debit_account_id') def onchange_debit_account_id(self): if not self.default_credit_account_id: self.default_credit_account_id = self.default_debit_account_id @api.onchange('default_credit_account_id') def onchange_credit_account_id(self): if not self.default_debit_account_id: self.default_debit_account_id = self.default_credit_account_id @api.multi def unlink(self): bank_accounts = self.env['res.partner.bank'].browse() for bank_account in self.mapped('bank_account_id'): accounts = self.search([('bank_account_id', '=', bank_account.id)]) if accounts <= self: bank_accounts += bank_account ret = super(AccountJournal, self).unlink() bank_accounts.unlink() return ret @api.one def copy(self, default=None): default = dict(default or {}) default.update(code=_("%s (copy)") % (self.code or ''), name=_("%s (copy)") % (self.name or '')) return super(AccountJournal, self).copy(default) @api.multi def write(self, vals): for journal in self: if ('company_id' in vals and journal.company_id.id != vals['company_id']): if self.env['account.move'].search( [('journal_id', 'in', self.ids)], limit=1): raise UserError( _('This journal already contains items, therefore you cannot modify its company.' )) if ('code' in vals and journal.code != vals['code']): if self.env['account.move'].search( [('journal_id', 'in', self.ids)], limit=1): raise UserError( _('This journal already contains items, therefore you cannot modify its short name.' )) new_prefix = self._get_sequence_prefix(vals['code'], refund=False) journal.sequence_id.write({'prefix': new_prefix}) if journal.refund_sequence_id: new_prefix = self._get_sequence_prefix(vals['code'], refund=True) journal.refund_sequence_id.write({'prefix': new_prefix}) if 'currency_id' in vals: if not 'default_debit_account_id' in vals and self.default_debit_account_id: self.default_debit_account_id.currency_id = vals[ 'currency_id'] if not 'default_credit_account_id' in vals and self.default_credit_account_id: self.default_credit_account_id.currency_id = vals[ 'currency_id'] if 'bank_acc_number' in vals and not vals.get( 'bank_acc_number') and journal.bank_account_id: raise UserError( _('You cannot empty the account number once set.\nIf you would like to delete the account number, you can do it from the Bank Accounts list.' )) result = super(AccountJournal, self).write(vals) # Create the bank_account_id if necessary if 'bank_acc_number' in vals: for journal in self.filtered( lambda r: r.type == 'bank' and not r.bank_account_id): journal.set_bank_account(vals.get('bank_acc_number'), vals.get('bank_id')) return result @api.model def _get_sequence_prefix(self, code, refund=False): prefix = code.upper() if refund: prefix = 'R' + prefix return prefix + '/%(range_year)s/' @api.model def _create_sequence(self, vals, refund=False): """ Create new no_gap entry sequence for every new Journal""" prefix = self._get_sequence_prefix(vals['code'], refund) seq = { 'name': vals['name'], 'implementation': 'no_gap', 'prefix': prefix, 'padding': 4, 'number_increment': 1, 'use_date_range': True, } if 'company_id' in vals: seq['company_id'] = vals['company_id'] return self.env['ir.sequence'].create(seq) @api.model def _prepare_liquidity_account(self, name, company, currency_id, type): ''' This function prepares the value to use for the creation of the default debit and credit accounts of a liquidity journal (created through the wizard of generating COA from templates for example). :param name: name of the bank account :param company: company for which the wizard is running :param currency_id: ID of the currency in wich is the bank account :param type: either 'cash' or 'bank' :return: mapping of field names and values :rtype: dict ''' # Seek the next available number for the account code code_digits = company.accounts_code_digits or 0 if type == 'bank': account_code_prefix = company.bank_account_code_prefix or '' else: account_code_prefix = company.cash_account_code_prefix or company.bank_account_code_prefix or '' for num in xrange(1, 100): new_code = str(account_code_prefix.ljust(code_digits - 1, '0')) + str(num) rec = self.env['account.account'].search( [('code', '=', new_code), ('company_id', '=', company.id)], limit=1) if not rec: break else: raise UserError(_('Cannot generate an unused account code.')) liquidity_type = self.env.ref('account.data_account_type_liquidity') return { 'name': name, 'currency_id': currency_id or False, 'code': new_code, 'user_type_id': liquidity_type and liquidity_type.id or False, 'company_id': company.id, } @api.model def create(self, vals): company_id = vals.get('company_id', self.env.user.company_id.id) if vals.get('type') in ('bank', 'cash'): # For convenience, the name can be inferred from account number if not vals.get('name') and 'bank_acc_number' in vals: vals['name'] = vals['bank_acc_number'] # If no code provided, loop to find next available journal code if not vals.get('code'): for num in xrange(1, 100): # journal_code has a maximal size of 5, hence we can enforce the boundary num < 100 journal_code = (vals['type'] == 'cash' and 'CSH' or 'BNK') + str(num) journal = self.env['account.journal'].search( [('code', '=', journal_code), ('company_id', '=', company_id)], limit=1) if not journal: vals['code'] = journal_code break else: raise UserError( _("Cannot generate an unused journal code. Please fill the 'Shortcode' field." )) # Create a default debit/credit account if not given default_account = vals.get('default_debit_account_id') or vals.get( 'default_credit_account_id') if not default_account: company = self.env['res.company'].browse(company_id) account_vals = self._prepare_liquidity_account( vals.get('name'), company, vals.get('currency_id'), vals.get('type')) default_account = self.env['account.account'].create( account_vals) vals['default_debit_account_id'] = default_account.id vals['default_credit_account_id'] = default_account.id # We just need to create the relevant sequences according to the chosen options if not vals.get('sequence_id'): vals.update({'sequence_id': self.sudo()._create_sequence(vals).id}) if vals.get('type') in ('sale', 'purchase') and vals.get( 'refund_sequence') and not vals.get('refund_sequence_id'): vals.update({ 'refund_sequence_id': self.sudo()._create_sequence(vals, refund=True).id }) journal = super(AccountJournal, self).create(vals) # Create the bank_account_id if necessary if journal.type == 'bank' and not journal.bank_account_id and vals.get( 'bank_acc_number'): journal.set_bank_account(vals.get('bank_acc_number'), vals.get('bank_id')) return journal def set_bank_account(self, acc_number, bank_id=None): """ Create a res.partner.bank and set it as value of the field bank_account_id """ self.ensure_one() self.bank_account_id = self.env['res.partner.bank'].create({ 'acc_number': acc_number, 'bank_id': bank_id, 'company_id': self.company_id.id, 'currency_id': self.currency_id.id, 'partner_id': self.company_id.partner_id.id, }).id @api.multi @api.depends('name', 'currency_id', 'company_id', 'company_id.currency_id') def name_get(self): res = [] for journal in self: currency = journal.currency_id or journal.company_id.currency_id name = "%s (%s)" % (journal.name, currency.name) res += [(journal.id, name)] return res @api.multi @api.depends('inbound_payment_method_ids', 'outbound_payment_method_ids') def _methods_compute(self): for journal in self: journal.at_least_one_inbound = bool( len(journal.inbound_payment_method_ids)) journal.at_least_one_outbound = bool( len(journal.outbound_payment_method_ids))
class AccountTaxGroup(models.Model): _name = 'account.tax.group' _order = 'sequence asc' name = fields.Char(required=True, translate=True) sequence = fields.Integer(default=10)
class case_type(models.Model): _name = 'case.type' name = fields.Char(string='Name',required=True)
class TicketGroup(models.Model): _name = "ticket.group" name = fields.Char('Name')
class ProductProduct(models.Model): _inherit = ['product.product', 'product.configurator'] _name = "product.product" # This is needed as the AbstractModel removes the delegated related field name = fields.Char(related="product_tmpl_id.name") @api.multi def _get_product_attributes_values_dict(self): # Retrieve first the attributes from template to preserve order res = self.product_tmpl_id._get_product_attributes_dict() for val in res: value = self.attribute_value_ids.filtered( lambda x: x.attribute_id.id == val['attribute_id']) val['value_id'] = value.id return res @api.multi def _get_product_attributes_values_text(self): description = self.attribute_value_ids.mapped( lambda x: "%s: %s" % (x.attribute_id.name, x.name)) return "%s\n%s" % (self.product_tmpl_id.name, "\n".join(description)) @api.model def _build_attributes_domain(self, product_template, product_attributes): domain = [] cont = 0 if product_template: domain.append(('product_tmpl_id', '=', product_template.id)) for attr_line in product_attributes: if isinstance(attr_line, dict): value_id = attr_line.get('value_id') else: value_id = attr_line.value_id.id if value_id: domain.append(('attribute_value_ids', '=', value_id)) cont += 1 return domain, cont @api.model def _product_find(self, product_template, product_attributes): if product_template: domain, cont = self._build_attributes_domain( product_template, product_attributes) products = self.search(domain) # Filter the product with the exact number of attributes values for product in products: if len(product.attribute_value_ids) == cont: return product return False @api.constrains('product_tmpl_id', 'attribute_value_ids') def _check_duplicity(self): for product in self: domain = [('product_tmpl_id', '=', product.product_tmpl_id.id)] for value in product.attribute_value_ids: domain.append(('attribute_value_ids', '=', value.id)) other_products = self.search(domain) # Filter the product with the exact number of attributes values cont = len(product.attribute_value_ids) for other_product in other_products: if (len(other_product.attribute_value_ids) == cont and other_product != product): raise exceptions.ValidationError( _("There's another product with the same attributes.")) @api.constrains('product_tmpl_id', 'attribute_value_ids') def _check_configuration_validity(self): """This method checks that the current selection values are correct according rules. As default, the validity means that all the attributes values are set. This can be overridden to set another rules. :raises: exceptions.ValidationError: If the check is not valid. """ for product in self: if bool(product.product_tmpl_id.attribute_line_ids.mapped( 'attribute_id') - product.attribute_line_ids.mapped('attribute_id')): raise exceptions.ValidationError( _("You have to fill all the attributes values.")) @api.model def create(self, vals): if (not vals.get('attribute_value_ids') and vals.get('product_attribute_ids')): vals['attribute_value_ids'] = ( (4, x[2]['value_id']) for x in vals.pop('product_attribute_ids') if x[2].get('value_id')) obj = self.with_context(product_name=vals.get('name', '')) return super(ProductProduct, obj).create(vals)
class IfrsLines(models.Model): _name = 'ifrs.lines' _order = 'ifrs_id, sequence' def _get_ifrs_query(self, cr, uid, brw, context=None): """ Fetches a semi-query to be provided as context into aml""" context = dict(context or {}) query = '' if not brw.filter_id: return query args = eval(brw.filter_id.domain) query = self.pool['account.move.line']._where_calc( cr, uid, args, context=context) where_clause, where_clause_params = query.get_sql()[1:] where_clause = where_clause.replace('account_move_line', 'l') query = cr.mogrify(where_clause, where_clause_params) return query def _get_sum_total( self, cr, uid, brw, operand, number_month=None, one_per=False, bag=None, context=None): """ Calculates the sum of the line total_ids & operand_ids the current ifrs.line @param number_month: period to compute """ context = context and dict(context) or {} res = 0 # If the report is two or twelve columns, will choose the field needed # to make the sum if context.get('whole_fy', False) or one_per: field_name = 'ytd' else: field_name = 'period_%s' % str(number_month) # It takes the sum of the total_ids & operand_ids for ttt in getattr(brw, operand): res += bag[ttt.id].get(field_name, 0.0) return res def _get_sum_detail(self, cr, uid, ids=None, number_month=None, context=None): """ Calculates the amount sum of the line type == 'detail' @param number_month: periodo a calcular """ fy_obj = self.pool.get('account.fiscalyear') period_obj = self.pool.get('account.period') context = context and dict(context) or {} cx = context.copy() res = 0.0 if not cx.get('fiscalyear'): cx['fiscalyear'] = fy_obj.find(cr, uid) fy_id = cx['fiscalyear'] brw = self.browse(cr, uid, ids) # custom date_domain = [] if brw.acc_val == 'init': if cx.get('whole_fy', False): cx['periods'] = period_obj.search(cr, uid, [ ('fiscalyear_id', '=', fy_id), ('special', '=', True)]) # custom date_domain += [('date_maturity', '<', fy_obj.browse(cr, uid, fy_id).date_start)] else: period_from = period_obj.search(cr, uid, [ ('fiscalyear_id', '=', fy_id), ('special', '=', True)]) # Case when the period_from is the first non-special period # of the fiscalyear if period_obj.browse(cr, uid, cx['period_from']).date_start == \ fy_obj.browse(cr, uid, fy_id).date_start: cx['period_to'] = period_from[0] else: cx['period_to'] = period_obj.previous( cr, uid, cx['period_from']) cx['period_from'] = period_from[0] # custom date_domain += [('date_maturity', '<', period_obj.browse(cr, uid, cx['period_from']).date_start)] elif brw.acc_val == 'var': # it is going to be the one sent by the previous cx if cx.get('whole_fy', False): cx['periods'] = period_obj.search(cr, uid, [ ('fiscalyear_id', '=', fy_id), ('special', '=', False)]) # custom date_domain += [('date_maturity', '>=', fy_obj.browse(cr, uid, fy_id).date_start)] date_domain += [('date_maturity', '<=', fy_obj.browse(cr, uid, fy_id).date_stop)] else: date_domain += [('date_maturity', '>=', period_obj.browse(cr, uid, cx['period_from']).date_start)] date_domain += [('date_maturity', '<=', period_obj.browse(cr, uid, cx['period_to']).date_stop)] else: # it is going to be from the fiscalyear's beginning if cx.get('whole_fy', False): cx['periods'] = period_obj.search(cr, uid, [ ('fiscalyear_id', '=', fy_id)]) # custom date_domain += [('date_maturity', '<=', fy_obj.browse(cr, uid, fy_id).date_stop)] else: period_from = period_obj.search(cr, uid, [ ('fiscalyear_id', '=', fy_id), ('special', '=', True)]) cx['period_from'] = period_from[0] cx['periods'] = \ period_obj.build_ctx_periods(cr, uid, cx['period_from'], cx['period_to']) # custom date_domain += [('date_maturity', '<=', fy_obj.browse(cr, uid, fy_id).date_stop)] if brw.type == 'detail': # Si es de tipo detail # If we have to only take into account a set of Journals cx['journal_ids'] = [aj_brw.id for aj_brw in brw.journal_ids] cx['analytic'] = [an.id for an in brw.analytic_ids] cx['ifrs_tax'] = [tx.id for tx in brw.tax_code_ids] cx['ifrs_partner'] = [p_brw.id for p_brw in brw.partner_ids] cx['ifrs_query'] = self._get_ifrs_query(cr, uid, brw, context) # NOTE: This feature is not yet been implemented # cx['partner_detail'] = cx.get('partner_detail') # Refreshing record with new context brw = self.browse(cr, uid, ids, context=cx) for aa in brw.cons_ids: # Se hace la sumatoria de la columna balance, credito o debito. # Dependiendo de lo que se escoja en el wizard if brw.value == 'debit': res += aa.debit elif brw.value == 'credit': res += aa.credit else: res += aa.balance # custom if brw.ifrs_id.arap_account_ids and aa.id in brw.ifrs_id.arap_account_ids._get_children_and_consol(): domain = [('account_id', 'in', aa._get_children_and_consol()), ('date_maturity', '!=', False)] + date_domain aml_ids = self.pool.get('account.move.line').search(cr, uid, domain) if brw.value in ('debit', 'balance'): res += sum([aml.amount_residual if aml.amount_residual > 0 else 0 for aml in self.pool.get('account.move.line').browse(cr, uid, aml_ids)]) else: res += sum([-aml.amount_residual if aml.amount_residual < 0 else 0 for aml in self.pool.get('account.move.line').browse(cr, uid, aml_ids)]) if brw.ifrs_id.forecast_ids: domain = [('account_id', 'in', aa._get_children_and_consol()), ('forecast_id', 'in', [f.id for f in brw.ifrs_id.forecast_ids])] + date_domain afl_ids = self.pool.get('account.forecast.line').search(cr, uid, domain) if brw.value in ('debit', 'balance'): res += sum([afl.amount if afl.amount > 0 else 0 for afl in self.pool.get('account.forecast.line').browse(cr, uid, afl_ids)]) else: res += sum([-afl.amount if afl.amount < 0 else 0 for afl in self.pool.get('account.forecast.line').browse(cr, uid, afl_ids)]) return res def _get_logical_operation(self, cr, uid, brw, ilf, irg, context=None): def result(brw, ifn, ilf, irg): if getattr(brw, ifn) == 'subtract': res = ilf - irg elif getattr(brw, ifn) == 'addition': res = ilf + irg elif getattr(brw, ifn) == 'lf': res = ilf elif getattr(brw, ifn) == 'rg': res = irg elif getattr(brw, ifn) == 'zr': res = 0.0 return res context = dict(context or {}) fnc = getattr(op, brw.logical_operation) if fnc(ilf, irg): res = result(brw, 'logical_true', ilf, irg) else: res = result(brw, 'logical_false', ilf, irg) return res def _get_grand_total( self, cr, uid, ids, number_month=None, one_per=False, bag=None, context=None): """ Calculates the amount sum of the line type == 'total' @param number_month: periodo a calcular """ fy_obj = self.pool.get('account.fiscalyear') context = context and dict(context) or {} cx = context.copy() res = 0.0 if not cx.get('fiscalyear'): cx['fiscalyear'] = fy_obj.find(cr, uid) brw = self.browse(cr, uid, ids) res = self._get_sum_total( cr, uid, brw, 'total_ids', number_month, one_per=one_per, bag=bag, context=cx) if brw.operator in ('subtract', 'condition', 'percent', 'ratio', 'product'): so = self._get_sum_total( cr, uid, brw, 'operand_ids', number_month, one_per=one_per, bag=bag, context=cx) if brw.operator == 'subtract': res -= so elif brw.operator == 'condition': res = self._get_logical_operation(cr, uid, brw, res, so, context=cx) elif brw.operator == 'percent': res = so != 0 and (100 * res / so) or 0.0 elif brw.operator == 'ratio': res = so != 0 and (res / so) or 0.0 elif brw.operator == 'product': res = res * so return res def _get_constant(self, cr, uid, ids=None, number_month=None, context=None): """ Calculates the amount sum of the line of constant @param number_month: periodo a calcular """ cx = context or {} brw = self.browse(cr, uid, ids, context=cx) if brw.constant_type == 'constant': return brw.constant fy_obj = self.pool.get('account.fiscalyear') period_obj = self.pool.get('account.period') if not cx.get('fiscalyear'): cx['fiscalyear'] = fy_obj.find(cr, uid, dt=None, context=cx) if not cx.get('period_from', False) and not cx.get('period_to', False): if context.get('whole_fy', False): cx['period_from'] = period_obj.find_special_period( cr, uid, cx['fiscalyear']) cx['period_to'] = period_obj.search( cr, uid, [('fiscalyear_id', '=', cx['fiscalyear'])])[-1] if brw.constant_type == 'period_days': res = period_obj._get_period_days( cr, uid, cx['period_from'], cx['period_to']) elif brw.constant_type == 'fy_periods': res = fy_obj._get_fy_periods(cr, uid, cx['fiscalyear']) elif brw.constant_type == 'fy_month': res = fy_obj._get_fy_month(cr, uid, cx[ 'fiscalyear'], cx['period_to']) elif brw.constant_type == 'number_customer': res = self._get_number_customer_portfolio(cr, uid, ids, cx[ 'fiscalyear'], cx['period_to'], cx) return res def exchange(self, cr, uid, ids, from_amount, to_currency_id, from_currency_id, exchange_date, context=None): context = context and dict(context) or {} if from_currency_id == to_currency_id: return from_amount curr_obj = self.pool.get('res.currency') context['date'] = exchange_date return curr_obj.compute(cr, uid, from_currency_id, to_currency_id, from_amount, context=context) def _get_amount_value( self, cr, uid, ids, ifrs_line=None, period_info=None, fiscalyear=None, exchange_date=None, currency_wizard=None, number_month=None, target_move=None, pdx=None, undefined=None, two=None, one_per=False, bag=None, context=None): """ Returns the amount corresponding to the period of fiscal year @param ifrs_line: linea a calcular monto @param period_info: informacion de los periodos del fiscal year @param fiscalyear: selected fiscal year @param exchange_date: date of change currency @param currency_wizard: currency in the report @param number_month: period number @param target_move: target move to consider """ context = context and dict(context) or {} # TODO: Current Company's Currency shall be used: the one on wizard from_currency_id = ifrs_line.ifrs_id.company_id.currency_id.id to_currency_id = currency_wizard if number_month: if two: context = { 'period_from': number_month, 'period_to': number_month} else: period_id = period_info[number_month][1] context = {'period_from': period_id, 'period_to': period_id} else: context = {'whole_fy': True} # NOTE: This feature is not yet been implemented # context['partner_detail'] = pdx context['fiscalyear'] = fiscalyear context['state'] = target_move if ifrs_line.type == 'detail': res = self._get_sum_detail( cr, uid, ifrs_line.id, number_month, context=context) elif ifrs_line.type == 'total': res = self._get_grand_total( cr, uid, ifrs_line.id, number_month, one_per=one_per, bag=bag, context=context) elif ifrs_line.type == 'constant': res = self._get_constant(cr, uid, ifrs_line.id, number_month, context=context) else: res = 0.0 if ifrs_line.type == 'detail': res = self.exchange( cr, uid, ids, res, to_currency_id, from_currency_id, exchange_date, context=context) return res def _get_dict_amount_with_operands( self, cr, uid, ids, ifrs_line, period_info=None, fiscalyear=None, exchange_date=None, currency_wizard=None, month_number=None, target_move=None, pdx=None, undefined=None, two=None, one_per=False, bag=None, context=None): """ Integrate operand_ids field in the calculation of the amounts for each line @param ifrs_line: linea a calcular monto @param period_info: informacion de los periodos del fiscal year @param fiscalyear: selected fiscal year @param exchange_date: date of change currency @param currency_wizard: currency in the report @param month_number: period number @param target_move: target move to consider """ context = dict(context or {}) direction = ifrs_line.inv_sign and -1.0 or 1.0 res = {} for number_month in range(1, 13): field_name = 'period_%(month)s' % dict(month=number_month) bag[ifrs_line.id][field_name] = self._get_amount_value( cr, uid, ids, ifrs_line, period_info, fiscalyear, exchange_date, currency_wizard, number_month, target_move, pdx, undefined, two, one_per=one_per, bag=bag, context=context) * direction res[number_month] = bag[ifrs_line.id][field_name] return res def _get_amount_with_operands( self, cr, uid, ids, ifrs_l, period_info=None, fiscalyear=None, exchange_date=None, currency_wizard=None, number_month=None, target_move=None, pdx=None, undefined=None, two=None, one_per=False, bag=None, context=None): """ Integrate operand_ids field in the calculation of the amounts for each line @param ifrs_line: linea a calcular monto @param period_info: informacion de los periodos del fiscal year @param fiscalyear: selected fiscal year @param exchange_date: date of change currency @param currency_wizard: currency in the report @param number_month: period number @param target_move: target move to consider """ context = context and dict(context) or {} if not number_month: context = {'whole_fy': True} res = self._get_amount_value( cr, uid, ids, ifrs_l, period_info, fiscalyear, exchange_date, currency_wizard, number_month, target_move, pdx, undefined, two, one_per=one_per, bag=bag, context=context) res = ifrs_l.inv_sign and (-1.0 * res) or res bag[ifrs_l.id]['ytd'] = res return res def _get_number_customer_portfolio(self, cr, uid, ids, fyr, period, context=None): ifrs_brw = self.browse(cr, uid, ids, context=context) company_id = ifrs_brw.ifrs_id.company_id.id if context.get('whole_fy', False): period_fy = [('period_id.fiscalyear_id', '=', fyr), ('period_id.special', '=', False)] else: period_fy = [('period_id', '=', period)] invoice_obj = self.pool.get('account.invoice') invoice_ids = invoice_obj.search(cr, uid, [ ('type', '=', 'out_invoice'), ('state', 'in', ('open', 'paid',)), ('company_id', '=', company_id)] + period_fy) partner_number = \ set([inv.partner_id.id for inv in invoice_obj.browse(cr, uid, invoice_ids, context=context)]) return len(list(partner_number)) def onchange_sequence(self, cr, uid, ids, sequence, context=None): context = context and dict(context) or {} return {'value': {'priority': sequence}} @api.returns('self') def _get_default_help_bool(self): ctx = dict(self._context) return ctx.get('ifrs_help', True) @api.returns('self') def _get_default_sequence(self): ctx = dict(self._context) res = 0 if not ctx.get('ifrs_id'): return res + 10 ifrs_lines_ids = self.search([('ifrs_id', '=', ctx['ifrs_id'])]) if ifrs_lines_ids: res = max(line.sequence for line in ifrs_lines_ids) return res + 10 def onchange_type_without(self, cr, uid, ids, ttype, operator, context=None): context = context and dict(context) or {} res = {} if ttype == 'total' and operator == 'without': res = {'value': {'operand_ids': []}} return res def write(self, cr, uid, ids, vals, context=None): ids = isinstance(ids, (int, long)) and [ids] or ids res = super(IfrsLines, self).write(cr, uid, ids, vals) for ifrs_line in self.pool.get('ifrs.lines').browse(cr, uid, ids): if ifrs_line.type == 'total' and ifrs_line.operator == 'without': vals['operand_ids'] = [(6, 0, [])] super(IfrsLines, self).write(cr, uid, ifrs_line.id, vals) return res help = fields.Boolean( string='Show Help', copy=False, related='ifrs_id.help', default=_get_default_help_bool, help='Allows you to show the help in the form') # Really!!! A repeated field with same functionality! This was done due # to the fact that web view everytime that sees sequence tries to allow # you to change the values and this feature here is undesirable. sequence = fields.Integer( string='Sequence', default=_get_default_sequence, help=('Indicates the order of the line in the report. The sequence ' 'must be unique and unrepeatable')) priority = fields.Integer( string='Sequence', default=_get_default_sequence, related='sequence', help=('Indicates the order of the line in the report. The sequence ' 'must be unique and unrepeatable')) name = fields.Char( string='Name', size=128, required=True, translate=True, help=('Line name in the report. This name can be translatable, if ' 'there are multiple languages loaded it can be translated')) type = fields.Selection( [('abstract', 'Abstract'), ('detail', 'Detail'), ('constant', 'Constant'), ('total', 'Total')], string='Type', required=True, default='abstract', help=('Line type of report: \n-Abstract(A),\n-Detail(D), ' '\n-Constant(C),\n-Total(T)')) constant = fields.Float( string='Constant', help=('Fill this field with your own constant that will be used ' 'to compute in your other lines'), readonly=False) constant_type = fields.Selection( [('constant', 'My Own Constant'), ('period_days', 'Days of Period'), ('fy_periods', "FY's Periods"), ('fy_month', "FY's Month"), ('number_customer', "Number of customers* in portfolio")], string='Constant Type', required=False, help='Constant Type') ifrs_id = fields.Many2one( 'ifrs.ifrs', string='IFRS', ondelete='cascade', required=True) company_id = fields.Many2one( 'res.company', string='Company', related='ifrs_id.company_id', store=True) amount = fields.Float( string='Amount', readonly=True, help=('This field will update when you click the compute button in ' 'the IFRS doc form')) cons_ids = fields.Many2many( 'account.account', 'ifrs_account_rel', 'ifrs_lines_id', 'account_id', string='Consolidated Accounts') journal_ids = fields.Many2many( 'account.journal', 'ifrs_journal_rel', 'ifrs_lines_id', 'journal_id', string='Journals', required=False) analytic_ids = fields.Many2many( 'account.analytic.account', 'ifrs_analytic_rel', 'ifrs_lines_id', 'analytic_id', string='Consolidated Analytic Accounts') partner_ids = fields.Many2many( 'res.partner', 'ifrs_partner_rel', 'ifrs_lines_id', 'partner_id', string='Partners') tax_code_ids = fields.Many2many( 'account.tax.code', 'ifrs_tax_rel', 'ifrs_lines_id', 'tax_code_id', string='Tax Codes') filter_id = fields.Many2one( 'ir.filters', string='Custom Filter', ondelete='set null', domain=("[('model_id','=','account.move.line')]")) parent_id = fields.Many2one( 'ifrs.lines', string='Parent', ondelete='set null', domain=("[('ifrs_id','=',parent.id)," "('type','=','total'),('id','!=',id)]")) operand_ids = fields.Many2many( 'ifrs.lines', 'ifrs_operand_rel', 'ifrs_parent_id', 'ifrs_child_id', string='Second Operand') operator = fields.Selection( [('subtract', 'Subtraction'), ('condition', 'Conditional'), ('percent', 'Percentage'), ('ratio', 'Ratio'), ('product', 'Product'), ('without', 'First Operand Only')], string='Operator', required=False, default='without', help='Leaving blank will not take into account Operands') logical_operation = fields.Selection( LOGICAL_OPERATIONS, string='Logical Operations', required=False, help=('Select type of Logical Operation to perform with First ' '(Left) and Second (Right) Operand')) logical_true = fields.Selection( LOGICAL_RESULT, string='Logical True', required=False, help=('Value to return in case Comparison is True')) logical_false = fields.Selection( LOGICAL_RESULT, string='Logical False', required=False, help=('Value to return in case Comparison is False')) comparison = fields.Selection( [('subtract', 'Subtraction'), ('percent', 'Percentage'), ('ratio', 'Ratio'), ('without', 'No Comparison')], string='Make Comparison', required=False, default='without', help=('Make a Comparison against the previous period.\nThat is, ' 'period X(n) minus period X(n-1)\nLeaving blank will not ' 'make any effects')) acc_val = fields.Selection( [('init', 'Initial Values'), ('var', 'Variation in Periods'), ('fy', ('Ending Values'))], string='Accounting Span', required=False, default='fy', help='Leaving blank means YTD') value = fields.Selection( [('debit', 'Debit'), ('credit', 'Credit'), ('balance', 'Balance')], string='Accounting Value', required=False, default='balance', help='Leaving blank means Balance') total_ids = fields.Many2many( 'ifrs.lines', 'ifrs_lines_rel', 'parent_id', 'child_id', string='First Operand') inv_sign = fields.Boolean( string='Change Sign to Amount', default=False, copy=True, help='Allows you to show the help in the form') invisible = fields.Boolean( string='Invisible', default=False, copy=True, help='Allows whether the line of the report is printed or not') comment = fields.Text( string='Comments/Question', help='Comments or questions about this ifrs line') # _sql_constraints = [ # ('sequence_ifrs_id_unique', 'unique(ifrs_id)', # 'The sequence already have been set in another IFRS line')] def _get_level(self, cr, uid, lll, tree, level=1, context=None): """ Calcula los niveles de los ifrs.lines, tomando en cuenta que sera un mismo arbol para los campos total_ids y operand_ids. @param lll: objeto a un ifrs.lines @param level: Nivel actual de la recursion @param tree: Arbol de dependencias entre lineas construyendose """ context = context and dict(context) or {} if not tree.get(level): tree[level] = {} # The search through level should be backwards from the deepest level # to the outmost level levels = tree.keys() levels.sort() levels.reverse() xlevel = False for nnn in levels: xlevel = isinstance(tree[nnn].get(lll.id), (set)) and nnn or xlevel if not xlevel: tree[level][lll.id] = set() elif xlevel < level: tree[level][lll.id] = tree[xlevel][lll.id] del tree[xlevel][lll.id] else: # xlevel >= level return True for jjj in set(lll.total_ids + lll.operand_ids): tree[level][lll.id].add(jjj.id) self._get_level(cr, uid, jjj, tree, level + 1, context=context) return True
class ResPartnerCategory(osv.osv): _name = "res.partner.category" name = fields.Char("Name", size=50, required=True)
class SaleOrder(models.Model): _inherit = 'sale.order' notify_approval = fields.Char( string=_(u'Notify approval'), size=100, ) date_delivery = fields.Date( string=_(u'Date delivery'), default=fields.Date.today, ) date_reception = fields.Date(string=_(u'Date reception'), #default=fields.Date.today, ) total_net_sale = fields.Float(string=_(u'Total net sale'), digits_compute=dp.get_precision('Account'), compute='_compute_profit_margin', store=True) perc_freight = fields.Float( string=_(u'Freight percentage'), digits_compute=dp.get_precision('Account'), ) total_freight = fields.Float(string=_(u'Total Freight'), digits_compute=dp.get_precision('Account'), compute='_compute_profit_margin', store=True) perc_installation = fields.Float( string=_(u'installation percentage'), digits_compute=dp.get_precision('Account'), ) total_installation = fields.Float( string=_(u'Total installation'), digits_compute=dp.get_precision('Account'), compute='_compute_profit_margin', store=True) profit_margin = fields.Float(string=_(u'Profit margin'), digits_compute=dp.get_precision('Account'), compute='_compute_profit_margin', store=True) not_be_billed = fields.Boolean(string=_(u'not be billed'), ) no_facturar = fields.Boolean(string=_(u'No Facturar'), ) manufacture = fields.Selection( [('special', _(u'Special')), ('line', _(u'Line')), ('replenishment', _(u'Replenishment')), ('semi_special', _(u'Semi special'))], string=_(u"Manufacture"), ) executive = fields.Char( string=_(u'Executive'), size=100, ) respo_reple = fields.Char( string=_(u'Responsible of replenishment'), size=200, ) priority = fields.Selection( [('high', _(u'High')), ('replenishment', _(u'Replenishment')), ('express', _(u'Express')), ('sample', _(u'Sample'))], _(u'Manufacturing priority'), ) complement_saleorder_id = fields.Many2one( 'sale.order', string=_(u'In complement:'), help=_(u'Displays a list of sales orders'), ) manufacturing_observations = fields.Text( string=_(u'Observations Manufacturing'), ) replenishing_motif = fields.Text( string=_(u'Reason for the replenishment'), ) credit_status = fields.Selection( [('normal', _(u'Normal')), ('suspended', _(u'Suspended for Collection')), ('conditioned', _(u'Conditioned'))], _(u'Credit status'), ) credit_note = fields.Text(string=_(u'Note Credit and Collections'), ) date_production = fields.Date(string=_('Date of Production Termination'), ) approve = fields.Selection( [('approved', _('Approved')), ('suggested', _('Suggested for Approval')), ('not_approved', _('Not Approved'))], default='not_approved', string=_('Approve Status'), store=True, copy=False, ) total_cost = fields.Float(string=_('Total cost'), compute='_compute_profit_margin', store=True) sale_picking_adm = fields.Boolean(string=_(u'Admin Sale Picking'), ) webiste_operator = fields.Boolean(string=_('Captured by Operator'), ) date_suggested = fields.Datetime(string=_('Suggestion Date Approval'), copy=False, help=_('Suggestion Date Approval.')) date_approved = fields.Datetime(string=_('Credit Release Date'), copy=False, help=_('Credit Release Date.')) _sql_constraints = [ ('name_unique', 'UNIQUE(name)', "The order name must be unique"), ] @api.onchange('partner_id') def _onchange_partner_id(self): self.webiste_operator = False self.notify_approval = False if self.partner_id: self.webiste_operator = True if self.partner_id.notify_approval: self.notify_approval = self.partner_id.notify_approval @api.depends('order_line.net_sale') def _compute_profit_margin(self): for order in self: global_cost = 0.0 global_net_sale = 0.0 global_freight = 0.0 global_installa = 0.0 global_profit_margin = 0.0 currency = order.company_id.currency_id for line in order.order_line: global_cost += line.standard_cost global_net_sale += line.net_sale global_freight += line.freight_amount global_installa += line.installation_amount if global_net_sale > 0.000000: global_total_pm = currency.compute( global_cost, order.pricelist_id.currency_id) global_profit_margin = (1 - (global_total_pm) / global_net_sale) global_profit_margin = global_profit_margin * 100 order.total_cost = global_cost order.total_net_sale = global_net_sale order.total_freight = global_freight order.total_installation = global_installa order.profit_margin = global_profit_margin @api.multi @api.onchange('project_id') def onchange_project_id(self): """ Trigger the change of warehouse when the analytic account is modified. """ if self.project_id and self.project_id.warehouse_id: self.warehouse_id = self.project_id.warehouse_id return {} @api.multi def action_confirm(self): for order in self: if order.company_id.is_manufacturer: order.validate_manufacturing() if not order.notify_approval: raise UserError( _('The following field is not invalid:\nNotify approval' )) if not order.manufacture: raise UserError( _('The following field is not invalid:\nManufacture')) # if not order.executive: # raise UserError( # _('The following field is not invalid:\nExecutive')) if not order.priority: raise UserError( _('The following field is not invalid:\nManufacturing \ priority')) if not order.project_id: raise UserError( _('The following field is not invalid:\nAnalytic Account' )) if not order.client_order_ref: raise UserError(_('This Sale Order not has OC captured')) if not order.date_reception: raise UserError( _('This Sale Order not has Date Reception')) for line in order.order_line: if not line.route_id: raise UserError( _('Product line %s does not have a route assigned' % (line.product_id.default_code))) if line.standard_cost == 0.00: raise UserError( "No se puede validar un producto con costo 0 (%s)" % (line.product_id.default_code)) # Comented toda vez que ya hay un modulo de # PLM que considera productos cotizacion: # for line in order.order_line: # if line.product_id.quotation_product: # raise UserError(_('The Product contains Quotation')) if order.warehouse_id != order.project_id.warehouse_id: raise UserError( ('No coincide la Analítica con el almacen seleccionado')) return super(SaleOrder, self).action_confirm() @api.multi def validate_manufacturing(self): for order in self: # pending = self.env['sale.order'].search( # [('state', '=', 'draft')]) # dife = 0.0 # dife = order.amount_total - order.total_nste # if order.total_nste > 0.0000000: # if abs(dife) > 0.6000: # raise UserError( # _('The amount are differents:\nAnalytic Account')) for line in order.order_line: if line.product_id: routes = line.product_id.route_ids + \ line.product_id.categ_id.total_route_ids if line.product_id.type == 'service': continue if len(routes) < 2: raise UserError( _('%s %s %s' % (_("The next product has no a valid Route"), line.product_id.default_code, line.product_id.name))) product_bom = False for bom in line.product_id.product_tmpl_id.bom_ids: if bom.product_id.id == line.product_id.id: product_bom = bom or False if not product_bom: raise UserError( _('%s %s %s' % (_("The next product has no a Bill of Materials" ), line.product_id.default_code, line.product_id.name))) # if not line.product_id.product_service_id: # raise UserError( # _('%s %s %s' % ( # _("The next product has not a SAT Code: "), # line.product_id.default_code, line.product_id.name))) return True @api.multi def approve_action(self): for order in self: if order.approve == 'approved': raise UserError(_('This Sale Order is already approved')) if order.create_uid.id == self.env.uid: if order.manufacture != 'replenishment' or \ order.priority != 'replenishment': raise UserError( _('Este no es un Pedido de Reposición solicita \ la aprobación de Credito y Cobranza.')) else: order.write({'approve': 'approved'}) order.date_approved = fields.Datetime.now() return if not self.env.user.has_group('account.group_account_manager'): raise ValidationError( _('Este no es un Pedido de Reposición solicita \ la aprobación de Credito y Cobranza.')) order.write({'approve': 'approved'}) order.date_approved = fields.Datetime.now() # resws = super(SaleOrder, self)._product_data_validation() return True @api.multi def suggested_action(self): for order in self: if order.approve == 'suggested': raise UserError( _('This Sale Order is already Suggested for Approval')) if not order.order_line: raise UserError(_('This Sale Order not has Products Captured')) if not order.client_order_ref: raise UserError(_('This Sale Order not has OC captured')) if order.partner_id.parent_id: order.partner_id = order.partner_id.parent_id order.write({'approve': 'suggested'}) order.date_suggested = fields.Datetime.now() if order.company_id.is_manufacturer: # resws = order._product_data_validation() resws2 = order.product_data_validation2() # if resws[0] != 'OK': # raise ValidationError('Este pedido no podra ser aprobado \ # debido a errores de configuracion \ # en los productos que ocasionarian \ # excepciones, se ha enviado un correo detallado a los \ # interesados.') return True @api.multi def _prepare_invoice(self): invoice_vals = super(SaleOrder, self)._prepare_invoice() invoice_vals['perc_freight'] = self.perc_freight invoice_vals['perc_installation'] = self.perc_installation # invoice_vals['executive'] = self.executive invoice_vals['journal_id'] = self.project_id.journal_sale_id.id invoice_vals['manufacture'] = self.manufacture return invoice_vals @api.multi def action_done(self): super(SaleOrder, self).action_done() # commented temporary til implementatio of CRM self.force_quotation_send() @api.multi def force_quotation_send(self): for order in self: email_act = order.action_quotation_send() if email_act and email_act.get('context'): email_ctx = email_act['context'] notify = order.notify_approval email_ctx.update(default_email_to=notify) email_ctx.update(default_email_from=order.company_id.email) order.with_context(email_ctx).message_post_with_template( email_ctx.get('default_template_id')) return True
class AccountInvoice(models.Model): _inherit = "account.invoice" main_id_number = fields.Char( related='commercial_partner_id.main_id_number', readonly=True, ) state_id = fields.Many2one( related='commercial_partner_id.state_id', store=True, readonly=True, ) currency_rate = fields.Float( string='Currency Rate', copy=False, digits=(16, 4), # TODO make it editable, we have to change move create method readonly=True, ) document_letter_id = fields.Many2one( related='document_type_id.document_letter_id', readonly=True, ) document_letter_name = fields.Char( related='document_letter_id.name', readonly=True, ) taxes_included = fields.Boolean( related='document_letter_id.taxes_included', readonly=True, ) afip_responsability_type_id = fields.Many2one( 'afip.responsability.type', string='AFIP Responsability Type', readonly=True, copy=False, ) invoice_number = fields.Integer( compute='_get_invoice_number', string="Invoice Number", ) point_of_sale_number = fields.Integer( compute='_get_invoice_number', string="Point Of Sale", ) # impuestos e importes de impuestos # todos los impuestos tipo iva (es un concepto mas bien interno) vat_tax_ids = fields.One2many( compute="_get_argentina_amounts", comodel_name='account.invoice.tax', string='VAT Taxes' ) # todos los impuestos iva que componene base imponible (no se incluyen 0, # 1, 2 que no son impuesto en si) vat_taxable_ids = fields.One2many( compute="_get_argentina_amounts", comodel_name='account.invoice.tax', string='VAT Taxes' ) # todos los impuestos menos los tipo iva vat_tax_ids not_vat_tax_ids = fields.One2many( compute="_get_argentina_amounts", comodel_name='account.invoice.tax', string='Not VAT Taxes' ) # suma de base para todos los impuestos tipo iva vat_base_amount = fields.Monetary( compute="_get_argentina_amounts", string='VAT Base Amount' ) # base imponible (no se incluyen 0, exento y no gravado) vat_taxable_amount = fields.Monetary( compute="_get_argentina_amounts", string='VAT Taxable Amount' ) # base iva exento vat_exempt_base_amount = fields.Monetary( compute="_get_argentina_amounts", string='VAT Exempt Base Amount' ) # base iva no gravado vat_untaxed_base_amount = fields.Monetary( compute="_get_argentina_amounts", string='VAT Untaxed Base Amount' ) # importe de iva vat_amount = fields.Monetary( compute="_get_argentina_amounts", string='VAT Amount' ) # importe de otros impuestos other_taxes_amount = fields.Monetary( compute="_get_argentina_amounts", string='Other Taxes Amount' ) afip_incoterm_id = fields.Many2one( 'afip.incoterm', 'Incoterm', readonly=True, states={'draft': [('readonly', False)]} ) point_of_sale_type = fields.Selection( related='journal_id.point_of_sale_type', readonly=True, ) # estos campos los agregamos en este modulo pero en realidad los usa FE # pero entendemos que podrian ser necesarios para otros tipos, por ahora # solo lo vamos a hacer requerido si el punto de venta es del tipo # electronico # TODO mejorar, este concepto deberia quedar fijo y no poder modificarse # una vez validada, cosa que pasaria por ej si cambias el producto afip_concept = fields.Selection( compute='_get_concept', # store=True, selection=[('1', 'Producto / Exportación definitiva de bienes'), ('2', 'Servicios'), ('3', 'Productos y Servicios'), ('4', '4-Otros (exportación)'), ], string="AFIP concept", ) afip_service_start = fields.Date( string='Service Start Date', readonly=True, states={'draft': [('readonly', False)]}, ) afip_service_end = fields.Date( string='Service End Date', readonly=True, states={'draft': [('readonly', False)]}, ) @api.one def _get_argentina_amounts(self): """ """ vat_taxes = self.tax_line_ids.filtered( lambda r: ( r.tax_id.tax_group_id.type == 'tax' and r.tax_id.tax_group_id.tax == 'vat')) # we add and "r.base" because only if a there is a base amount it is # considered taxable, this is used for eg to validate invoices on afip vat_taxables = vat_taxes.filtered( lambda r: ( r.tax_id.tax_group_id.afip_code not in [0, 1, 2]) and r.base) vat_amount = sum(vat_taxes.mapped('amount')) self.vat_tax_ids = vat_taxes self.vat_taxable_ids = vat_taxables self.vat_amount = vat_amount # self.vat_taxable_amount = sum(vat_taxables.mapped('base_amount')) self.vat_taxable_amount = sum(vat_taxables.mapped('base')) # self.vat_base_amount = sum(vat_taxes.mapped('base_amount')) self.vat_base_amount = sum(vat_taxes.mapped('base')) # vat exempt values # exempt taxes are the ones with code 2 vat_exempt_taxes = self.tax_line_ids.filtered( lambda r: ( r.tax_id.tax_group_id.type == 'tax' and r.tax_id.tax_group_id.tax == 'vat' and r.tax_id.tax_group_id.afip_code == 2)) self.vat_exempt_base_amount = sum( vat_exempt_taxes.mapped('base')) # self.vat_exempt_base_amount = sum( # vat_exempt_taxes.mapped('base_amount')) # vat_untaxed_base_amount values (no gravado) # vat exempt taxes are the ones with code 1 vat_untaxed_taxes = self.tax_line_ids.filtered( lambda r: ( r.tax_id.tax_group_id.type == 'tax' and r.tax_id.tax_group_id.tax == 'vat' and r.tax_id.tax_group_id.afip_code == 1)) self.vat_untaxed_base_amount = sum( vat_untaxed_taxes.mapped('base')) # self.vat_untaxed_base_amount = sum( # vat_untaxed_taxes.mapped('base_amount')) # other taxes values not_vat_taxes = self.tax_line_ids - vat_taxes other_taxes_amount = sum(not_vat_taxes.mapped('amount')) self.not_vat_tax_ids = not_vat_taxes self.other_taxes_amount = other_taxes_amount @api.multi @api.depends('document_number', 'number') def _get_invoice_number(self): """ Funcion que calcula numero de punto de venta y numero de factura a partir del document number. Es utilizado principalmente por el modulo de vat ledger citi """ # TODO mejorar estp y almacenar punto de venta y numero de factura por # separado, de hecho con esto hacer mas facil la carga de los # comprobantes de compra # decidimos obtener esto solamente para comprobantes con doc number for rec in self: str_number = rec.document_number or False if str_number: if rec.document_type_id.code in ['33', '99', '331', '332']: point_of_sale = '0' # leave only numbers and convert to integer invoice_number = str_number # despachos de importacion elif rec.document_type_id.code == '66': point_of_sale = '0' invoice_number = '0' elif "-" in str_number: splited_number = str_number.split('-') invoice_number = splited_number.pop() point_of_sale = splited_number.pop() elif "-" not in str_number and len(str_number) == 12: point_of_sale = str_number[:4] invoice_number = str_number[-8:] else: raise ValidationError(_( 'Could not get invoice number and point of sale for ' 'invoice id %i') % (rec.id)) rec.invoice_number = int( re.sub("[^0-9]", "", invoice_number)) rec.point_of_sale_number = int( re.sub("[^0-9]", "", point_of_sale)) @api.one @api.depends( 'invoice_line_ids', 'invoice_line_ids.product_id', 'invoice_line_ids.product_id.type', 'localization', ) def _get_concept(self): afip_concept = False if self.point_of_sale_type in ['online', 'electronic']: # exportaciones invoice_lines = self.invoice_line_ids product_types = set( [x.product_id.type for x in invoice_lines if x.product_id]) consumible = set(['consu', 'product']) service = set(['service']) mixed = set(['consu', 'service', 'product']) # default value "product" afip_concept = '1' if product_types.issubset(mixed): afip_concept = '3' if product_types.issubset(service): afip_concept = '2' if product_types.issubset(consumible): afip_concept = '1' if self.document_type_id.code in ['19', '20', '21']: # TODO verificar esto, como par expo no existe 3 y existe 4 # (otros), considermaos que un mixto seria el otros if afip_concept == '3': afip_concept = '4' self.afip_concept = afip_concept @api.multi def get_localization_invoice_vals(self): self.ensure_one() if self.localization == 'argentina': commercial_partner = self.partner_id.commercial_partner_id currency = self.currency_id.with_context( date=self.date_invoice or fields.Date.context_today(self)) if self.company_id.currency_id == currency: currency_rate = 1.0 else: currency_rate = currency.compute( 1., self.company_id.currency_id, round=False) return { 'afip_responsability_type_id': ( commercial_partner.afip_responsability_type_id.id), 'currency_rate': currency_rate, } else: return super( AccountInvoice, self).get_localization_invoice_vals() @api.model def _get_available_journal_document_types( self, journal, invoice_type, partner): """ This function search for available document types regarding: * Journal * Partner * Company * Documents configuration If needed, we can make this funcion inheritable and customizable per localization """ if journal.localization != 'argentina': return super( AccountInvoice, self)._get_available_journal_document_types( journal, invoice_type, partner) commercial_partner = partner.commercial_partner_id journal_document_types = journal_document_type = self.env[ 'account.journal.document.type'] if invoice_type in [ 'out_invoice', 'in_invoice', 'out_refund', 'in_refund']: if journal.use_documents: letters = journal.get_journal_letter( counterpart_partner=commercial_partner) domain = [ ('journal_id', '=', journal.id), '|', ('document_type_id.document_letter_id', 'in', letters.ids), ('document_type_id.document_letter_id', '=', False), ] # if invoice_type is refund, only credit notes if invoice_type in ['out_refund', 'in_refund']: domain += [ ('document_type_id.internal_type', # '=', 'credit_note')] # TODO, check if we need to add tickets and others # also 'in', ['credit_note', 'in_document'])] # else, none credit notes else: domain += [ ('document_type_id.internal_type', '!=', 'credit_note')] # If internal_type in context we try to serch specific document # for eg used on debit notes internal_type = self._context.get('internal_type', False) if internal_type: journal_document_type = journal_document_type.search( domain + [ ('document_type_id.internal_type', '=', internal_type)], limit=1) # For domain, we search all documents journal_document_types = journal_document_types.search(domain) # If not specific document type found, we choose another one if not journal_document_type and journal_document_types: journal_document_type = journal_document_types[0] if invoice_type == 'in_invoice': other_document_types = (commercial_partner.other_document_type_ids) domain = [ ('journal_id', '=', journal.id), ('document_type_id', 'in', other_document_types.ids), ] other_journal_document_types = self.env[ 'account.journal.document.type'].search(domain) journal_document_types += other_journal_document_types # if we have some document sepecific for the partner, we choose it if other_journal_document_types: journal_document_type = other_journal_document_types[0] return { 'available_journal_document_types': journal_document_types, 'journal_document_type': journal_document_type, } @api.multi @api.constrains('document_number', 'partner_id', 'company_id') def _check_document_number_unique(self): for rec in self.filtered(lambda x: x.localization == 'argentina'): if rec.document_number: domain = [ ('type', '=', rec.type), ('document_number', '=', rec.document_number), ('document_type_id', '=', rec.document_type_id.id), ('company_id', '=', rec.company_id.id), ('id', '!=', rec.id) ] msg = ( 'Error en factura con id %s: El numero de comprobante (%s)' ' debe ser unico por tipo de documento') if rec.type in ['out_invoice', 'out_refund']: # si es factura de cliente entonces tiene que ser numero # unico por compania y tipo de documento rec.search(domain) else: # si es factura de proveedor debe ser unica por proveedor domain += [ ('partner_id.commercial_partner_id', '=', rec.commercial_partner_id.id)] msg += ' y proveedor' if rec.search(domain): raise ValidationError(msg % (rec.id, rec.document_number)) @api.multi def action_move_create(self): """ We add currency rate on move creation so it can be used by electronic invoice later on action_number """ self.check_argentinian_invoice_taxes() return super(AccountInvoice, self).action_move_create() @api.multi def check_argentinian_invoice_taxes(self): """ We make theis function to be used as a constraint but also to be called from other models like vat citi """ # only check for argentinian localization companies _logger.info('Running checks related to argentinian documents') # we consider argentinian invoices the ones from companies with # localization localization and that belongs to a journal with # use_documents argentinian_invoices = self.filtered( lambda r: ( r.localization == 'argentina' and r.use_documents)) if not argentinian_invoices: return True # check partner has responsability so it will be assigned on invoice # validate without_responsability = argentinian_invoices.filtered( lambda x: not x.commercial_partner_id.afip_responsability_type_id) if without_responsability: raise ValidationError(_( 'The following invoices has a partner without AFIP ' 'responsability: %s' % without_responsability.ids)) # we check all invoice tax lines has tax_id related # we exclude exempt vats and untaxed (no gravados) wihtout_tax_id = argentinian_invoices.mapped('tax_line_ids').filtered( lambda r: not r.tax_id) if wihtout_tax_id: raise ValidationError(_( "Some Invoice Tax Lines don't have a tax_id asociated, please " "correct them or try to refresh invoice ")) # check codes has argentinian tax attributes configured tax_groups = argentinian_invoices.mapped( 'tax_line_ids.tax_id.tax_group_id') unconfigured_tax_groups = tax_groups.filtered( lambda r: not r.type or not r.tax or not r.application) if unconfigured_tax_groups: raise ValidationError(_( "You are using argentinian localization and there are some tax" " groups that are not configured. Tax Groups (id): %s" % ( ', '.join(unconfigured_tax_groups.mapped( lambda x: '%s (%s)' % (x.name, x.id)))))) vat_taxes = self.env['account.tax'].search([ ('tax_group_id.type', '=', 'tax'), ('tax_group_id.tax', '=', 'vat')]) lines_without_vat = self.env['account.invoice.line'].search([ ('invoice_id', 'in', argentinian_invoices.ids), ('invoice_line_tax_ids', 'not in', vat_taxes.ids)]) if lines_without_vat: raise ValidationError(_( "Invoice with ID %s has some lines without vat Tax ") % ( lines_without_vat.mapped('invoice_id').ids)) # for invoice in argentinian_invoices: # # TODO usar round # # TODO tal vez debamos usar esto para un chequeo de suma de # # importes y demas, tener en cuenta caso de importaciones # # tal como esta este chequeo da error si se agregan impuestos # # manuales # if abs(invoice.vat_base_amount - invoice.amount_untaxed) > 0.1: # raise ValidationError(_( # "Invoice with ID %i has some lines without vat Tax ") % ( # invoice.id)) # Check except vat invoice afip_exempt_codes = ['Z', 'X', 'E', 'N', 'C'] for invoice in argentinian_invoices: special_vat_taxes = invoice.tax_line_ids.filtered( lambda r: r.tax_id.tax_group_id.afip_code in [1, 2, 3]) if ( special_vat_taxes and invoice.fiscal_position_id.afip_code not in afip_exempt_codes): raise ValidationError(_( "If you have choose a 0, exempt or untaxed 'tax', " "you must choose a fiscal position with afip code in %s.\n" "* Invoice id %i" % (afip_exempt_codes, invoice.id)) ) # TODO sacamos esto porque no era muy lindo y daba algunos errores con # el account_fix, hicimos que los datos demo hagan el compute tax # habria que ver una mejor forma de hacerlo para que tambien ande bien si # se importa desde interfaz # If we import or get demo data # tax_id is not loaded on tax lines, we couldn't find the error # so we add this to fix it # @api.one # @api.constrains('invoice_line_ids') # def update_taxes_fix(self): # context = dict(self._context) # if context.get('constraint_update_taxes'): # return True # self.with_context(constraint_update_taxes=True).compute_taxes() # we add fiscal position with fp method instead of directly from partner # TODO. this should go in a PR to ODOO @api.onchange('partner_id', 'company_id') def _onchange_partner_id(self): res = super(AccountInvoice, self)._onchange_partner_id() fiscal_position = self.env[ 'account.fiscal.position'].with_context( force_company=self.company_id.id).get_fiscal_position( self.partner_id.id) if fiscal_position: self.fiscal_position_id = fiscal_position return res @api.one @api.constrains('date_invoice') def set_date_afip(self): if self.date_invoice: date_invoice = fields.Datetime.from_string(self.date_invoice) if not self.afip_service_start: self.afip_service_start = date_invoice + relativedelta(day=1) if not self.afip_service_end: self.afip_service_end = date_invoice + \ relativedelta(day=1, days=-1, months=+1)
class HrExpense(models.Model): _name = "hr.expense" _inherit = ['mail.thread', 'ir.needaction_mixin'] _description = "Expense" _order = "date desc" name = fields.Char(string='Expense Description', readonly=True, required=True, states={'draft': [('readonly', False)]}) date = fields.Date(readonly=True, states={'draft': [('readonly', False)]}, default=fields.Date.context_today, string="Date") employee_id = fields.Many2one('hr.employee', string="Employee", required=True, readonly=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1)) product_id = fields.Many2one('product.product', string='Product', readonly=True, states={'draft': [('readonly', False)]}, domain=[('can_be_expensed', '=', True)], required=True) product_uom_id = fields.Many2one('product.uom', string='Unit of Measure', required=True, readonly=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env['product.uom'].search([], limit=1, order='id')) unit_amount = fields.Float(string='Unit Price', readonly=True, required=True, states={'draft': [('readonly', False)]}, digits=dp.get_precision('Product Price')) quantity = fields.Float(required=True, readonly=True, states={'draft': [('readonly', False)]}, digits=dp.get_precision('Product Unit of Measure'), default=1) tax_ids = fields.Many2many('account.tax', 'expense_tax', 'expense_id', 'tax_id', string='Taxes', states={'done': [('readonly', True)], 'post': [('readonly', True)]}) untaxed_amount = fields.Float(string='Subtotal', store=True, compute='_compute_amount', digits=dp.get_precision('Account')) total_amount = fields.Float(string='Total', store=True, compute='_compute_amount', digits=dp.get_precision('Account')) company_id = fields.Many2one('res.company', string='Company', readonly=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env.user.company_id) currency_id = fields.Many2one('res.currency', string='Currency', readonly=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env.user.company_id.currency_id) analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account', states={'post': [('readonly', True)], 'done': [('readonly', True)]}, oldname='analytic_account', domain=[('account_type', '=', 'normal')]) department_id = fields.Many2one('hr.department', string='Department', states={'post': [('readonly', True)], 'done': [('readonly', True)]}) description = fields.Text() payment_mode = fields.Selection([("own_account", "Employee (to reimburse)"), ("company_account", "Company")], default='own_account', states={'done': [('readonly', True)], 'post': [('readonly', True)]}, string="Payment By") journal_id = fields.Many2one('account.journal', string='Expense Journal', states={'done': [('readonly', True)], 'post': [('readonly', True)]}, default=lambda self: self.env['account.journal'].search([('type', '=', 'purchase')], limit=1), help="The journal used when the expense is done.") bank_journal_id = fields.Many2one('account.journal', string='Bank Journal', states={'done': [('readonly', True)], 'post': [('readonly', True)]}, default=lambda self: self.env['account.journal'].search([('type', 'in', ['case', 'bank'])], limit=1), help="The payment method used when the expense is paid by the company.") account_move_id = fields.Many2one('account.move', string='Journal Entry', copy=False, track_visibility="onchange") attachment_number = fields.Integer(compute='_compute_attachment_number', string='Number of Attachments') state = fields.Selection([('draft', 'To Submit'), ('submit', 'Submitted'), ('approve', 'Approved'), ('post', 'Waiting Payment'), ('done', 'Paid'), ('cancel', 'Refused') ], string='Status', index=True, readonly=True, track_visibility='onchange', copy=False, default='draft', required=True, help='When the expense request is created the status is \'To Submit\'.\n It is submitted by the employee and request is sent to manager, the status is \'Submitted\'.\ \nIf the manager approve it, the status is \'Approved\'.\n If the accountant genrate the accounting entries for the expense request, the status is \'Waiting Payment\'.') @api.depends('quantity', 'unit_amount', 'tax_ids', 'currency_id') def _compute_amount(self): for expense in self: expense.untaxed_amount = expense.unit_amount * expense.quantity taxes = expense.tax_ids.compute_all(expense.unit_amount, expense.currency_id, expense.quantity, expense.product_id, expense.employee_id.user_id.partner_id) expense.total_amount = taxes.get('total_included') @api.multi def _compute_attachment_number(self): attachment_data = self.env['ir.attachment'].read_group([('res_model', '=', 'hr.expense'), ('res_id', 'in', self.ids)], ['res_id'], ['res_id']) attachment = dict((data['res_id'], data['res_id_count']) for data in attachment_data) for expense in self: expense.attachment_number = attachment.get(expense.id, 0) @api.onchange('product_id') def _onchange_product_id(self): if self.product_id: if not self.name: self.name = self.product_id.display_name or '' self.unit_amount = self.env['product.template']._price_get(self.product_id, 'standard_price')[self.product_id.id] self.product_uom_id = self.product_id.uom_id self.tax_ids = self.product_id.supplier_taxes_id @api.onchange('product_uom_id') def _onchange_product_uom_id(self): if self.product_id and self.product_uom_id.category_id != self.product_id.uom_id.category_id: raise UserError(_('Selected Unit of Measure does not belong to the same category as the product Unit of Measure')) @api.onchange('employee_id') def _onchange_employee_id(self): self.department_id = self.employee_id.department_id def _add_followers(self): user_ids = [] employee = self.employee_id if employee.user_id: user_ids.append(employee.user_id.id) if employee.parent_id: user_ids.append(employee.parent_id.user_id.id) if employee.department_id and employee.department_id.manager_id and employee.parent_id != employee.department_id.manager_id: user_ids.append(employee.department_id.manager_id.user_id.id) self.sudo().message_subscribe_users(user_ids=user_ids) @api.model def create(self, vals): hr_expense = super(HrExpense, self).create(vals) if vals.get('employee_id'): hr_expense._add_followers() return hr_expense @api.multi def write(self, vals): res = super(HrExpense, self).write(vals) if vals.get('employee_id'): self._add_followers() return res @api.multi def unlink(self): if any(expense.state not in ['draft', 'cancel'] for expense in self): raise UserError(_('You can only delete draft or refused expenses!')) return super(HrExpense, self).unlink() @api.multi def submit_expenses(self): if any(expense.state != 'draft' for expense in self): raise UserError(_("You can only submit draft expenses!")) self.write({'state': 'submit'}) @api.multi def approve_expenses(self): self.write({'state': 'approve'}) @api.multi def refuse_expenses(self, reason): self.write({'state': 'cancel'}) if self.employee_id.user_id: body = (_("Your Expense %s has been refused.<br/><ul class=o_timeline_tracking_value_list><li>Reason<span> : </span><span class=o_timeline_tracking_value>%s</span></li></ul>") % (self.name, reason)) self.message_post(body=body, partner_ids=[self.employee_id.user_id.partner_id.id]) @api.multi def paid_expenses(self): self.write({'state': 'done'}) @api.multi def reset_expenses(self): return self.write({'state': 'draft'}) @api.multi def _track_subtype(self, init_values): self.ensure_one() if 'state' in init_values and self.state == 'approve': return 'hr_expense.mt_expense_approved' elif 'state' in init_values and self.state == 'submit': return 'hr_expense.mt_expense_confirmed' elif 'state' in init_values and self.state == 'cancel': return 'hr_expense.mt_expense_refused' return super(HrExpense, self)._track_subtype(init_values) def _prepare_move_line(self, line): ''' This function prepares move line of account.move related to an expense ''' partner_id = self.employee_id.address_home_id.commercial_partner_id.id return { 'date_maturity': line.get('date_maturity'), 'partner_id': partner_id, 'name': line['name'][:64], 'debit': line['price'] > 0 and line['price'], 'credit': line['price'] < 0 and -line['price'], 'account_id': line['account_id'], 'analytic_line_ids': line.get('analytic_line_ids'), 'amount_currency': line['price'] > 0 and abs(line.get('amount_currency')) or -abs(line.get('amount_currency')), 'currency_id': line.get('currency_id'), 'tax_line_id': line.get('tax_line_id'), 'tax_ids': line.get('tax_ids'), 'ref': line.get('ref'), 'quantity': line.get('quantity',1.00), 'product_id': line.get('product_id'), 'product_uom_id': line.get('uom_id'), 'analytic_account_id': line.get('analytic_account_id'), } @api.multi def _compute_expense_totals(self, company_currency, account_move_lines, move_date): ''' internal method used for computation of total amount of an expense in the company currency and in the expense currency, given the account_move_lines that will be created. It also do some small transformations at these account_move_lines (for multi-currency purposes) :param account_move_lines: list of dict :rtype: tuple of 3 elements (a, b ,c) a: total in company currency b: total in hr.expense currency c: account_move_lines potentially modified ''' self.ensure_one() total = 0.0 total_currency = 0.0 for line in account_move_lines: line['currency_id'] = False line['amount_currency'] = False if self.currency_id != company_currency: line['currency_id'] = self.currency_id.id line['amount_currency'] = line['price'] line['price'] = self.currency_id.with_context(date=move_date or fields.Date.context_today(self)).compute(line['price'], company_currency) total -= line['price'] total_currency -= line['amount_currency'] or line['price'] return total, total_currency, account_move_lines @api.multi def action_move_create(self): ''' main function that is called when trying to create the accounting entries related to an expense ''' if any(expense.state != 'approve' for expense in self): raise UserError(_("You can only generate accounting entry for approved expense(s).")) if any(expense.employee_id != self[0].employee_id for expense in self): raise UserError(_("Expenses must belong to the same Employee.")) if any(not expense.journal_id for expense in self): raise UserError(_("Expenses must have an expense journal specified to generate accounting entries.")) journal_dict = {} maxdate = False for expense in self: if expense.date > maxdate: maxdate = expense.date jrn = expense.bank_journal_id if expense.payment_mode == 'company_account' else expense.journal_id journal_dict.setdefault(jrn, []) journal_dict[jrn].append(expense) for journal, expense_list in journal_dict.items(): #create the move that will contain the accounting entries move = self.env['account.move'].create({ 'journal_id': journal.id, 'company_id': self.env.user.company_id.id, 'date': maxdate, }) for expense in expense_list: company_currency = expense.company_id.currency_id diff_currency_p = expense.currency_id != company_currency #one account.move.line per expense (+taxes..) move_lines = expense._move_line_get() #create one more move line, a counterline for the total on payable account total, total_currency, move_lines = expense._compute_expense_totals(company_currency, move_lines, maxdate) if expense.payment_mode == 'company_account': if not expense.bank_journal_id.default_credit_account_id: raise UserError(_("No credit account found for the %s journal, please configure one.") % (expense.bank_journal_id.name)) emp_account = expense.bank_journal_id.default_credit_account_id.id else: if not expense.employee_id.address_home_id: raise UserError(_("No Home Address found for the employee %s, please configure one.") % (expense.employee_id.name)) emp_account = expense.employee_id.address_home_id.property_account_payable_id.id move_lines.append({ 'type': 'dest', 'name': expense.employee_id.name, 'price': total, 'account_id': emp_account, 'date_maturity': expense.date, 'amount_currency': diff_currency_p and total_currency or False, 'currency_id': diff_currency_p and expense.currency_id.id or False, 'ref': expense.employee_id.address_home_id.ref or False }) #convert eml into an osv-valid format lines = map(lambda x:(0, 0, expense._prepare_move_line(x)), move_lines) move.with_context(dont_create_taxes=True).write({'line_ids': lines}) expense.write({'account_move_id': move.id, 'state': 'post'}) if expense.payment_mode == 'company_account': expense.paid_expenses() move.post() return True @api.multi def _move_line_get(self): account_move = [] for expense in self: if expense.product_id: account = expense.product_id.product_tmpl_id._get_product_accounts()['expense'] if not account: raise UserError(_("No Expense account found for the product %s (or for it's category), please configure one.") % (expense.product_id.name)) else: account = self.env['ir.property'].with_context(force_company=expense.company_id.id).get('property_account_expense_categ_id', 'product.category') if not account: raise UserError(_('Please configure Default Expense account for Product expense: `property_account_expense_categ_id`.')) move_line = { 'type': 'src', 'name': expense.name.split('\n')[0][:64], 'price_unit': expense.unit_amount, 'quantity': expense.quantity, 'price': expense.total_amount, 'account_id': account.id, 'product_id': expense.product_id.id, 'uom_id': expense.product_uom_id.id, 'analytic_account_id': expense.analytic_account_id.id, } account_move.append(move_line) # Calculate tax lines and adjust base line taxes = expense.tax_ids.compute_all(expense.unit_amount, expense.currency_id, expense.quantity, expense.product_id) account_move[-1]['price'] = taxes['total_excluded'] account_move[-1]['tax_ids'] = expense.tax_ids.ids for tax in taxes['taxes']: account_move.append({ 'type': 'tax', 'name': tax['name'], 'price_unit': tax['amount'], 'quantity': 1, 'price': tax['amount'], 'account_id': tax['account_id'] or move_line['account_id'], 'tax_line_id': tax['id'], }) return account_move @api.multi def action_get_attachment_view(self): self.ensure_one() res = self.env['ir.actions.act_window'].for_xml_id('base', 'action_attachment') res['domain'] = [('res_model', '=', 'hr.expense'), ('res_id', 'in', self.ids)] res['context'] = {'default_res_model': 'hr.expense', 'default_res_id': self.id} return res
class qdodoo_car_sale_contract(models.Model): """ 销售合同 """ _name = 'qdodoo.car.sale.contract' _rec_name = 'contract_num' _order = 'id desc' name = fields.Char(u'合同编号') contract_num = fields.Char( u'合同号', required=True, default=lambda self: str(datetime.now().year) + '-SDHSLGXHSC-') partner_id = fields.Many2one('res.partner', u'客户', required=True) date_order = fields.Date(u'合同日期', required=True, default=datetime.now().date()) car_number = fields.Float(u'车辆数', compute="_get_car_number") amount_total = fields.Float(u'金额', compute="_get_car_number") deposit_rate = fields.Float(u'保证金比例(%)', required=True) state = fields.Selection([('draft', u'草稿'), ('doing', u'执行'), ('done', u'完成'), ('cancel', u'取消'), ('no_normal', u'异常')], u'状态', default='draft') order_line = fields.One2many('qdodoo.car.sale.contract.line', 'order_id', u'车辆明细') file_name = fields.Char(u'合同名称', copy=False) contract_file = fields.Binary(u'合同扫描件', copy=False) pledge_money = fields.Float(u'保证金已收金额', copy=False) currency_id = fields.Many2one('res.currency', u'外币币种', required=True) currency_raise = fields.Float(u'外币汇率', required=True, digits=(14, 6)) is_issuing = fields.Boolean(u'已收保证金', copy=False) is_make_invoice = fields.Boolean(u'全部已开票', copy=False) is_payment = fields.Boolean(u'全部已收提车款', copy=False) is_settlement = fields.Boolean(u'已结算', copy=False) dalay_date = fields.Float(u'延期天数', default='90') dalay_rate = fields.Float(u'押汇利率(%)', default='4.5') dalay_china_rate = fields.Float(u'延期利率(%)人民币', default='10') _sql_constraints = [ ('contract_num_uniq', 'unique(contract_num)', '销售合同号已存在!'), ] @api.onchange('currency_id') def onchange_currcency(self): if self.currency_id: self.currency_raise = 1 / self.currency_id.rate_silent # 只能删除草稿或取消的订单 @api.multi def unlink(self): for ids in self: if ids.state not in ('draft', 'cancel'): raise osv.except_osv(_(u'错误'), _(u'只能删除草稿或取消的订单!')) return super(qdodoo_car_sale_contract, self).unlink() # 获取唯一的编号 @api.model def create(self, vals): if not vals.get('name'): vals['name'] = self.env['ir.sequence'].get( 'qdodoo.car.sale.contract') return super(qdodoo_car_sale_contract, self).create(vals) # 获取车辆数量、金额 def _get_car_number(self): for ids in self: number = 0 money = 0 for line in ids.order_line: number += line.product_qty money += line.all_money ids.car_number = number ids.amount_total = money # 押汇申请(销售) @api.multi def btn_negotiation_sale(self): negotiation_obj = self.env['qdodoo.car.negotiation.manager.sale'] # 判断是否存在押汇 negotiation_id = negotiation_obj.search([('purchase_id.contract_id', '=', self.id), ('state', '!=', 'cancel')]) result = self.env['ir.model.data'].get_object_reference( 'qdodoo_car_import_trade', 'tree_qdodoo_car_negotiation_manager_sale') view_id = result and result[1] or False result_form = self.env['ir.model.data'].get_object_reference( 'qdodoo_car_import_trade', 'form_qdodoo_car_negotiation_manager_sale') view_id_form = result_form and result_form[1] or False return { 'name': _('押汇'), 'view_type': 'form', "view_mode": 'tree,form', 'res_model': 'qdodoo.car.negotiation.manager.sale', 'type': 'ir.actions.act_window', 'domain': [('id', '=', negotiation_id.ids)], 'views': [(view_id, 'tree'), (view_id_form, 'form')], 'view_id': [view_id], } @api.one # 确认销售合同(生成车辆档案) def btn_doing(self): information_obj = self.env['qdodoo.car.information'] for ids in self: if not ids.order_line: raise osv.except_osv(_('错误!'), _('请输入车辆明细.')) for line in ids.order_line: # 判断如果存在车架号,则数量只能为1 if line.product_num and line.product_qty != 1: raise osv.except_osv(_('错误!'), _('填写车架号的明细数量只能为1.')) line.write({'rest_qty': line.product_qty}) val = {} val['sale_contract'] = ids.id val['product_id'] = line.product_id.id val['price_unit'] = line.price_unit val['tax_money'] = line.tax_money val['product_num'] = line.product_num val['pledge_money'] = line.pledge_money / line.product_qty val['agent_money'] = line.agent_money val['currency_id'] = self.currency_id.id if line.product_qty > 1: for k in range(line.product_qty): information_obj.create(val) else: information_obj.create(val) return self.write({'state': 'doing'}) @api.multi # 保证金(收款通知) def btn_predict_money(self): payment_obj = self.env['qdodoo.car.payment.order'] line_obj = self.env['qdodoo.car.payment.line'] information_obj = self.env['qdodoo.car.information'] domain = [('type', '=', 'collection'), ('contract_id', '=', self.id), ('is_pledge', '=', True), ('state', '!=', 'cancel')] value = { 'type': 'collection', 'contract_id': self.id, 'partner_id': self.partner_id.id, 'is_pledge': True } # 是保证金 model_id, pay_project = self.env['ir.model.data'].get_object_reference( 'qdodoo_car_import_trade', 'qdodoo_car_expense_1') value['pay_project'] = pay_project # 判断是否存在收款通知 payment_id = payment_obj.search(domain) if not payment_id: # 创建对应的收款通知 payment_id = payment_obj.create(value) # 创建对应的收款明细 # 获取销售订单对应的车辆档案 information_ids = information_obj.search([('sale_contract', '=', self.id)]) for information_id in information_ids: money = information_id.pledge_money line_obj.create({ 'order_id': payment_id.id, 'product_num': information_id.id, 'money': money }) result = self.env['ir.model.data'].get_object_reference( 'qdodoo_car_import_trade', 'tree_qdodoo_car_payment_order') view_id = result and result[1] or False result_form = self.env['ir.model.data'].get_object_reference( 'qdodoo_car_import_trade', 'form_qdodoo_car_payment_order') view_id_form = result_form and result_form[1] or False return { 'name': _('收款通知'), 'view_type': 'form', "view_mode": 'tree,form', 'res_model': 'qdodoo.car.payment.order', 'type': 'ir.actions.act_window', 'domain': [('id', '=', payment_id.id)], 'views': [(view_id, 'tree'), (view_id_form, 'form')], 'view_id': [view_id], } @api.multi # 二次保证金 def btn_predict_money_two(self): margin_obj = self.env['qdodoo.car.margin.money'] margin_ids = margin_obj.search([('partner_id', '=', self.partner_id.id) ]) result = self.env['ir.model.data'].get_object_reference( 'qdodoo_car_import_trade', 'tree_qdodoo_car_margin_money') view_id = result and result[1] or False result_form = self.env['ir.model.data'].get_object_reference( 'qdodoo_car_import_trade', 'form_qdodoo_car_margin_money') view_id_form = result_form and result_form[1] or False return { 'name': _('二次保证金'), 'view_type': 'form', "view_mode": 'tree,form', 'res_model': 'qdodoo.car.margin.money', 'type': 'ir.actions.act_window', 'domain': [('id', '=', margin_ids.ids)], 'context': { 'partner_id': self.partner_id.id }, 'views': [(view_id, 'tree'), (view_id_form, 'form')], 'view_id': [view_id], } @api.multi # 提车款 def btn_bring_car(self): carry_obj = self.env['qdodoo.car.carry.money'] carry_ids = carry_obj.search([('partner_id', '=', self.partner_id.id)]) result = self.env['ir.model.data'].get_object_reference( 'qdodoo_car_import_trade', 'tree_qdodoo_car_carry_money') view_id = result and result[1] or False result_form = self.env['ir.model.data'].get_object_reference( 'qdodoo_car_import_trade', 'form_qdodoo_car_carry_money') view_id_form = result_form and result_form[1] or False return { 'name': _('提车款'), 'view_type': 'form', "view_mode": 'tree,form', 'res_model': 'qdodoo.car.carry.money', 'type': 'ir.actions.act_window', 'domain': [('id', '=', carry_ids.ids)], 'context': { 'partner_id': self.partner_id.id }, 'views': [(view_id, 'tree'), (view_id_form, 'form')], 'view_id': [view_id], } @api.multi # 结算 def btn_squaring_up(self): settlement_obj = self.env['qdodoo.car.settlement'] settlement_ids = settlement_obj.search([('sale_id', '=', self.id)]) result = self.env['ir.model.data'].get_object_reference( 'qdodoo_car_import_trade', 'tree_qdodoo_car_settlement') view_id = result and result[1] or False result_form = self.env['ir.model.data'].get_object_reference( 'qdodoo_car_import_trade', 'form_qdodoo_car_settlement') view_id_form = result_form and result_form[1] or False return { 'name': _('结算单'), 'view_type': 'form', "view_mode": 'tree,form', 'res_model': 'qdodoo.car.settlement', 'type': 'ir.actions.act_window', 'domain': [('id', '=', settlement_ids.ids)], 'views': [(view_id, 'tree'), (view_id_form, 'form')], 'view_id': [view_id], } @api.one # 取消 def btn_cancel(self): return self.write({'state': 'cancel'}) @api.one # 异常 def btn_unusual(self): return self.write({'state': 'no_normal'}) @api.one # 恢复执行 def btn_perform(self): return self.write({'state': 'doing'}) @api.multi # 查看车辆档案 def btn_car_information(self): information_obj = self.env['qdodoo.car.information'] information_ids = information_obj.search([('sale_contract', '=', self.id)]) result = self.env['ir.model.data'].get_object_reference( 'qdodoo_car_import_trade', 'tree_qdodoo_car_information') view_id = result and result[1] or False result_form = self.env['ir.model.data'].get_object_reference( 'qdodoo_car_import_trade', 'form_qdodoo_car_information') view_id_form = result_form and result_form[1] or False return { 'name': _('车辆档案'), 'view_type': 'form', "view_mode": 'tree,form', 'res_model': 'qdodoo.car.information', 'type': 'ir.actions.act_window', 'domain': [('id', 'in', information_ids.ids)], 'views': [(view_id, 'tree'), (view_id_form, 'form')], 'view_id': [view_id], } @api.multi # 查看外贸合同 def btn_purchase_contract(self): purchase_obj = self.env['qdodoo.car.purchase.contract'] purchase_ids = purchase_obj.search([('contract_id', '=', self.id)]) result = self.env['ir.model.data'].get_object_reference( 'qdodoo_car_import_trade', 'tree_qdodoo_car_purchase_contract') view_id = result and result[1] or False result_form = self.env['ir.model.data'].get_object_reference( 'qdodoo_car_import_trade', 'form_qdodoo_car_purchase_contract') view_id_form = result_form and result_form[1] or False return { 'name': _('外贸合同'), 'view_type': 'form', "view_mode": 'tree,form', 'res_model': 'qdodoo.car.purchase.contract', 'type': 'ir.actions.act_window', 'domain': [('id', 'in', purchase_ids.ids)], 'views': [(view_id, 'tree'), (view_id_form, 'form')], 'view_id': [view_id], }
def _check_attended_date(self): for obj in self: call_received = obj.call_received call_attended = obj.call_attended if call_received and call_attended: if call_attended < call_received: return False return True _constraints = [ (_check_attended_date, 'Call Attended should be greater than call received', ['call_received','call_attended']), ] # id = fields.Integer(string="ID",readonly=True) name = fields.Char(string="Case",required=True) message_summary=fields.Char('Message Summary') case_name = fields.Char(string="Case",required=True) active = fields.Boolean(string="Active",required=False,default=True) create_date = fields.Datetime(string="Creation Date",readonly=True, index=True) write_date = fields.Datetime(string="Update Date",readonly=True) date_deadline = fields.Date(string="Deadline") partner_id = fields.Many2one('res.partner', 'Customer', required=True) contact_id = fields.Many2one('res.partner', 'Contact', required=False) company_id = fields.Many2one('res.company', 'Company') description = fields.Text(string="Private Note") action_taken = fields.Text(compute='_task_lines_cal', string='Action Taken') re_open = fields.Text(string="Reopen") kanban_state= fields.Selection([('normal','Normal'),('blocked','Blocked'),('done','Ready for next stage'),\ ],string = 'Kanban State', track_visibility='onchange',help="A Case's kanban state indicates special situations affecting it:\n" " * Normal is the default situation\n"
class AccountTaxReport(models.Model): _name = 'account.tax.report' _description = 'Account Tax Report (pdf/xls)' _auto = False doc_type = fields.Selection( [('sale', 'Sales'), ('purchase', 'Purchase')], string='Type', ) period_id = fields.Many2one( 'account.period', string='Period', ) move_number = fields.Char(string='JE Number', ) move_ref = fields.Char( string='JE Ref.', help="Original Document", ) year = fields.Char(string='Year', ) month = fields.Char(string='Month', ) report_period_id = fields.Many2one( 'account.period', string='Reporting Period', ) partner_id = fields.Many2one( 'res.partner', string='Partner', ) partner_name = fields.Char(string='Partner Name', ) partner_title = fields.Char(string='Partner Title', ) partner_vat = fields.Char(string='Partner VAT', ) partner_taxbranch = fields.Char(string='Partner Taxbranch', ) tax_id = fields.Many2one( 'account.tax', string='Tax', ) tax_sequence = fields.Integer(string='Sequence', ) tax_sequence_display = fields.Char(string='Sequence Display', ) invoice_date = fields.Date(string='Invoice Date', ) invoice_number = fields.Char(string='Invoice Number', ) base = fields.Float(string='Base', ) amount = fields.Float(string='Tax', ) def _select(self): res = """ doc_type, atd.id, atd.period_id, am.name as move_number, am.ref as move_ref, to_char(ap.date_start, 'YYYY') as "year", to_char(ap.date_start, 'MM') as "month", report_period_id, tax_sequence, tax_id, tax_sequence_display, invoice_date, invoice_number, atd.partner_id, case when cancel is true then '' else rp.name end as partner_name, case when cancel is true then '' else rpt.name end as partner_title, case when cancel is true then '' else rp.vat end as vat, case when cancel is true then '' else rp.taxbranch end as taxbranch, case when cancel is true then 0.0 else base_company end as base, case when cancel is true then 0.0 else amount_company end as amount """ return res def init(self, cr): tools.drop_view_if_exists(cr, self._table) cr.execute("""CREATE or REPLACE VIEW %s as ( select %s from account_tax_detail atd left outer join res_partner rp on rp.id = atd.partner_id left outer join res_partner_title rpt on rp.title = rpt.id left outer join account_period ap on atd.period_id = ap.id left outer join account_move am on am.id = atd.ref_move_id where report_period_id is not null order by year, month, tax_sequence )""" % ( self._table, self._select(), ))
class project_case_version(models.Model): _name = "project.case.version" _order = "name desc" name = fields.Char(string="student Name") active = fields.Boolean(string="student Name",default=True)
class TxtIva(models.Model): _name = "txt.iva" _inherit = ['mail.thread'] @api.model def _default_period_id(self): """ Return current period """ fecha = time.strftime('%m/%Y') periods = self.env['account.period'].search([('code', '=', fecha)]) return periods and periods[0].id or False @api.multi def _get_amount_total(self): """ Return total amount withheld of each selected bill """ res = {} for txt in self: res[txt.id] = 0.0 for txt_line in txt.txt_ids: if txt_line.invoice_id.type in ['out_refund', 'in_refund']: res[txt.id] -= txt_line.amount_withheld else: res[txt.id] += txt_line.amount_withheld return res @api.multi def _get_amount_total_base(self): """ Return total amount base of each selected bill """ res = {} for txt in self: res[txt.id] = 0.0 for txt_line in txt.txt_ids: if txt_line.invoice_id.type in ['out_refund', 'in_refund']: res[txt.id] -= txt_line.untaxed else: res[txt.id] += txt_line.untaxed return res name = fields.Char( string='Description', size=128, required=True, select=True, default=lambda self: 'Withholding Vat ' + time.strftime('%m/%Y'), help="Description about statement of withholding income") company_id = fields.Many2one( 'res.company', string='Company', required=True, readonly=True, states={'draft': [('readonly', False)]}, help='Company', default=lambda self: self.env['res.company']._company_default_get()) state = fields.Selection([ ('draft', 'Draft'), ('confirmed', 'Confirmed'), ('done', 'Done'), ('cancel', 'Cancelled') ], string='Estado', select=True, readonly=True, default='draft', help="proof status") period_id = fields.Many2one( 'account.period', string='Period', required=True, readonly=True, default=_default_period_id, states={'draft': [('readonly', False)]}, help='fiscal period') type = fields.Boolean( string='Retention Suppliers?', required=True, states={'draft': [('readonly', False)]}, default=True, help="Select the type of retention to make") date_start = fields.Date( string='Begin Date', required=True, states={'draft': [('readonly', False)]}, help="Begin date of period") date_end = fields.Date( string='End date', required=True, states={'draft': [('readonly', False)]}, help="End date of period") txt_ids = fields.One2many( 'txt.iva.line', 'txt_id', readonly=True, states={'draft': [('readonly', False)]}, help='Txt field lines of ar required by SENIAT for' ' VAT withholding') amount_total_ret = fields.Float( string='Withholding total amount', digits=dp.get_precision('Account'), compute=_get_amount_total, readonly=True, help="Monto Total Retenido") amount_total_base = fields.Float( string='Taxable total amount', digits=dp.get_precision('Account'), compute=_get_amount_total_base, readonly=True, help="Total de la Base Imponible") @api.multi def name_get(self): """ Return a list with id and name of the current register """ res = [(r.id, r.name) for r in self] return res @api.multi def action_anular(self): """ Return document state to draft """ self.write({'state': 'draft'}) return True @api.multi def check_txt_ids(self): """ Check that txt_iva has lines to process.""" for awi in self: if not awi.txt_ids: raise exceptions.except_orm( _("Missing Values !"), _("Missing VAT TXT Lines!!!")) return True @api.multi def action_confirm(self): """ Transfers the document status to confirmed """ self.check_txt_ids() self.write({'state': 'confirmed'}) return True @api.multi def action_generate_lines_txt(self): """ Current lines are cleaned and rebuilt """ rp_obj = self.env['res.partner'] voucher_obj = self.env['account.wh.iva'] txt_iva_obj = self.env['txt.iva.line'] vouchers = [] txt_brw = self.browse(self._ids)[0] txt_ids = txt_iva_obj.search([('txt_id', '=', txt_brw.id)]) if txt_ids: txt_ids.unlink() if txt_brw.type: vouchers = voucher_obj.search([ ('date_ret', '>=', txt_brw.date_start), ('date_ret', '<=', txt_brw.date_end), ('period_id', '=', txt_brw.period_id.id), ('state', '=', 'done'), ('type', 'in', ['in_invoice', 'in_refund'])]) else: vouchers = voucher_obj.search([ ('date_ret', '>=', txt_brw.date_start), ('date_ret', '<=', txt_brw.date_end), ('period_id', '=', txt_brw.period_id.id), ('state', '=', 'done'), ('type', 'in', ['out_invoice', 'out_refund'])]) for voucher in vouchers: acc_part_id = rp_obj._find_accounting_partner(voucher.partner_id) for voucher_lines in voucher.wh_lines: if voucher_lines.invoice_id.state not in ['open', 'paid']: continue for voucher_tax_line in voucher_lines.tax_line: txt_iva_obj.create( {'partner_id': acc_part_id.id, 'voucher_id': voucher.id, 'invoice_id': voucher_lines.invoice_id.id, 'txt_id': txt_brw.id, 'untaxed': voucher_tax_line.base, 'amount_withheld': voucher_tax_line.amount_ret, 'tax_wh_iva_id': voucher_tax_line.id, }) return True @api.model def get_buyer_vendor(self, txt, txt_line): """ Return the buyer and vendor of the sale or purchase invoice @param txt: current txt document @param txt_line: One line of the current txt document """ rp_obj = self.env['res.partner'] vat_company = rp_obj._find_accounting_partner( txt.company_id.partner_id).vat[2:] vat_partner = rp_obj._find_accounting_partner( txt_line.partner_id).vat[2:] if txt_line.invoice_id.type in ['out_invoice', 'out_refund']: vendor = vat_company buyer = vat_partner else: buyer = vat_company vendor = vat_partner return (vendor, buyer) @api.model def get_document_affected(self, txt_line): """ Return the reference or number depending of the case @param txt_line: line of the current document """ number = '0' if txt_line.invoice_id.type in ['in_invoice', 'in_refund'] and \ txt_line.invoice_id.parent_id: number = txt_line.invoice_id.parent_id.supplier_invoice_number elif txt_line.invoice_id.parent_id: number = txt_line.invoice_id.parent_id.number return number @api.model def get_number(self, number, inv_type, max_size): """ Return a list of number for document number @param number: list of characters from number or reference of the bill @param inv_type: invoice type @param long: max size oh the number """ if not number: return '0' result = '' for i in number: if inv_type == 'vou_number' and i.isdigit(): if len(result) < max_size: result = i + result elif i.isalnum(): if len(result) < max_size: result = i + result return result[::-1].strip() @api.model def get_document_number(self, txt_line, inv_type): """ Return the number o reference of the invoice into txt line @param txt_line: One line of the current txt document @param inv_type: invoice type into txt line """ number = 0 if txt_line.invoice_id.type in ['in_invoice', 'in_refund']: if not txt_line.invoice_id.supplier_invoice_number: raise exceptions.except_orm( _('Invalid action !'), _("Unable to make txt file, because the bill has no" " reference number free!")) else: number = self.get_number( txt_line.invoice_id.supplier_invoice_number.strip(), inv_type, 20) elif txt_line.invoice_id.number: number = self.get_number( txt_line.invoice_id.number.strip(), inv_type, 20) return number @api.model def get_type_document(self, txt_line): """ Return the document type @param txt_line: line of the current document """ inv_type = '03' if txt_line.invoice_id.type in ['out_invoice', 'in_invoice']: inv_type = '01' elif txt_line.invoice_id.type in ['out_invoice', 'in_invoice'] and \ txt_line.invoice_id.parent_id: inv_type = '02' return inv_type @api.model def get_max_aliquot(self, txt_line): """Get maximum aliquot per invoice""" res = [] for tax_line in txt_line.invoice_id.tax_line: res.append(int(tax_line.tax_id.amount * 100)) return max(res) @api.model def get_amount_line(self, txt_line, amount_exempt): """Method to compute total amount""" ali_max = self.get_max_aliquot(txt_line) exempt = 0 if ali_max == int(txt_line.tax_wh_iva_id.tax_id.amount * 100): exempt = amount_exempt total = (txt_line.tax_wh_iva_id.base + txt_line.tax_wh_iva_id.amount + exempt) return total, exempt @api.model def get_amount_exempt_document(self, txt_line): """ Return total amount not entitled to tax credit and the remaining amounts @param txt_line: One line of the current txt document """ tax = 0 amount_doc = 0 for tax_line in txt_line.invoice_id.tax_line: if 'SDCF' in tax_line.name or \ (tax_line.base and not tax_line.amount): tax = tax_line.base + tax else: amount_doc = tax_line.base + amount_doc return (tax, amount_doc) @api.model def get_alicuota(self, txt_line): """ Return aliquot of the withholding into line @param txt_line: One line of the current txt document """ return int(txt_line.tax_wh_iva_id.tax_id.amount * 100) @api.multi def generate_txt(self): """ Return string with data of the current document """ txt_string = '' rp_obj = self.env['res.partner'] for txt in self: vat = rp_obj._find_accounting_partner( txt.company_id.partner_id).vat[2:] vat = vat for txt_line in txt.txt_ids: vendor, buyer = self.get_buyer_vendor(txt, txt_line) period = txt.period_id.name.split('/') period2 = period[0] + period[1] # TODO: use the start date of the period to get the period2 # with the 'YYYYmm' operation_type = ('V' if txt_line.invoice_id.type in ['out_invoice', 'out_refund'] else 'C') document_type = self.get_type_document(txt_line) document_number = self.get_document_number( txt_line, 'inv_number') control_number = self.get_number( txt_line.invoice_id.nro_ctrl, 'inv_ctrl', 20) document_affected = self.get_document_affected(txt_line) voucher_number = self.get_number( txt_line.voucher_id.number, 'vou_number', 14) amount_exempt, amount_untaxed = \ self.get_amount_exempt_document(txt_line) amount_untaxed = amount_untaxed alicuota = self.get_alicuota(txt_line) amount_total, amount_exempt = self.get_amount_line( txt_line, amount_exempt) txt_string = ( txt_string + buyer + '\t' + period2.strip() + '\t' + txt_line.invoice_id.date_invoice + '\t' + operation_type + '\t' + document_type + '\t' + vendor + '\t' + document_number + '\t' + control_number + '\t' + str(round(amount_total, 2)) + '\t' + str(round(txt_line.untaxed, 2)) + '\t' + str(round(txt_line.amount_withheld, 2)) + '\t' + document_affected + '\t' + voucher_number + '\t' + str(round(amount_exempt, 2)) + '\t' + str(alicuota) + '\t' + '0' + '\n') return txt_string @api.multi def _write_attachment(self, root, context=None): """ Encrypt txt, save it to the db and view it on the client as an attachment @param root: location to save document """ fecha = time.strftime('%Y_%m_%d_%H%M%S') name = 'IVA_' + fecha + '.' + 'txt' self.env['ir.attachment'].create({ 'name': name, 'datas': base64.encodestring(root), 'datas_fname': name, 'res_model': 'txt.iva', 'res_id': self.ids[0], }) msg = _("File TXT %s generated.") % (name) self.message_post(body=msg) @api.multi def action_done(self): """ Transfer the document status to done """ root = self.generate_txt() self._write_attachment(root) self.write({'state': 'done'}) return True
class sla_main_mast(models.Model): _name='sla.main.mast' name = fields.Char(string='SLA')
class BankingExportSepaWizard(models.TransientModel): _name = 'banking.export.sepa.wizard' _inherit = ['banking.export.pain'] _description = 'Export SEPA Credit Transfer File' state = fields.Selection([('create', 'Create'), ('finish', 'Finish')], string='State', readonly=True, default='create') batch_booking = fields.Boolean( string='Batch Booking', help="If true, the bank statement will display only one debit " "line for all the wire transfers of the SEPA XML file ; if " "false, the bank statement will display one debit line per wire " "transfer of the SEPA XML file.") charge_bearer = fields.Selection( [('SLEV', 'Following Service Level'), ('SHAR', 'Shared'), ('CRED', 'Borne by Creditor'), ('DEBT', 'Borne by Debtor')], string='Charge Bearer', default='SLEV', required=True, help="Following service level : transaction charges are to be " "applied following the rules agreed in the service level " "and/or scheme (SEPA Core messages must use this). Shared : " "transaction charges on the debtor side are to be borne by " "the debtor, transaction charges on the creditor side are to " "be borne by the creditor. Borne by creditor : all " "transaction charges are to be borne by the creditor. Borne " "by debtor : all transaction charges are to be borne by the " "debtor.") nb_transactions = fields.Integer(string='Number of Transactions', readonly=True) total_amount = fields.Float(string='Total Amount', readonly=True) file = fields.Binary(string="File", readonly=True) filename = fields.Char(string="Filename", readonly=True) payment_order_ids = fields.Many2many('payment.order', 'wiz_sepa_payorders_rel', 'wizard_id', 'payment_order_id', string='Payment Orders', readonly=True) @api.model def create(self, vals): payment_order_ids = self._context.get('active_ids', []) vals.update({ 'payment_order_ids': [[6, 0, payment_order_ids]], }) return super(BankingExportSepaWizard, self).create(vals) @api.multi def create_sepa(self): """Creates the SEPA Credit Transfer file. That's the important code!""" pain_flavor = self.payment_order_ids[0].mode.type.code convert_to_ascii = \ self.payment_order_ids[0].mode.convert_to_ascii if pain_flavor == 'pain.001.001.02': bic_xml_tag = 'BIC' name_maxsize = 70 root_xml_tag = 'pain.001.001.02' elif pain_flavor == 'pain.001.001.03': bic_xml_tag = 'BIC' # size 70 -> 140 for <Nm> with pain.001.001.03 # BUT the European Payment Council, in the document # "SEPA Credit Transfer Scheme Customer-to-bank # Implementation guidelines" v6.0 available on # http://www.europeanpaymentscouncil.eu/knowledge_bank.cfm # says that 'Nm' should be limited to 70 # so we follow the "European Payment Council" # and we put 70 and not 140 name_maxsize = 70 root_xml_tag = 'CstmrCdtTrfInitn' elif pain_flavor == 'pain.001.001.04': bic_xml_tag = 'BICFI' name_maxsize = 140 root_xml_tag = 'CstmrCdtTrfInitn' elif pain_flavor == 'pain.001.001.05': bic_xml_tag = 'BICFI' name_maxsize = 140 root_xml_tag = 'CstmrCdtTrfInitn' # added pain.001.003.03 for German Banks # it is not in the offical ISO 20022 documentations, but nearly all # german banks are working with this instead 001.001.03 elif pain_flavor == 'pain.001.003.03': bic_xml_tag = 'BIC' name_maxsize = 70 root_xml_tag = 'CstmrCdtTrfInitn' else: raise Warning( _("Payment Type Code '%s' is not supported. The only " "Payment Type Codes supported for SEPA Credit Transfers " "are 'pain.001.001.02', 'pain.001.001.03', " "'pain.001.001.04', 'pain.001.001.05'" " and 'pain.001.003.03'.") % pain_flavor) gen_args = { 'bic_xml_tag': bic_xml_tag, 'name_maxsize': name_maxsize, 'convert_to_ascii': convert_to_ascii, 'payment_method': 'TRF', 'file_prefix': 'sct_', 'pain_flavor': pain_flavor, 'pain_xsd_file': 'account_banking_sepa_credit_transfer/data/%s.xsd' % pain_flavor, } pain_ns = { 'xsi': 'http://www.w3.org/2001/XMLSchema-instance', None: 'urn:iso:std:iso:20022:tech:xsd:%s' % pain_flavor, } xml_root = etree.Element('Document', nsmap=pain_ns) pain_root = etree.SubElement(xml_root, root_xml_tag) pain_03_to_05 = [ 'pain.001.001.03', 'pain.001.001.04', 'pain.001.001.05', 'pain.001.003.03' ] # A. Group header group_header_1_0, nb_of_transactions_1_6, control_sum_1_7 = \ self.generate_group_header_block(pain_root, gen_args) transactions_count_1_6 = 0 total_amount = 0.0 amount_control_sum_1_7 = 0.0 lines_per_group = {} # key = (requested_date, priority) # values = list of lines as object for payment_order in self.payment_order_ids: total_amount = total_amount + payment_order.total for line in payment_order.bank_line_ids: priority = line.priority # The field line.date is the requested payment date # taking into account the 'date_prefered' setting # cf account_banking_payment_export/models/account_payment.py # in the inherit of action_open() key = (line.date, priority) if key in lines_per_group: lines_per_group[key].append(line) else: lines_per_group[key] = [line] for (requested_date, priority), lines in lines_per_group.items(): # B. Payment info payment_info_2_0, nb_of_transactions_2_4, control_sum_2_5 = \ self.generate_start_payment_info_block( pain_root, "self.payment_order_ids[0].reference + '-' " "+ requested_date.replace('-', '') + '-' + priority", priority, False, False, requested_date, { 'self': self, 'priority': priority, 'requested_date': requested_date, }, gen_args) self.generate_party_block(payment_info_2_0, 'Dbtr', 'B', self.payment_order_ids[0].mode.bank_id, gen_args) charge_bearer_2_24 = etree.SubElement(payment_info_2_0, 'ChrgBr') charge_bearer_2_24.text = self.charge_bearer transactions_count_2_4 = 0 amount_control_sum_2_5 = 0.0 for line in lines: transactions_count_1_6 += 1 transactions_count_2_4 += 1 # C. Credit Transfer Transaction Info credit_transfer_transaction_info_2_27 = etree.SubElement( payment_info_2_0, 'CdtTrfTxInf') payment_identification_2_28 = etree.SubElement( credit_transfer_transaction_info_2_27, 'PmtId') end2end_identification_2_30 = etree.SubElement( payment_identification_2_28, 'EndToEndId') end2end_identification_2_30.text = self._prepare_field( 'End to End Identification', 'line.name', {'line': line}, 35, gen_args=gen_args) currency_name = self._prepare_field('Currency Code', 'line.currency.name', {'line': line}, 3, gen_args=gen_args) amount_2_42 = etree.SubElement( credit_transfer_transaction_info_2_27, 'Amt') instructed_amount_2_43 = etree.SubElement(amount_2_42, 'InstdAmt', Ccy=currency_name) instructed_amount_2_43.text = '%.2f' % line.amount_currency amount_control_sum_1_7 += line.amount_currency amount_control_sum_2_5 += line.amount_currency if not line.bank_id: raise Warning( _("Bank account is missing on the bank payment line " "of partner '%s' (reference '%s').") % (line.partner_id.name, line.name)) self.generate_party_block( credit_transfer_transaction_info_2_27, 'Cdtr', 'C', line.bank_id, gen_args) self.generate_remittance_info_block( credit_transfer_transaction_info_2_27, line, gen_args) if pain_flavor in pain_03_to_05: nb_of_transactions_2_4.text = unicode(transactions_count_2_4) control_sum_2_5.text = '%.2f' % amount_control_sum_2_5 if pain_flavor in pain_03_to_05: nb_of_transactions_1_6.text = unicode(transactions_count_1_6) control_sum_1_7.text = '%.2f' % amount_control_sum_1_7 else: nb_of_transactions_1_6.text = unicode(transactions_count_1_6) control_sum_1_7.text = '%.2f' % amount_control_sum_1_7 return self.finalize_sepa_file_creation(xml_root, total_amount, transactions_count_1_6, gen_args) @api.multi def save_sepa(self): """Save the SEPA file: send the done signal to all payment orders in the file. With the default workflow, they will transition to 'done', while with the advanced workflow in account_banking_payment they will transition to 'sent' waiting reconciliation. """ for order in self.payment_order_ids: workflow.trg_validate(self._uid, 'payment.order', order.id, 'done', self._cr) self.env['ir.attachment'].create({ 'res_model': 'payment.order', 'res_id': order.id, 'name': self.filename, 'datas': self.file, }) return True
class AccountOperationTemplate(models.Model): _name = "account.operation.template" _description = "Preset to create journal entries during a invoices and payments matching" name = fields.Char(string='Button Label', required=True) sequence = fields.Integer(required=True, default=10) has_second_line = fields.Boolean(string='Add a second line', default=False) company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env.user.company_id) account_id = fields.Many2one('account.account', string='Account', ondelete='cascade', domain=[('deprecated', '=', False)]) journal_id = fields.Many2one( 'account.journal', string='Journal', ondelete='cascade', help="This field is ignored in a bank statement reconciliation.") label = fields.Char(string='Journal Item Label') amount_type = fields.Selection([('fixed', 'Fixed'), ('percentage', 'Percentage of balance')], required=True, default='percentage') amount = fields.Float( digits=0, required=True, default=100.0, help= "Fixed amount will count as a debit if it is negative, as a credit if it is positive." ) tax_id = fields.Many2one('account.tax', string='Tax', ondelete='restrict', domain=[('type_tax_use', '=', 'purchase')]) analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account', ondelete='set null', domain=[('account_type', '=', 'normal')]) second_account_id = fields.Many2one('account.account', string='Account', ondelete='cascade', domain=[('deprecated', '=', False)]) second_journal_id = fields.Many2one( 'account.journal', string='Journal', ondelete='cascade', help="This field is ignored in a bank statement reconciliation.") second_label = fields.Char(string='Journal Item Label') second_amount_type = fields.Selection( [('fixed', 'Fixed'), ('percentage', 'Percentage of amount')], string='Amount type', required=True, default='percentage') second_amount = fields.Float( string='Amount', digits=0, required=True, default=100.0, help= "Fixed amount will count as a debit if it is negative, as a credit if it is positive." ) second_tax_id = fields.Many2one('account.tax', string='Tax', ondelete='restrict', domain=[('type_tax_use', '=', 'purchase')]) second_analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account', ondelete='set null', domain=[('account_type', '=', 'normal')]) @api.onchange('name') def onchange_name(self): self.label = self.name
class wizard_ntty_product_import(models.TransientModel): _name = 'wizard.ntty.product.import' @api.one def _compute_enable_supplier_button(self): return_value = False ntty = self.env['ntty.config.settings'].search([]) # ntty = self.env['ntty.config.settings'].browse(1) if not ntty: raise osv.except_osv('Error', 'Connection not defined') return_value = False if ntty and not ntty.ntty_check_supliers: return_value = True self.enable_supplier_button = return_value @api.one def _compute_len_detail_ids(self): self.len_detail_ids = len(self.detail_ids) ntty_id = fields.Char('NTTY ID') detail_ids = fields.One2many( comodel_name='wizard.ntty.product.import.detail', inverse_name='import_id') ntty_data = fields.Text('NTTY Data') certifications = fields.Text('Certifications') sqm_pcb = fields.Char('SQM PCB') enable_supplier_button = fields.Boolean( string='Enable Supplier Button', default=True, compute=_compute_enable_supplier_button) len_detail_ids = fields.Integer(string='len_detail_ids', compute=_compute_len_detail_ids) @api.multi def create_ntty_products(self): ntty_id = self.ntty_id if not ntty_id: raise osv.except_osv(('Error'), ('Please enter a NTTY ID')) return None ntty = self.env['ntty.config.settings'].search([]) # ntty = self.env['ntty.config.settings'].browse(1) if not ntty: return None ntty_service_address = ntty.ntty_service_address ntty_service_user_email = ntty.ntty_service_user_email ntty_service_token = ntty.ntty_service_token ntty_check_supliers = ntty.ntty_check_supliers ntty_generate_price_list = ntty.ntty_generate_price_list ntty_related_products = ntty.ntty_related_products ntty_partner_info = ntty.ntty_partner_info ntty_product_category = ntty.ntty_product_category ntty_supplier_short_name = ntty.ntty_supplier_short_name if len(self.detail_ids) == 0 and ntty_check_supliers: raise osv.except_osv( ('Error'), ('Please enter a NTTY ID and pull its suppliers')) return None identifier = self.ntty_id ntty_data = self.ntty_data if not ntty_data: httplib.HTTPConnection._http_vsn = 10 httplib.HTTPConnection._http_vsn_str = 'HTTP/1.0' req = urllib2.Request( str(ntty_service_address) + "/entities/" + str(ntty_id)) req.add_header('X-User-Email', str(ntty_service_user_email)) req.add_header('X-User-Token', str(ntty_service_token)) try: resp = urllib2.urlopen(req) except StandardError: raise except_orm(_('Warning'), _("Error connecting to NTTY.")) return False if not resp.code == 200 and resp.msg == "OK": raise except_orm(_('Warning'), _("Unable to connect to NTTY.")) return {} content = resp.read() res = json.loads(content) else: identifier = self.ntty_id res = ast.literal_eval(self.ntty_data) if not ntty_check_supliers: vals_detail = { 'import_id': self.id, 'ntty_partner_id': -999, 'selected': 'yes' } self.env['wizard.ntty.product.import.detail'].create(vals_detail) # routes = self.env['stock.location.route'].search(['|',('name','=','Make To Order'),('name','=','Buy')]) routes = self.env['stock.location.route'].search([('name', '=', 'Buy') ]) if not routes: raise osv.except_osv(('Error'), ('Routes MTO and Buy are not present!!!')) return None route_ids = [x.id for x in routes] created_products = [] for detail in self.detail_ids: ntty_pull_log = '' if detail.selected == 'yes': entity = res['entity'] ntty_pull_log = res['entity'] part_numbers = res['part_numbers'] temp_wcp_weight = entity['values'].get('wcp_weight', False) if not temp_wcp_weight: weight = 0 else: try: weight = float( temp_wcp_weight[:temp_wcp_weight.find(' ')]) except: weight = 0 pass flag_ul = False flag_rohs = False flag_defense = False flag_automotive = False certifications = entity['values'].get('certifications', False) categ_id = 1 certification_technology = '' if certifications and ntty_product_category: for certification in certifications: if certification['id'] == 131: flag_rohs = True if certification['id'] == 132: flag_ul = True #if 'Automotive' in certification['name']: # flag_automotive = True # category_id = self.env['product.category'].search([('name','=','Automotive')]) # if category_id: # categ_id = category_id.id #if 'Defense' in certification['name']: # flag_defense = True # category_id = self.env['product.category'].search([('name','=','Defense')]) # if category_id: # categ_id = category_id.id temp_supplier = detail.partner_id supplier_currency = temp_supplier.property_product_pricelist_purchase.currency_id.name pcb_category_id = self.env['product.category'].search([ ('name', '=', 'PCB') ]) currency_category_id = self.env[ 'product.category'].search([('name', '=', supplier_currency)]) if not pcb_category_id: vals_pcb_category = { 'name': 'PCB', 'parent_id': 1, } pcb_category_id = self.env[ 'product.category'].create(vals_pcb_category) if not currency_category_id: vals_currency_category = { 'name': supplier_currency, 'parent_id': pcb_category_id.id } currency_category_id = self.env[ 'product.category'].create( vals_currency_category) pcb_category_id = currency_category_id.id categ_id = currency_category_id.id else: if len(currency_category_id) > 1: for currency_category in currency_category_id: if currency_category.parent_id.id == pcb_category_id.id: pcb_category_id = currency_category.id categ_id = currency_category.id break else: pcb_category_id = currency_category_id.id categ_id = currency_category_id.id try: if certification[ 'certification_type'] == 'Technology': certification_technology = certification[ 'name'] if certification[ 'certification_type'] == 'Category': product_category = self.env['product.category'].search(\ [('name','=',certification['name'])]) parent_category = self.env['product.category'].search(\ [('name','=',certification_technology)]) parent_category_id = None if not parent_category: vals_parent_category = { 'name': certification_technology, 'parent_id': pcb_category_id, } parent_category = self.env[ 'product.category'].create( vals_parent_category) parent_category = parent_category.id else: parent_category = parent_category.id if product_category and len( product_category) == 1: categ_id = product_category.id else: if certification_technology == '': for cert in certifications: if cert['certification_type'] == 'Technology': certification_technology = cert[ 'name'] parent_category = self.env['product.category'].search(\ [('name','=',certification_technology)]) parent_category_id = None if not parent_category: vals_parent_category = { 'name': certification_technology, 'parent_id': pcb_category_id, } parent_category = self.env[ 'product.category'].create( vals_parent_category ) parent_category = parent_category.id else: parent_category = parent_category.id vals_category = { 'name': certification['name'], 'parent_id': parent_category, } category = self.env[ 'product.category'].create( vals_category) categ_id = category.id except: continue # Searches for product_owner product_brand = entity.get('product_owner', '') product_brand_text = entity.get('product_owner', '') lifecycle_blocks_orders = entity.get('lifecycle_blocks_orders', False) lifecycle_blocks_quotes = entity.get('lifecycle_blocks_quotes', False) lifecycle = entity.get('lifecycle', False) product_brand_id = None if product_brand: product_brand_id = self.env['product.brand'].search([ ('name', '=', product_brand) ]) if product_brand_id: product_brand_id = product_brand_id[0].id else: vals_brand = {'name': product_brand} product_brand_id = self.env['product.brand'].create( vals_brand) product_brand_id = product_brand_id.id # article_part_number = entity.get('article_part_number','') article_part_number = entity.get('name', 'N/A') identifier = entity.get('identifier', '') article_part_name = entity.get('article_part_name', 'article_part_number') ntty_name = entity.get('article_part_name', 'N/A') short_description = entity.get('short_description', '') long_description = entity.get('long_description', '') article_id = entity.get('article_id', '') panel_factor = int(entity['values'].get( 'panel_units', entity['values'].get('units', 1))) try: panel_weight = float( entity['values']['weight_calculation_panel'][0] ['panel_weight']) except KeyError: panel_weight = 1.00 try: panel_length = float(entity['values']['pcb_length']) except KeyError: panel_length = 0.00 try: panel_width = float(entity['values']['pcb_width']) except KeyError: panel_width = 0.00 try: sqm_pcb = float(entity['values']['sqm_pcb']) except KeyError: sqm_pcb = 0.00 try: signal_layers = float(entity['values']['signal_layers']) except KeyError: signal_layers = 0.00 try: weight_calc = (panel_weight / panel_factor) / 1000 except FloatingPointError: weight_calc = 0.00 try: files = entity['files'] for file in files: print file['url'] except KeyError: files = {} vals = { 'name': article_part_name, 'ntty_name': ntty_name, 'ntty_pull_log': ntty_pull_log, 'article_part_number': article_part_number, 'description': long_description, 'ntty_id': identifier, 'type': "product", 'panel_factor': panel_factor, 'panel_weight': panel_weight, 'panel_width': panel_width, 'panel_length': panel_length, 'weight_net': weight_calc, 'weight': weight_calc, 'sqm_pcb': sqm_pcb, 'signal_layers': signal_layers, 'product_brand_id': product_brand_id, 'product_brand_text': product_brand_text, 'ntty_rohs': flag_rohs, 'lifecycle_blocks_orders': lifecycle_blocks_orders, 'lifecycle_blocks_quotes': lifecycle_blocks_quotes, 'lifecycle': lifecycle, 'ntty_ul': flag_ul, 'sale_delay': 0, 'categ_id': categ_id, 'weight': weight / 1000, } product_code = '' part_name = '' part_description = '' default_code = '' # This is the place # prod = self.env['product.template'].search([('ntty_id', '=', identifier)]) for part_number in part_numbers: if part_number['part_organization'] == 'ELMATICA AS': default_code = part_number.get('part_number', '') if part_number['part_organization'] != 'ELMATICA AS'\ and part_number['part_organization'] != product_brand_text: part_name = part_number.get('part_name', '') part_description = part_number.get( 'part_description', '') product_code = part_number.get('part_number', '') vals['product_code'] = product_code default_code = product_code vals['default_code'] = default_code if detail.partner_id.short_name and ntty_supplier_short_name and ntty_check_supliers: vals[ 'name'] = article_part_number + ' ' + product_code + ' ' + detail.partner_id.short_name else: if detail.partner_id.name: vals[ 'name'] = article_part_number + ' ' + product_code + ' ' + detail.partner_id.name else: vals['name'] = article_part_number + ' ' + product_code vals['description'] = part_description, identifier_odoo = identifier + '#' + str( detail.partner_id.ntty_partner_id) prod = self.env['product.product'].search([('ntty_odoo', '=', identifier_odoo)]) vals['ntty_odoo'] = identifier_odoo vals['ntty_id'] = identifier if ntty.ntty_mto: #routes = self.env['stock.location.route'].search([('name','=','Make To Order')]) #route_ids = [] #if routes: # for route in routes: # route_ids.append(route.id) vals['route_ids'] = [(6, 0, route_ids)] if ntty.ntty_sold: vals['sale_ok'] = True else: vals['sale_ok'] = False if ntty.ntty_purchased: vals['purchase_ok'] = True else: vals['purchase_ok'] = False if not prod: prod = prod.create(vals) else: if len(prod) > 1: raise osv.except_osv(('Error'), ( 'More than one product/supplier for this NTTY ID')) return None prod.write(vals) created_products.append(prod) # Check suppliers setting if ntty_check_supliers: vals_supplier = { 'name': detail.partner_id.id, 'company_id': 1, 'product_tmpl_id': prod.product_tmpl_id.id, } prod_sup = self.env['product.supplierinfo'].search([('name','=',detail.partner_id.id),\ ('product_tmpl_id','=',prod.product_tmpl_id.id)]) if not prod_sup: prod_sup = self.env['product.supplierinfo'].create( vals_supplier) # Creates attributes for entity_values_key in entity['values'].keys(): value = entity['values'][entity_values_key] attribute_id = self.env['product.attribute'].search([ ('name', '=', entity_values_key) ]) if attribute_id: value_id = self.env['product.attribute.value'].\ search([('attribute_id','=',attribute_id.id),\ ('name','=',str(value))]) if not value_id: vals_value = { 'attribute_id': attribute_id.id, 'name': str(value), } value_id = self.env[ 'product.attribute.value'].create(vals_value) value_id = value_id.id else: value_id = value_id.id vals_attribute_line = { 'product_tmpl_id': prod.product_tmpl_id.id, 'attribute_id': attribute_id.id, 'value_ids': [(6, 0, [value_id])] } attribute_line_id = self.env['product.attribute.line'].\ create(vals_attribute_line) for detail in self.detail_ids: if detail.selected == 'no' and detail.partner_id: prod_sup = self.env['product.supplierinfo'].search([ ('name', '=', detail.partner_id.id) ]) po_sup = self.env['purchase.order'].search([ ('partner_id', '=', detail.partner_id.id) ]) inv_sup = self.env['account.invoice'].search([ ('partner_id', '=', detail.partner_id.id) ]) so_sup = self.env['sale.order'].search(['|',('partner_invoice_id','=',detail.partner_id.id),\ ('partner_id','=',detail.partner_id.id)]) if not prod_sup and not po_sup and not inv_sup and not so_sup: supplier_to_delete = detail.partner_id try: supplier_to_delete.unlink() except: pass else: if detail.ntty_partner_id != -999: detail.partner_id.message_post( body="Supplier created. Needs setup", context={}) if ntty_related_products: for prod in created_products: related_products = self.env['product.product'].search([ ('ntty_id', '=', self.ntty_id) ]) related = [related_product.product_tmpl_id.id for related_product in related_products \ if prod.product_tmpl_id.id != related_product.product_tmpl_id.id] vals = { 'alternative_product_ids': [(6, 0, related)], } product_template = prod.product_tmpl_id product_template.write(vals) res = { "name": "product.template.ntty.pull" + str(product_template.id), "type": "ir.actions.act_window", "res_model": "product.template", "view_type": "form", "view_mode": "form", #"view_id": "product.product_template_form_view", "res_id": product_template.id, "nodestroy": True, } return res @api.multi def pull_ntty_suppliers(self): ntty_id = self.ntty_id if not ntty_id: raise osv.except_osv(('Error'), ('Please enter a NTTY ID')) return None ntty = self.env['ntty.config.settings'].search([]) # ntty = self.env['ntty.config.settings'].browse(1) if not ntty: return None ntty_service_address = ntty.ntty_service_address ntty_service_user_email = ntty.ntty_service_user_email ntty_service_token = ntty.ntty_service_token httplib.HTTPConnection._http_vsn = 10 httplib.HTTPConnection._http_vsn_str = 'HTTP/1.0' req = urllib2.Request( str(ntty_service_address) + "/entities/" + str(ntty_id)) req.add_header('X-User-Email', str(ntty_service_user_email)) req.add_header('X-User-Token', str(ntty_service_token)) try: resp = urllib2.urlopen(req) except StandardError: raise except_orm(_('Warning'), _("Error connecting to NTTY.")) return False if not resp.code == 200 and resp.msg == "OK": raise except_orm(_('Warning'), _("Unable to connect to NTTY.")) return {} content = resp.read() res = json.loads(content) self.write({'ntty_data': res}) if res: if not ntty.ntty_check_supliers: return { 'type': 'ir.actions.act_window', 'res_model': self._name, # this model 'res_id': self.id, # the current wizard record 'view_type': 'form', 'view_mode': 'form', 'target': 'new' } import_id = self.id try: suppliers = res['entity']['values'].get( 'supplier_matching', []) except: suppliers = [] try: sqm_pcb = res['entity']['values']['sqm_pcb'] except: sqm_pcb = 0 try: res_certifications = res['entity']['values']['certifications'] except: res_certifications = [] certifications = '' for certification in res_certifications: certifications = certifications + ' - ' + certification['name'] self.write({'sqm_pcb': sqm_pcb, 'certifications': certifications}) for supplier in suppliers: supplier_id = self.env['res.partner'].search([ ('ntty_partner_id', '=', supplier['id']) ]) if not supplier_id: sup_odoo = self.env['res.partner'].create({'supplier': True, \ 'is_company': True, 'name': supplier['name'], 'ntty_partner_id': supplier['id'], \ 'partner_approved': True, 'active': True}) # sup_odoo.message_post(body="Supplier created. Needs setup", context={}) supplier_id = sup_odoo.id else: if len(supplier_id) > 1: supplier_id = supplier_id[0].id else: supplier_id = supplier_id.id vals_sup = { 'import_id': import_id, 'partner_id': supplier_id, 'ntty_partner_id': supplier['id'], 'selected': 'no', } return_id = self.env[ 'wizard.ntty.product.import.detail'].create(vals_sup) # return True return { 'type': 'ir.actions.act_window', 'res_model': self._name, # this model 'res_id': self.id, # the current wizard record 'view_type': 'form', 'view_mode': 'form', 'target': 'new' }
class AccountAccount(models.Model): _name = "account.account" _description = "Account" _order = "code" #@api.multi #def _compute_has_unreconciled_entries(self): # print "ici dedans" # account_ids = self.ids # for account in self: # # Avoid useless work if has_unreconciled_entries is not relevant for this account # if account.deprecated or not account.reconcile: # account.has_unreconciled_entries = False # account_ids = account_ids - account # if account_ids: # res = dict.fromkeys([x.id for x in account_ids], False) # self.env.cr.execute( # """ SELECT s.account_id FROM( # SELECT # a.id as account_id, a.last_time_entries_checked AS last_time_entries_checked, # MAX(l.write_date) AS max_date # FROM # account_move_line l # RIGHT JOIN account_account a ON (a.id = l.account_id) # WHERE # a.id in %s # AND EXISTS ( # SELECT 1 # FROM account_move_line l # WHERE l.account_id = a.id # AND l.amount_residual > 0 # ) # AND EXISTS ( # SELECT 1 # FROM account_move_line l # WHERE l.account_id = a.id # AND l.amount_residual < 0 # ) # GROUP BY a.id, a.last_time_entries_checked # ) as s # WHERE (last_time_entries_checked IS NULL OR max_date > last_time_entries_checked) # """ % (account_ids,)) # res.update(self.env.cr.dictfetchall()) # for account in self.browse(res.keys()): # if res[account.id]: # account.has_unreconciled_entries = True @api.multi @api.constrains('internal_type', 'reconcile') def _check_reconcile(self): for account in self: if account.internal_type in ( 'receivable', 'payable') and account.reconcile == False: raise ValueError( _('You cannot have a receivable/payable account that is not reconciliable. (account code: %s)' ) % account.code) name = fields.Char(required=True, index=True) currency_id = fields.Many2one( 'res.currency', string='Account Currency', help="Forces all moves for this account to have this account currency." ) code = fields.Char(size=64, required=True, index=True) deprecated = fields.Boolean(index=True, default=False) user_type_id = fields.Many2one( 'account.account.type', string='Type', required=True, oldname="user_type", help= "Account Type is used for information purpose, to generate country-specific legal reports, and set the rules to close a fiscal year and generate opening entries." ) internal_type = fields.Selection(related='user_type_id.type', store=True, readonly=True) #has_unreconciled_entries = fields.Boolean(compute='_compute_has_unreconciled_entries', # help="The account has at least one unreconciled debit and credit since last time the invoices & payments matching was performed.") last_time_entries_checked = fields.Datetime(string='Latest Invoices & Payments Matching Date', readonly=True, copy=False, help='Last time the invoices & payments matching was performed on this account. It is set either if there\'s not at least '\ 'an unreconciled debit and an unreconciled credit Or if you click the "Done" button.') reconcile = fields.Boolean( string='Allow Reconciliation', default=False, help= "Check this box if this account allows invoices & payments matching of journal items." ) tax_ids = fields.Many2many('account.tax', 'account_account_tax_default_rel', 'account_id', 'tax_id', string='Default Taxes') note = fields.Text('Internal Notes') company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env['res.company']. _company_default_get('account.account')) tag_ids = fields.Many2many( 'account.account.tag', 'account_account_account_tag', string='Tags', help="Optional tags you may want to assign for custom reporting") _sql_constraints = [ ('code_company_uniq', 'unique (code,company_id)', 'The code of the account must be unique per company !') ] @api.model def name_search(self, name, args=None, operator='ilike', limit=100): args = args or [] domain = [] if name: domain = [ '|', ('code', '=ilike', name + '%'), ('name', operator, name) ] if operator in expression.NEGATIVE_TERM_OPERATORS: domain = ['&', '!'] + domain[1:] accounts = self.search(domain + args, limit=limit) return accounts.name_get() @api.onchange('internal_type') def onchange_internal_type(self): if self.internal_type in ('receivable', 'payable'): self.reconcile = True @api.multi @api.depends('name', 'code') def name_get(self): result = [] for account in self: name = account.code + ' ' + account.name result.append((account.id, name)) return result @api.one def copy(self, default=None): default = dict(default or {}) default.update(code=_("%s (copy)") % (self.code or '')) return super(AccountAccount, self).copy(default) @api.multi def write(self, vals): # Dont allow changing the company_id when account_move_line already exist if vals.get('company_id', False): move_lines = self.env['account.move.line'].search( [('account_id', 'in', self.ids)], limit=1) for account in self: if (account.company_id.id <> vals['company_id']) and move_lines: raise UserError( _('You cannot change the owner company of an account that already contains journal items.' )) return super(AccountAccount, self).write(vals) @api.multi def unlink(self): if self.env['account.move.line'].search( [('account_id', 'in', self.ids)], limit=1): raise UserError( _('You cannot do that on an account that contains journal items.' )) #Checking whether the account is set as a property to any Partner or not values = [ 'account.account,%s' % (account_id, ) for account_id in self.ids ] partner_prop_acc = self.env['ir.property'].search( [('value_reference', 'in', values)], limit=1) if partner_prop_acc: raise UserError( _('You cannot remove/deactivate an account which is set on a customer or vendor.' )) return super(AccountAccount, self).unlink() @api.multi def mark_as_reconciled(self): return self.write({ 'last_time_entries_checked': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) }) @api.multi def action_open_reconcile(self): # Open reconciliation view for this account action_context = { 'show_mode_selector': False, 'account_ids': [ self.id, ] } return { 'type': 'ir.actions.client', 'tag': 'manual_reconciliation_view', 'context': action_context, }
class MilkControlReport(models.Model): _name = 'milk.control.report' exploitation_1 = fields.Many2one('res.partner', 'Exploitation 1', required=True, domain=[('farm', '=', True), ('is_cooperative','=',False)]) exploitation_2 = fields.Many2one('res.partner', 'Exploitation 2', domain=[('farm', '=', True), ('is_cooperative','=',False)]) from_date = fields.Date('From date', required=True) to_date = fields.Date('To date', required=True) milking_type_1 = fields.Selection(MILKING_TYPES, required=True) milking_type_2 = fields.Selection(MILKING_TYPES) primerizas_cant_1 = fields.Integer(compute='_calculate_vals', digits=dp.get_precision('milk_report')) primerizas_del_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) primerizas_litros_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) primerizas_grasa_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) primerizas_proteina_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) primerizas_celulas_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) primerizas_urea_1 = fields.Float() adultas_cant_1 = fields.Integer(compute='_calculate_vals', digits=dp.get_precision('milk_report')) adultas_del_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) adultas_litros_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) adultas_grasa_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) adultas_proteina_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) adultas_celulas_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) adultas_urea_1 = fields.Float() total_cant_1 = fields.Integer(compute='_calculate_vals', digits=dp.get_precision('milk_report')) total_del_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) total_litros_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) total_grasa_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) total_proteina_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) total_celulas_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) total_urea_1 = fields.Float() vacas_invertidas_1 = fields.Char(compute='_calculate_vals', digits=dp.get_precision('milk_report')) coeficiente_persistencia_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) leche_corregida_a_del_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) prod_media_lact_acumulada_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) num_medio_partos_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) relacion_grasa_prot_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) pico_novillas_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) pico_novillas_del_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) num_novillas_pico_del_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) pico_adultas_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) pico_adultas_del_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) num_adultas_pico_del_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) relacion_entre_picos_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) prim_terc_lact_num_animales_1 = fields.Integer(compute='_calculate_vals', digits=dp.get_precision('milk_report')) prim_terc_lact_porcen_animales_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) prim_terc_lact_media_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) prim_terc_lact_litros_total_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) seg_terc_lact_num_animales_1 = fields.Integer(compute='_calculate_vals', digits=dp.get_precision('milk_report')) seg_terc_lact_porcen_animales_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) seg_terc_lact_media_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) seg_terc_lact_litros_total_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) terc_terc_lact_num_animales_1 = fields.Integer(compute='_calculate_vals', digits=dp.get_precision('milk_report')) terc_terc_lact_porcen_animales_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) terc_terc_lact_media_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) terc_terc_lact_litros_total_1 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) primerizas_cant_2 = fields.Integer(compute='_calculate_vals', digits=dp.get_precision('milk_report')) primerizas_del_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) primerizas_litros_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) primerizas_grasa_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) primerizas_proteina_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) primerizas_celulas_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) primerizas_urea_2 = fields.Float() adultas_cant_2 = fields.Integer(compute='_calculate_vals', digits=dp.get_precision('milk_report')) adultas_del_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) adultas_litros_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) adultas_grasa_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) adultas_proteina_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) adultas_celulas_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) adultas_urea_2 = fields.Float() total_cant_2 = fields.Integer(compute='_calculate_vals', digits=dp.get_precision('milk_report')) total_del_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) total_litros_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) total_grasa_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) total_proteina_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) total_celulas_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) total_urea_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) vacas_invertidas_2 = fields.Char(compute='_calculate_vals', digits=dp.get_precision('milk_report')) coeficiente_persistencia_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) leche_corregida_a_del_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) prod_media_lact_acumulada_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) num_medio_partos_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) relacion_grasa_prot_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) pico_novillas_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) pico_novillas_del_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) num_novillas_pico_del_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) pico_adultas_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) pico_adultas_del_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) num_adultas_pico_del_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) relacion_entre_picos_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) prim_terc_lact_num_animales_2 = fields.Integer(compute='_calculate_vals', digits=dp.get_precision('milk_report')) prim_terc_lact_porcen_animales_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) prim_terc_lact_media_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) prim_terc_lact_litros_total_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) seg_terc_lact_num_animales_2 = fields.Integer(compute='_calculate_vals', digits=dp.get_precision('milk_report')) seg_terc_lact_porcen_animales_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) seg_terc_lact_media_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) seg_terc_lact_litros_total_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) terc_terc_lact_num_animales_2 = fields.Integer(compute='_calculate_vals', digits=dp.get_precision('milk_report')) terc_terc_lact_porcen_animales_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) terc_terc_lact_media_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) terc_terc_lact_litros_total_2 = fields.Float(compute='_calculate_vals', digits=dp.get_precision('milk_report')) graphic_img = fields.Binary(compute='_calculate_vals') def _get_company(self): return self.env.user.company_id company_id = fields.Many2one('res.company', 'Company', default=_get_company) @api.multi def _get_data(self, num): self.ensure_one() suffix = u'_%s' % num milk_control = self.env['milk.control'].search([('date', '>=', self.from_date), ('date', '<=', self.to_date), ('exploitation_id', '=', self['exploitation%s' % suffix].id)]) milk_data = milk_control.mapped('line_ids') if not milk_data: return self['primerizas_cant%s' % suffix] = sum([1 for x in milk_data if x.cib and x.birth_number == 1]) self['primerizas_del%s' % suffix] = average([x.days for x in milk_data if x.birth_number == 1]) self['primerizas_litros%s' % suffix] = average([x.get_liters(self['milking_type%s' % suffix]) for x in milk_data if x.birth_number == 1]) self['primerizas_grasa%s' % suffix] = (sum([x.get_liters(self['milking_type%s' % suffix]) * (x.fat / 100) for x in milk_data if x.birth_number == 1 and x.fat > 0]) / (sum([x.get_liters(self['milking_type%s' % suffix]) for x in milk_data if x.fat > 0 and x.birth_number == 1]) or 1.0)) * 100 self['primerizas_proteina%s' % suffix] = (sum([x.get_liters(self['milking_type%s' % suffix]) * (x.protein / 100) for x in milk_data if x.birth_number == 1 and x.protein > 0]) / (sum([x.get_liters(self['milking_type%s' % suffix]) for x in milk_data if x.protein > 0 and x.birth_number == 1]) or 1.0)) * 100 self['primerizas_celulas%s' % suffix] = sum([x.rcs * 1000 * x.get_liters(self['milking_type%s' % suffix]) for x in milk_data if x.birth_number == 1]) / ((sum([x.get_liters(self['milking_type%s' % suffix]) for x in milk_data if x.birth_number == 1 and x.fat > 0]) or 1.0) * 1000) self['adultas_cant%s' % suffix] = sum([1 for x in milk_data if x.birth_number > 1]) self['adultas_del%s' % suffix] = average([x.days for x in milk_data if x.birth_number > 1]) self['adultas_litros%s' % suffix] = average([x.get_liters(self['milking_type%s' % suffix]) for x in milk_data if x.birth_number > 1]) self['adultas_grasa%s' % suffix] = (sum([x.get_liters(self['milking_type%s' % suffix]) * (x.fat / 100) for x in milk_data if x.birth_number > 1 and x.fat > 0]) / (sum([x.get_liters(self['milking_type%s' % suffix]) for x in milk_data if x.birth_number > 1 and x.fat > 0]) or 1.0)) * 100 self['adultas_proteina%s' % suffix] = (sum([x.get_liters(self['milking_type%s' % suffix]) * (x.protein / 100) for x in milk_data if x.birth_number > 1 and x.protein > 0]) / (sum([x.get_liters(self['milking_type%s' % suffix]) for x in milk_data if x.birth_number > 1 and x.protein > 0]) or 1.0)) * 100 self['adultas_celulas%s' % suffix] = sum([x.rcs * 1000 * x.get_liters(self['milking_type%s' % suffix]) for x in milk_data if x.birth_number > 1]) / ((sum([x.get_liters(self['milking_type%s' % suffix]) for x in milk_data if x.birth_number > 1 and x.fat > 0]) or 1.0) * 1000) self['total_cant%s' % suffix] = sum([1 for x in milk_data if x.get_liters(self['milking_type%s' % suffix]) > 0]) self['total_del%s' % suffix] = average([x.days for x in milk_data]) self['total_litros%s' % suffix] = average([x.get_liters(self['milking_type%s' % suffix]) for x in milk_data]) self['total_grasa%s' % suffix] = (sum([x.get_liters(self['milking_type%s' % suffix]) * (x.fat / 100)]) / (sum([x.get_liters(self['milking_type%s' % suffix])]) or 1.0)) * 100 self['total_proteina%s' % suffix] = (sum([x.get_liters(self['milking_type%s' % suffix]) * (x.protein / 100)]) / (sum([x.get_liters(self['milking_type%s' % suffix])]) or 1.0)) * 100 self['total_celulas%s' % suffix] = sum([x.rcs * 1000 * x.get_liters(self['milking_type%s' % suffix]) for x in milk_data]) / ((sum([x.get_liters(self['milking_type%s' % suffix]) for x in milk_data if x.fat > 0]) or 1.0) * 1000) invertidas = sum([1 for x in milk_data if x.protein > x.fat]) self['vacas_invertidas%s' % suffix] = '%s (%0.2f%%)' % (invertidas, (float(invertidas) / (self['total_cant%s' % suffix] or 1.0)) * 100) self['coeficiente_persistencia%s' % suffix] = (average([x.get_liters(self['milking_type%s' % suffix]) for x in milk_data if x.days > 29 and x.days < 91]) - average([x.get_liters(self['milking_type%s' % suffix]) for x in milk_data if x.days > 179 and x.days < 306])) / ((average([x.days for x in milk_data if x.days > 179 and x.days < 306]) - average([x.days for x in milk_data if x.days > 29 and x.days < 91])) or 1.0) # TODO: Cambiar 165 por campo !!!!! self['leche_corregida_a_del%s' % suffix] = average([x.get_liters(self['milking_type%s' % suffix]) for x in milk_data]) + ((average([x.days for x in milk_data]) - 165) * self['coeficiente_persistencia%s' % suffix]) self['prod_media_lact_acumulada%s' % suffix] = average([x.cumulative_milk for x in milk_data]) / (average([x.days for x in milk_data]) or 1.0) self['num_medio_partos%s' % suffix] = average([x.birth_number for x in milk_data]) self['relacion_grasa_prot%s' % suffix] = self['total_grasa%s' % suffix] / (self['total_proteina%s' % suffix] or 1.0) self['pico_novillas%s' % suffix] = average([x.get_liters(self['milking_type%s' % suffix]) for x in milk_data if x.birth_number == 1 and x.days > 29 and x.days < 91]) self['pico_novillas_del%s' % suffix] = average([x.days for x in milk_data if x.birth_number == 1 and x.days > 29 and x.days < 91]) self['num_novillas_pico_del%s' % suffix] = sum([1 for x in milk_data if x.birth_number == 1 and x.days > 29 and x.days < 91]) self['pico_adultas%s' % suffix] = average([x.get_liters(self['milking_type%s' % suffix]) for x in milk_data if x.birth_number > 1 and x.days > 29 and x.days < 61]) self['pico_adultas_del%s' % suffix] = average([x.days for x in milk_data if x.birth_number > 1 and x.days > 29 and x.days < 61]) self['num_adultas_pico_del%s' % suffix] = sum([1 for x in milk_data if x.birth_number > 1 and x.days > 29 and x.days < 61]) if self['pico_adultas%s' % suffix]: self['relacion_entre_picos%s' % suffix] = self['pico_novillas%s' % suffix] / (self['pico_adultas%s' % suffix] or 1.0) else: self['relacion_entre_picos%s' % suffix] = 0 self['prim_terc_lact_num_animales%s' % suffix] = len(milk_data.filtered(lambda r: r.days < 91)) self['prim_terc_lact_porcen_animales%s' % suffix] = (self['prim_terc_lact_num_animales%s' % suffix] / (float(len(milk_data)) or 1.0)) * 100 self['prim_terc_lact_litros_total%s' % suffix] = sum([x.get_liters(self['milking_type%s' % suffix]) for x in milk_data if x.days < 91]) self['prim_terc_lact_media%s' % suffix] = self['prim_terc_lact_litros_total%s' % suffix] / (self['prim_terc_lact_num_animales%s' % suffix] or 1.0) self['seg_terc_lact_num_animales%s' % suffix] = len(milk_data.filtered(lambda r: r.days > 90 and r.days < 181)) self['seg_terc_lact_porcen_animales%s' % suffix] = (self['seg_terc_lact_num_animales%s' % suffix] / (float(len(milk_data)) or 1.0)) * 100 self['seg_terc_lact_litros_total%s' % suffix] = sum([x.get_liters(self['milking_type%s' % suffix]) for x in milk_data if x.days > 90 and x.days < 181]) self['seg_terc_lact_media%s' % suffix] = self['seg_terc_lact_litros_total%s' % suffix] / (self['seg_terc_lact_num_animales%s' % suffix] or 1.0) self['terc_terc_lact_num_animales%s' % suffix] = len(milk_data.filtered(lambda r : r.days > 180)) self['terc_terc_lact_porcen_animales%s' % suffix] = (self['terc_terc_lact_num_animales%s' % suffix] / (float(len(milk_data)) or 1.0)) * 100 self['terc_terc_lact_litros_total%s' % suffix] = sum([x.get_liters(self['milking_type%s' % suffix]) for x in milk_data if x.days > 180]) self['terc_terc_lact_media%s' % suffix] = self['terc_terc_lact_litros_total%s' % suffix] / (self['terc_terc_lact_num_animales%s' % suffix] or 1.0) @api.multi def _calculate_vals(self): for report in self: report._get_data(1) report._get_data(2) milk_control_1 = self.env['milk.control'].search([('date', '>=', self.from_date), ('date', '<=', self.to_date), ('exploitation_id', '=', self.exploitation_1.id)]) milk_data_1 = milk_control_1.mapped('line_ids') if self.exploitation_2: milk_control_2 = self.env['milk.control'].search([('date', '>=', self.from_date), ('date', '<=', self.to_date), ('exploitation_id', '=', self.exploitation_2.id)]) milk_data_2 = milk_control_2.mapped('line_ids') if not milk_data_1: return graph_vals = [] for vals in [(0, 31), (30, 91), (90, 121), (120, 181), (180, 241), (240, 305)]: av = average([x.get_liters(self.milking_type_1) for x in milk_data_1 if x.days > vals[0] and x.days < vals[1]]) cont = sum([1 for x in milk_data_1 if x.days > vals[0] and x.days < vals[1] and x.get_liters(self.milking_type_1) > 0]) if self.exploitation_2: av_2 = average([x.get_liters(self.milking_type_2) for x in milk_data_2 if x.days > vals[0] and x.days < vals[1]]) cont_2 = sum([1 for x in milk_data_2 if x.days > vals[0] and x.days < vals[1] and x.get_liters(self.milking_type_2) > 0]) graph_vals.append([round(av, 2), cont, round(av_2, 2), cont_2]) else: graph_vals.append([round(av, 2), cont]) av = average([x.get_liters(self.milking_type_1) for x in milk_data_1 if x.days > 305]) cont = sum([1 for x in milk_data_1 if x.days > 305 and x.get_liters(self.milking_type_1) > 0]) if self.exploitation_2: av_2 = average([x.get_liters(self.milking_type_2) for x in milk_data_2 if x.days > 305]) cont_2 = sum([1 for x in milk_data_2 if x.days > 305 and x.get_liters(self.milking_type_2) > 0]) graph_vals.append([round(av, 2), cont, round(av_2, 2), cont_2]) else: graph_vals.append([round(av, 2), cont]) if not self.exploitation_2: y_vals = [x[0] for x in graph_vals] + [x[1] for x in graph_vals] series_labels=[u'Media(lts/vaca y dia)', u'n de animales'] series_colors=['red', 'blue'] else: y_vals = [x[0] for x in graph_vals] + [x[1] for x in graph_vals] + [x[2] for x in graph_vals] + [x[3] for x in graph_vals] series_labels=[u'Media(lts/vaca y dia) %s' % self.exploitation_1.name, u'n de animales %s' % self.exploitation_1.name, u'Media(lts/vaca y dia) %s' % self.exploitation_2.name, u'n de animales %s' % self.exploitation_2.name] series_colors=['red', 'blue', 'green', 'yellow'] max_y = int(max(y_vals)) + 10 min_y = int(min(y_vals)) - 5 min_y = min_y > 0 and min_y or 0 test = VerticalBarPlot("/tmp/object_way.png", graph_vals, 900, 700, display_values=True, series_labels=series_labels, series_colors=series_colors, y_bounds=[min_y, max_y], x_labels=['DEL <30', 'DE 31-90 DEL', 'DE 91-120 DEL', 'DE 121-180 DEL', 'DE 181-240 DEL', 'DE 241-305 DEL', 'DEL>305'], border=20) test.render() test.commit() f = open("/tmp/object_way.png", "rb") self.graphic_img = base64.b64encode(f.read())
class AccountTax(models.Model): _name = 'account.tax' _description = 'Tax' _order = 'sequence' @api.model def _default_tax_group(self): return self.env['account.tax.group'].search([], limit=1) name = fields.Char(string='Tax Name', required=True, translate=True) type_tax_use = fields.Selection( [('sale', 'Sales'), ('purchase', 'Purchases'), ('none', 'None')], string='Tax Scope', required=True, default="sale", help= "Determines where the tax is selectable. Note : 'None' means a tax can't be used by itself, however it can still be used in a group." ) amount_type = fields.Selection(default='percent', string="Tax Computation", required=True, oldname='type', selection=[ ('group', 'Group of Taxes'), ('fixed', 'Fixed'), ('percent', 'Percentage of Price'), ('division', 'Percentage of Price Tax Included') ]) active = fields.Boolean( default=True, help="Set active to false to hide the tax without removing it.") company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env.user.company_id) children_tax_ids = fields.Many2many('account.tax', 'account_tax_filiation_rel', 'parent_tax', 'child_tax', string='Children Taxes') sequence = fields.Integer( required=True, default=1, help= "The sequence field is used to define order in which the tax lines are applied." ) amount = fields.Float(required=True, digits=(16, 4)) account_id = fields.Many2one( 'account.account', domain=[('deprecated', '=', False)], string='Tax Account', ondelete='restrict', help= "Account that will be set on invoice tax lines for invoices. Leave empty to use the expense account.", oldname='account_collected_id') refund_account_id = fields.Many2one( 'account.account', domain=[('deprecated', '=', False)], string='Tax Account on Refunds', ondelete='restrict', help= "Account that will be set on invoice tax lines for refunds. Leave empty to use the expense account.", oldname='account_paid_id') description = fields.Char(string='Label on Invoices', translate=True) price_include = fields.Boolean( string='Included in Price', default=False, help= "Check this if the price you use on the product and invoices includes this tax." ) include_base_amount = fields.Boolean( string='Affect Base of Subsequent Taxes', default=False, help= "If set, taxes which are computed after this one will be computed based on the price tax included." ) analytic = fields.Boolean( string="Include in Analytic Cost", help= "If set, the amount computed by this tax will be assigned to the same analytic account as the invoice line (if any)" ) tag_ids = fields.Many2many( 'account.account.tag', 'account_tax_account_tag', string='Tags', help="Optional tags you may want to assign for custom reporting") tax_group_id = fields.Many2one('account.tax.group', string="Tax Group", default=_default_tax_group, required=True) _sql_constraints = [ ('name_company_uniq', 'unique(name, company_id, type_tax_use)', 'Tax names must be unique !'), ] @api.multi def unlink(self): company_id = self.env.user.company_id.id ir_values = self.env['ir.values'] supplier_taxes_id = set( ir_values.get_default('product.template', 'supplier_taxes_id', company_id=company_id)) deleted_sup_tax = self.filtered( lambda tax: tax.id in supplier_taxes_id) if deleted_sup_tax: ir_values.sudo().set_default('product.template', "supplier_taxes_id", list(supplier_taxes_id - set(deleted_sup_tax.ids)), for_all_users=True, company_id=company_id) taxes_id = set(self.env['ir.values'].get_default( 'product.template', 'taxes_id', company_id=company_id)) deleted_tax = self.filtered(lambda tax: tax.id in taxes_id) if deleted_tax: ir_values.sudo().set_default('product.template', "taxes_id", list(taxes_id - set(deleted_tax.ids)), for_all_users=True, company_id=company_id) return super(AccountTax, self).unlink() @api.one @api.constrains('children_tax_ids', 'type_tax_use') def _check_children_scope(self): if not all(child.type_tax_use in ('none', self.type_tax_use) for child in self.children_tax_ids): raise UserError( _('The application scope of taxes in a group must be either the same as the group or "None".' )) @api.one def copy(self, default=None): default = dict(default or {}, name=_("%s (Copy)") % self.name) return super(AccountTax, self).copy(default=default) @api.model def name_search(self, name, args=None, operator='ilike', limit=80): """ Returns a list of tupples containing id, name, as internally it is called {def name_get} result format: {[(id, name), (id, name), ...]} """ args = args or [] if operator in expression.NEGATIVE_TERM_OPERATORS: domain = [('description', operator, name), ('name', operator, name)] else: domain = [ '|', ('description', operator, name), ('name', operator, name) ] taxes = self.search(expression.AND([domain, args]), limit=limit) return taxes.name_get() @api.model def search(self, args, offset=0, limit=None, order=None, count=False): context = self._context or {} if context.get('type'): if context.get('type') in ('out_invoice', 'out_refund'): args += [('type_tax_use', '=', 'sale')] elif context.get('type') in ('in_invoice', 'in_refund'): args += [('type_tax_use', '=', 'purchase')] if context.get('journal_id'): journal = self.env['account.journal'].browse( context.get('journal_id')) if journal.type in ('sale', 'purchase'): args += [('type_tax_use', '=', journal.type)] return super(AccountTax, self).search(args, offset, limit, order, count=count) @api.onchange('amount') def onchange_amount(self): if self.amount_type in ( 'percent', 'division') and self.amount != 0.0 and not self.description: self.description = "{0:.4g}%".format(self.amount) @api.onchange('account_id') def onchange_account_id(self): self.refund_account_id = self.account_id @api.onchange('price_include') def onchange_price_include(self): if self.price_include: self.include_base_amount = True def get_grouping_key(self, invoice_tax_val): """ Returns a string that will be used to group account.invoice.tax sharing the same properties""" self.ensure_one() return str(invoice_tax_val['tax_id']) + '-' + str( invoice_tax_val['account_id']) + '-' + str( invoice_tax_val['account_analytic_id']) def _compute_amount(self, base_amount, price_unit, quantity=1.0, product=None, partner=None): """ Returns the amount of a single tax. base_amount is the actual amount on which the tax is applied, which is price_unit * quantity eventually affected by previous taxes (if tax is include_base_amount XOR price_include) """ self.ensure_one() if self.amount_type == 'fixed': # Use copysign to take into account the sign of the base amount which includes the sign # of the quantity and the sign of the price_unit # Amount is the fixed price for the tax, it can be negative # Base amount included the sign of the quantity and the sign of the unit price and when # a product is returned, it can be done either by changing the sign of quantity or by changing the # sign of the price unit. # When the price unit is equal to 0, the sign of the quantity is absorbed in base_amount then # a "else" case is needed. if base_amount: return math.copysign(quantity, base_amount) * self.amount else: return quantity * self.amount if (self.amount_type == 'percent' and not self.price_include) or (self.amount_type == 'division' and self.price_include): return base_amount * self.amount / 100 if self.amount_type == 'percent' and self.price_include: return base_amount - (base_amount / (1 + self.amount / 100)) if self.amount_type == 'division' and not self.price_include: return base_amount / (1 - self.amount / 100) - base_amount @api.v8 def compute_all(self, price_unit, currency=None, quantity=1.0, product=None, partner=None): """ Returns all information required to apply taxes (in self + their children in case of a tax goup). We consider the sequence of the parent for group of taxes. Eg. considering letters as taxes and alphabetic order as sequence : [G, B([A, D, F]), E, C] will be computed as [A, D, F, C, E, G] RETURN: { 'total_excluded': 0.0, # Total without taxes 'total_included': 0.0, # Total with taxes 'taxes': [{ # One dict for each tax in self and their children 'id': int, 'name': str, 'amount': float, 'sequence': int, 'account_id': int, 'refund_account_id': int, 'analytic': boolean, }] } """ if len(self) == 0: company_id = self.env.user.company_id else: company_id = self[0].company_id if not currency: currency = company_id.currency_id taxes = [] # By default, for each tax, tax amount will first be computed # and rounded at the 'Account' decimal precision for each # PO/SO/invoice line and then these rounded amounts will be # summed, leading to the total amount for that tax. But, if the # company has tax_calculation_rounding_method = round_globally, # we still follow the same method, but we use a much larger # precision when we round the tax amount for each line (we use # the 'Account' decimal precision + 5), and that way it's like # rounding after the sum of the tax amounts of each line prec = currency.decimal_places if company_id.tax_calculation_rounding_method == 'round_globally' or not bool( self.env.context.get("round", True)): prec += 5 total_excluded = total_included = base = round(price_unit * quantity, prec) # Sorting key is mandatory in this case. When no key is provided, sorted() will perform a # search. However, the search method is overridden in account.tax in order to add a domain # depending on the context. This domain might filter out some taxes from self, e.g. in the # case of group taxes. for tax in self.sorted(key=lambda r: r.sequence): if tax.amount_type == 'group': ret = tax.children_tax_ids.compute_all(price_unit, currency, quantity, product, partner) total_excluded = ret['total_excluded'] base = ret['base'] total_included = ret['total_included'] tax_amount = total_included - total_excluded taxes += ret['taxes'] continue tax_amount = tax._compute_amount(base, price_unit, quantity, product, partner) if company_id.tax_calculation_rounding_method == 'round_globally' or not bool( self.env.context.get("round", True)): tax_amount = round(tax_amount, prec) else: tax_amount = currency.round(tax_amount) if tax.price_include: total_excluded -= tax_amount base -= tax_amount else: total_included += tax_amount if tax.include_base_amount: base += tax_amount taxes.append({ 'id': tax.id, 'name': tax.with_context(**{ 'lang': partner.lang } if partner else {}).name, 'amount': tax_amount, 'sequence': tax.sequence, 'account_id': tax.account_id.id, 'refund_account_id': tax.refund_account_id.id, 'analytic': tax.analytic, }) return { 'taxes': sorted(taxes, key=lambda k: k['sequence']), 'total_excluded': currency.round(total_excluded) if bool( self.env.context.get("round", True)) else total_excluded, 'total_included': currency.round(total_included) if bool( self.env.context.get("round", True)) else total_included, 'base': base, } @api.v7 def compute_all(self, cr, uid, ids, price_unit, currency_id=None, quantity=1.0, product_id=None, partner_id=None, context=None): currency = currency_id and self.pool.get('res.currency').browse( cr, uid, currency_id, context=context) or None product = product_id and self.pool.get('product.product').browse( cr, uid, product_id, context=context) or None partner = partner_id and self.pool.get('res.partner').browse( cr, uid, partner_id, context=context) or None ids = isinstance(ids, (int, long)) and [ids] or ids recs = self.browse(cr, uid, ids, context=context) return AccountTax.compute_all(recs, price_unit, currency, quantity, product, partner) @api.model def _fix_tax_included_price(self, price, prod_taxes, line_taxes): """Subtract tax amount from price when corresponding "price included" taxes do not apply""" # FIXME get currency in param? incl_tax = prod_taxes.filtered( lambda tax: tax not in line_taxes and tax.price_include) if incl_tax: return incl_tax.compute_all(price)['total_excluded'] return price
class ProductWishlist(models.Model): _name = "product.wishlist" _sql_constrains = [ ("session_or_user_id", "CHECK(session IS NULL != user_id IS NULL)", "Need a session or user for wishlisting a product, but never both."), ("product_unique_session", "UNIQUE(product_tmpl_id, session)", "Duplicated wishlisted product for this session."), ("product_unique_user_id", "UNIQUE(product_tmpl_id, user_id)", "Duplicated wishlisted product for this user."), ] product_tmpl_id = fields.Many2one( comodel_name="product.template", string="Product", required=True, ondelete="cascade", help="Wishlisted product.", ) session = fields.Char( default=lambda self: self._default_session(), help="Website session identifier where this product was wishlisted.", ) user_id = fields.Many2one( comodel_name="res.users", string="User", ondelete="cascade", default=lambda self: self._default_user_id(), help="User that wishlisted this product.", ) website_id = fields.Many2one( comodel_name="website", string="Website", required=True, ondelete="cascade", default=lambda self: self._default_website_id(), help="Website where the user wishlisted the product.", ) @api.model def _default_session(self): """Default to current session.""" return request.session.sid if not self._default_user_id() else False @api.model def _default_user_id(self): """Default to current user if it's not the website user.""" return (self.env.user != self._default_website_id().user_id and self.env.user) @api.model def _default_website_id(self): """Default to current website.""" return self.env["website"].get_current_website() @api.model def _join_current_user_and_session(self): """Assign all dangling session wishlisted products to user.""" user_products = self.search([ ("user_id", "=", self.env.uid), ]).mapped("product_tmpl_id") session_domain = [ ("session", "=", request.session.sid), ("user_id", "=", False), ] # Remove session products already present for the user self.search( session_domain + [("product_tmpl_id", "in", user_products.ids)]) \ .unlink() # Assign the rest to the user self.search(session_domain).write({ "user_id": self.env.uid, "session": False, }) @api.model def _garbage_collector(self): """Remove wishlists for unexisting sessions.""" self.search([ ("session", "not in", root.session_store.list()), ("user_id", "=", False), ]).unlink() @api.model def _clear_methods_cache(self): """Clear cache for wishlist-related methods.""" Website = self.env["website"] Product = self.env["product.template"] Website.wishlist_product_ids.clear_cache(Website) Website.wishlisted_product_template_ids.clear_cache(Website) Product.wishlisted.clear_cache(Product) @api.model def create(self, vals): self._clear_methods_cache() return super(ProductWishlist, self).create(vals) @api.multi def unlink(self): self._clear_methods_cache() return super(ProductWishlist, self).unlink() @api.multi def write(self, vals): self._clear_methods_cache() return super(ProductWishlist, self).write(vals)
class ClouderModel(models.AbstractModel): """ Define the clouder.model abstract object, which is inherited by most objects in clouder. """ _name = 'clouder.model' _description = 'Clouder Model' _autodeploy = True BACKUP_BASE_DIR = '/base-backup/' BACKUP_DATA_DIR = '/opt/backup/' BACKUP_HOME_DIR = '/home/backup/' BACKUP_DATE_FILE = 'backup-date' # We create the name field to avoid warning for the constraints name = fields.Char('Name', required=True) job_ids = fields.One2many( 'clouder.job', 'res_id', domain=lambda self: [('model_name', '=', self._name)], auto_join=True, string='Jobs') @property def version(self): return int(release.version.split('.')[0]) @property def email_sysadmin(self): """ Property returning the sysadmin email of the clouder. """ return self.env.ref('clouder.clouder_settings').email_sysadmin @property def salt_master(self): """ Property returning the salt master of the clouder. """ return self.env.ref('clouder.clouder_settings').salt_master_id @property def user_partner(self): """ Property returning the full name of the server. """ return self.env['res.partner'].search( [('user_ids', 'in', int(self.env.uid))])[0] @property def archive_path(self): """ Property returning the path where are stored the archives in the archive container. """ return '/opt/archives' @property def services_hostpath(self): """ Property returning the path where are stored the archives in the host system. """ return '/opt/services' @property def home_directory(self): """ Property returning the path to the home directory. """ return expanduser("~") @property def now(self): """ Property returning the actual date. """ now = datetime.now() return now.strftime("%Y-%m-%d %H:%M:%S") @property def now_date(self): """ Property returning the actual date. """ now = datetime.now() return now.strftime("%Y-%m-%d") @property def now_hour(self): """ Property returning the actual hour. """ now = datetime.now() return now.strftime("%H-%M") @property def now_hour_regular(self): """ Property returning the actual hour. """ now = datetime.now() return now.strftime("%H:%M:%S") @property def now_bup(self): """ Property returning the actual date, at the bup format. """ now = datetime.now() return now.strftime("%Y-%m-%d-%H%M%S") @api.multi def get_directory_key(self, add_path=None): """ It returns the current working directory for keys :param add_path: (str|iter) String or Iterator of Strings indicating path parts to add to the default remote working path :returns: (str) Key directory on the local """ name = 'key_%s' % self.env.uid return self._get_directory_tmp(name, add_path) @api.multi def get_directory_clouder(self, add_path=None): """ It returns the current clouder directory on the remote :param add_path: (str|iter) String or Iterator of Strings indicating path parts to add to the default remote working path :returns: (str) Clouder directory on the remote """ return self._get_directory_tmp('clouder', add_path) @api.multi def _get_directory_tmp(self, name, add_path=None): """ It returns a directory in tmp for name :param name: (str) Name of the directory in tmp :param add_path: (str|iter) String or Iterator of Strings indicating path parts to add to the default remote working path :returns: (str) Clouder directory on the remote """ if add_path is None: add_path = [] elif not isinstance(add_path, (tuple, list, dict)): add_path = [str(add_path)] return os.path.join('/tmp', str(name), *add_path) @api.multi @contextmanager def _private_env(self): """ It provides an isolated environment/commit Usage: ``with self._private_env() as self`` Yields: Current ``self``, but in a new environment """ # with api.Environment.manage(): # with registry(self.env.cr.dbname).cursor() as cr: # env = api.Environment(cr, self.env.uid, self.env.context) # _logger.debug('Created new env %s for %s', env, self) yield self self.env.cr.commit() # pylint: disable=E8102 # _logger.debug('Cursor %s has been committed', cr) @api.multi @api.constrains('name') def _check_config(self): """ Check that we specified the sysadmin email in configuration before making any action. """ if self._name != 'clouder.config.settings' and not \ self.env.ref('clouder.clouder_settings').email_sysadmin: self.raise_error( "You need to specify the sysadmin email in configuration.", ) @api.multi def check_priority(self): priority = False for record_job in self.job_ids: if record_job.job_id and record_job.job_id.state != 'done' and \ record_job.job_id.priority <= 999: priority = record_job.job_id.priority return priority @api.multi def control_priority(self): return False @api.multi def log(self, message): """ Add a message in the logs specified in context. :param message: The message which will be logged. """ with self._private_env() as self: job_obj = self.env['clouder.job'] now = fields.Datetime.now() message = re.sub(r'$$$\w+$$$', '**********', message) message = filter(lambda x: x in string.printable, message) _logger.info(message) if 'clouder_jobs' not in self.env.context: return for key, job_id in self.env.context['clouder_jobs'].iteritems(): if job_obj.search([('id', '=', job_id)]): job = job_obj.browse(job_id) if job.state == 'started': job.log = '%s%s : %s\n' % ( (job.log or ''), now, message, ) @api.multi def raise_error(self, message, interpolations=None): """ Raises a ClouderError with a translated message :param message: (str) Message including placeholders for string interpolation. Interpolation takes place via the ``%`` operator. :param interpolations: (dict|tuple) Mixed objects to be used for string interpolation after message translation. Dict for named parameters or tuple for positional. Cannot use both. :raises: (clouder.exceptions.ClouderError) """ if interpolations is None: interpolations = () raise ClouderError(self, _(message) % interpolations) @api.multi def do(self, name, action, where=False): where = where or self if 'clouder_jobs' not in self.env.context: self = self.with_context(clouder_jobs={}) job_id = False key = where._name + '_' + str(where.id) if key not in self.env.context['clouder_jobs']: job = self.env['clouder.job'].create({ 'name': name, 'action': action, 'model_name': where._name, 'res_id': where.id, 'state': 'started'}) jobs = self.env.context['clouder_jobs'] jobs[key] = job.id self = self.with_context(clouder_jobs=jobs) job_id = job.id # if 'no_enqueue' not in self.env.context: # self.enqueue(name, action, job_id) # else: getattr(self, 'do_exec')(action, job_id) @api.multi def enqueue(self, name, action, clouder_job_id): return @api.multi def do_exec(self, action, job_id): if job_id: job = self.env['clouder.job'].browse(job_id) job.write({'start_date': self.now}) try: getattr(self, action)() if job_id: job.write({'end_date': self.now, 'state': 'done'}) except: self.log('===================') self.log('FAIL!') self.log('===================') if job_id: job.write({'end_date': self.now, 'state': 'failed'}) raise @api.multi def deploy_frame(self): try: self.deploy() self.deploy_links() except: self.log('===================') self.log('FAIL! Reverting...') self.log('===================') self.purge() raise @api.multi def deploy(self): """ Hook which can be used by inheriting objects to execute actions when we create a new record. """ self.purge() return @api.multi def purge(self): """ Hook which can be used by inheriting objects to execute actions when we delete a record. """ self.purge_links() return @api.multi def deploy_links(self): """ Force deployment of all links linked to a record. """ if hasattr(self, 'link_ids'): for link in self.link_ids: link.deploy_() @api.multi def purge_links(self): """ Force purge of all links linked to a record. """ if hasattr(self, 'link_ids'): for link in self.link_ids: link.purge_() @api.multi def reinstall(self): """" Action which purge then redeploy a record. """ self.do('reinstall', 'deploy_frame') @api.multi def hook_create(self): return @api.model def create(self, vals): """ Override the default create function to create log, call deploy hook, and call unlink if something went wrong. :param vals: The values needed to create the record. """ res = super(ClouderModel, self).create(vals) if self._autodeploy: res.hook_create() res.do('create', 'deploy_frame') return res @api.multi def unlink(self): """ Override the default unlink function to create log and call purge hook. """ for rec in self: if self._autodeploy: try: rec.purge() except: pass res = super(ClouderModel, self).unlink() self.env['clouder.job'].search([ ('res_id', 'in', self.ids), ('model_name', '=', self._name)]).unlink() return res @api.multi def connect(self, server_name='', port=False, username=False): """ Method which can be used to get an ssh connection to execute command. :param host: The host we need to connect. :param port: The port we need to connect. :param username: The username we need to connect. """ server = self if self._name == 'clouder.container': username = False server = self.server_id if not server_name: server_name = server.fulldomain global ssh_connections host_fullname = server_name + \ (port and ('_' + port) or '') + \ (username and ('_' + username) or '') if host_fullname not in ssh_connections\ or not ssh_connections[host_fullname]._transport: ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh_config = paramiko.SSHConfig() user_config_file = os.path.expanduser("~/.ssh/config") if os.path.exists(user_config_file): with open(user_config_file) as f: ssh_config.parse(f) user_config = ssh_config.lookup(server_name) identityfile = None if 'identityfile' in user_config: host = user_config['hostname'] identityfile = user_config['identityfile'] if not username: username = user_config['user'] if not port: port = user_config['port'] if identityfile is None: self.raise_error( 'Clouder does not have a record in the ssh config to ' 'connect to your server.\n' 'Make sure there is a "%s" record in the "~/.ssh/config" ' 'of the Clouder system user.\n' 'To easily add this record, you can click on the ' '"Reinstall" button of the server record, or the ' '"Reset Key" button of the container record you are ' 'trying to access', self.name ) # Security with latest version of Paramiko # https://github.com/clouder-community/clouder/issues/11 if isinstance(identityfile, list): identityfile = identityfile[0] # Probably not useful anymore, to remove later if not isinstance(identityfile, basestring): self.raise_error( 'For an unknown reason, the variable identityfile ' 'in the connect ssh function is invalid. Please report ' 'this message.\n' 'IdentityFile: "%s", Type: "%s"', identityfile, type(identityfile), ) self.log('connect: ssh ' + (username and username + '@' or '') + host + (port and ' -p ' + str(port) or '')) try: ssh.connect( host, port=int(port), username=username, key_filename=os.path.expanduser(identityfile)) except Exception as inst: self.raise_error( 'We were not able to connect to your server. Please ' 'make sure you add the public key in the ' 'authorized_keys file of your root user on your server.\n' 'If you were trying to connect to a container, ' 'a click on the "Reset Key" button on the container ' 'record may resolve the problem.\n' 'Target: "%s" \n' 'Error: "%s"', host, inst, ) ssh_connections[host_fullname] = ssh else: ssh = ssh_connections[host_fullname] return {'ssh': ssh, 'host': server_name, 'server': server} @api.multi def execute(self, cmd, stdin_arg=False, path=False, ssh=False, server_name='', username=False, executor='bash'): """ Method which can be used with an ssh connection to execute command. :param ssh: The connection we need to use. :param cmd: The command we need to execute. :param stdin_arg: The command we need to execute in stdin. :param path: The path where the command need to be executed. """ if self._name == 'clouder.container' \ and self.childs and 'exec' in self.childs: return self.childs['exec'].execute( cmd, stdin_arg=stdin_arg, path=path, ssh=ssh, server_name=server_name, username=username, executor=executor) res_ssh = self.connect(server_name=server_name, username=username) ssh, host = res_ssh['ssh'], res_ssh['host'] if path: self.log('path : ' + path) cmd.insert(0, 'cd ' + path + ';') if self._name == 'clouder.container': cmd_temp = [] first = True for cmd_arg in cmd: cmd_arg = cmd_arg.replace('"', '\\"') if first: cmd_arg = '"' + cmd_arg first = False cmd_temp.append(cmd_arg) cmd = cmd_temp cmd.append('"') cmd.insert(0, self.name + ' ' + executor + ' -c ') if username: cmd.insert(0, '-u ' + username) cmd.insert(0, 'docker exec') self.log('host : ' + host) self.log('command : ' + ' '.join(cmd)) cmd = [c.replace('$$$', '') for c in cmd] transport = ssh.get_transport() channel = transport.open_session() channel.exec_command(' '.join(cmd)) # Pushing additional input if stdin_arg: chnl_stdin = channel.makefile('wb', -1) for arg in stdin_arg: self.log('command : ' + arg) chnl_stdin.write(arg) chnl_stdin.flush() # Reading outputs stdout_read = '' chnl_out = '' chnl_err = '' chnl_buffer_size = 4096 # As long as the command is running while not channel.exit_status_ready(): rl, _, _ = select.select([channel], [], [], 0.0) if len(rl) > 0: # Polling and printing stdout if channel.recv_ready(): chnl_out += channel.recv(chnl_buffer_size) stdout_read += chnl_out chnl_pending_out = chnl_out.split('\n') chnl_out = chnl_pending_out[-1] for output_to_print in chnl_pending_out[:-1]: self.log('stdout : {0}'.format(output_to_print)) # Polling and printing stderr if channel.recv_stderr_ready(): chnl_err += channel.recv_stderr(chnl_buffer_size) chnl_pending_err = chnl_err.split('\n') chnl_err = chnl_pending_err[-1] for err_to_print in chnl_pending_err[:-1]: self.log('stderr : {0}'.format(err_to_print)) # Polling last outputs if any: rl, _, _ = select.select([channel], [], [], 0.0) if len(rl) > 0: # Polling and printing stdout if channel.recv_ready(): chnl_out += channel.recv(chnl_buffer_size) stdout_read += chnl_out chnl_pending_out = chnl_out.split('\n') for output_to_print in chnl_pending_out: # The last one MAY be empty if output_to_print: self.log('stdout : {0}'.format(output_to_print)) # Polling and printing stderr if channel.recv_stderr_ready(): chnl_err += channel.recv_stderr(chnl_buffer_size) chnl_pending_err = chnl_err.split('\n') for err_to_print in chnl_pending_err: # The last one MAY be empty if err_to_print: self.log('stderr : {0}'.format(err_to_print)) return stdout_read @api.multi def get(self, source, destination, ssh=False): """ Method which can be used with an ssh connection to transfer files. :param ssh: The connection we need to use. :param source: The path we need to get the file. :param destination: The path we need to send the file. """ if self._name == 'clouder.container' and self.childs \ and 'exec' in self.childs: return self.childs['exec'].get(source, destination, ssh=ssh) host = self.name if self._name == 'clouder.container': # TODO self.insert(0, 'docker exec ' + self.name) host = self.server_id.name if not ssh: ssh = self.connect(host) sftp = ssh.open_sftp() self.log('get : ' + source + ' to ' + destination) sftp.get(source, destination) sftp.close() @api.multi def send(self, source, destination, ssh=False, username=False): """ Method which can be used with an ssh connection to transfer files. :param ssh: The connection we need to use. :param source: The path we need to get the file. :param destination: The path we need to send the file. """ if self._name == 'clouder.container' and self.childs \ and 'exec' in self.childs: return self.childs['exec'].send( source, destination, ssh=ssh, username=username) res_ssh = self.connect(username=username) ssh, server = res_ssh['ssh'], res_ssh['server'] final_destination = destination tmp_dir = False if self != server: tmp_dir = self.get_directory_clouder(time.time()) server.execute(['mkdir', '-p', tmp_dir]) destination = os.path.join(tmp_dir, 'file') sftp = ssh.open_sftp() self.log('send: "%s" to "%s"' % (source, destination)) sftp.put(source, destination) sftp.close() if tmp_dir: server.execute([ 'cat', destination, '|', 'docker', 'exec', '-i', username and ('-u %s' % username) or '', self.name, 'sh', '-c', "'cat > %s'" % final_destination, ]) # if username: # server.execute([ # 'docker', 'exec', '-i', self.name, # 'chown', username, final_destination]) server.execute(['rm', '-rf', tmp_dir]) @api.multi def send_dir(self, source, destination, ssh=False, username=False): self.log('Send directory ' + source + ' to ' + destination) self.execute(['mkdir', '-p', destination]) for dirpath, dirnames, filenames in os.walk(source): self.log('dirpath ' + str(dirpath)) self.log('dirnames ' + str(dirnames)) self.log('filenames ' + str(filenames)) relpath = os.path.relpath(dirpath, source) for dirname in dirnames: remote_path = os.path.join( destination, os.path.join(relpath, dirname)) self.execute(['mkdir', '-p', remote_path]) for filename in filenames: local_path = os.path.join(dirpath, filename) remote_filepath = os.path.join( destination, os.path.join(relpath, filename)) self.send( local_path, remote_filepath, ssh=ssh, username=username) @api.multi def execute_local(self, cmd, path=False, shell=False): """ Method which can be used to execute command on the local system. :param cmd: The command we need to execute. :param path: The path where the command shall be executed. :param shell: Specify if the command shall be executed in shell mode. """ self.log('command : ' + ' '.join(cmd)) cwd = os.getcwd() if path: self.log('path : ' + path) os.chdir(path) proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=shell) out = '' for line in iter(proc.stdout.readline, b''): out += line self.log("stdout : {0}".format(line)) os.chdir(cwd) return out @api.multi def exist(self, ssh, path): """ Method which use an ssh connection to check is a file exist. :param ssh: The connection we need to use. :param path: The path we need to check. """ sftp = ssh.open_sftp() try: sftp.stat(path) except IOError as e: if e.errno == errno.ENOENT: sftp.close() return False raise else: sftp.close() return True @api.multi def local_file_exist(self, localfile): """ Method which check is a file exist on the local system. :param localfile: The path to the file we need to check. """ return os.path.isfile(localfile) @api.multi def local_dir_exist(self, localdir): """ Method which check is a directory exist on the local system. :param localdir: The path to the dir we need to check. """ return os.path.isdir(localdir) @api.multi def execute_write_file(self, localfile, value, operator='a'): """ Method which write in a file on the local system. :param localfile: The path to the file we need to write. :param value: The value we need to write in the file. """ with open(localfile, operator) as f: f.write(value) def request( self, url, method='get', headers=None, data=None, params=None, files=None): self.log('request "%s" "%s"' % (method, url)) if headers is None: headers = {} else: self.log('request "%s" "%s"' % (method, url)) if data is None: data = {} else: self.log('data %s' % data) if params is None: params = {} else: self.log('params %s' % params) if files is None: files = {} else: self.log('files %s' % files) result = requests.request( method, url, headers=headers, data=data, params=params, files=files, verify=False, ) self.log('status %s %s' % (result.status_code, result.reason)) self.log('result %s' % result.json()) return result
class Session(models.Model): _name = 'openacademy.session' name = fields.Char(required=True) start_date = fields.Date(default=fields.Date.today) duration = fields.Float(digits=(6, 2), help="Duration in days") seats = fields.Integer(string="Number of seats") active = fields.Boolean(default=True) instructor_id = fields.Many2one(comodel_name="res.partner", string="Instructor", required=False, domain=[ '|', ('instructor', '=', True), ('category_id.name', 'ilike', 'Teacher') ]) course_id = fields.Many2one(comodel_name="openacademy.course", string="Course", required=True, ondelete='cascade') attendee_ids = fields.Many2many('res.partner', string="Attendees") taken_seats = fields.Float(string='Taken seats', compute='_taken_seats') end_date = fields.Date(string="End Date", store=True, compute='_get_end_date', inverse='_set_end_date') hours = fields.Float(string="Duration in hours", compute='_get_hours', inverse='_set_hours') attendees_count = fields.Integer(string="Attendees count", compute='_get_attendees_count', store=True) # Save color for kanban view color = fields.Integer() # Workflow like state = fields.Selection([ ('draft', "Draff"), ('confirmed', "Confirmed"), ('done', "Done"), ]) @api.multi def action_draft(self): self.state = 'draft' @api.multi def action_confirm(self): self.state = 'confirmed' @api.multi def action_done(self): self.state = 'done' @api.depends('seats', 'attendee_ids') def _taken_seats(self): for r in self: if not r.seats: r.taken_seats = 0.0 else: r.taken_seats = 100.0 * len(r.attendee_ids) / r.seats @api.depends('start_date', 'duration') def _get_end_date(self): for r in self: if not (r.start_date and r.duration): r.end_date = r.start_date continue """ Add duration to start_date, but: Monday + 5 days = Saturday, so subtract one second to get on Friday instead """ start = fields.Datetime.from_string(r.start_date) duration = timedelta(days=r.duration, seconds=-1) r.end_date = start + duration def _set_end_date(self): for r in self: if not (r.start_date and r.end_date): continue """ Compute the defference between dates, but: Friday - Monday = 4 days, so add one day to get 5 day instead """ start_date = fields.Datetime.from_string(r.start_date) end_date = fields.Datetime.from_string(r.end_date) r.duration = (end_date - start_date).days + 1 @api.depends('duration') def _get_hours(self): """ Tra ve gia tri cho compute field hours :return: """ for r in self: r.hours = r.duration * 24 def _set_hours(self): """ Ham set gia tri cho compute field hours :return: """ for r in self: r.duration = r.hours / 24 @api.depends('attendee_ids') def _get_attendees_count(self): for r in self: r.attendees_count = len(r.attendee_ids) @api.onchange('seats', 'attendee_ids') def _verify_valid_seats(self): """ Dam bao so cho ngoi khong bi vuot qua :return: """ if self.seats < 0: return { 'warning': { 'title': _("Incorrect 'seats' value"), 'message': _("The number of available seats may not be negative"), } } if self.seats < len(self.attendee_ids): return { 'warning': { 'title': _("Too many attendees"), 'message': _("Increase seats or remove excess attendees") } } @api.constrains('instructor_id', 'attendee_ids') def _check_instrcutor_not_in_attendees(self): """ Checks that the instructor is not present in the attendees of his/her own session :return: """ for r in self: if r.instructor_id and r.instructor_id in r.attendee_ids: raise exceptions.ValidationError( _("A session's instrcutor can't be an attendee"))
class Invite(models.TransientModel): """ Wizard to invite partners (or channels) and make them followers. """ _name = 'mail.wizard.invite' _description = 'Invite wizard' @api.model def default_get(self, fields): result = super(Invite, self).default_get(fields) user_name = self.env.user.name_get()[0][1] model = result.get('res_model') res_id = result.get('res_id') if self._context.get('mail_invite_follower_channel_only'): result['send_mail'] = False if 'message' in fields and model and res_id: model_name = self.env['ir.model'].search([ ('model', '=', self.pool[model]._name) ]).name_get()[0][1] document_name = self.env[model].browse(res_id).name_get()[0][1] message = _( '<div><p>Hello,</p><p>%s invited you to follow %s document: %s.<p></div>' ) % (user_name, model_name, document_name) result['message'] = message elif 'message' in fields: result['message'] = _( '<div><p>Hello,</p><p>%s invited you to follow a new document.</p></div>' ) % user_name return result res_model = fields.Char('Related Document Model', required=True, select=1, help='Model of the followed resource') res_id = fields.Integer('Related Document ID', select=1, help='Id of the followed resource') partner_ids = fields.Many2many( 'res.partner', string='Recipients', help= "List of partners that will be added as follower of the current document." ) channel_ids = fields.Many2many( 'mail.channel', string='Channels', help= 'List of channels that will be added as listeners of the current document.' ) message = fields.Html('Message') send_mail = fields.Boolean( 'Send Email', default=True, help= "If checked, the partners will receive an email warning they have been added in the document's followers." ) @api.multi def add_followers(self): email_from = self.env['mail.message']._get_default_from() for wizard in self: Model = self.env[wizard.res_model] document = Model.browse(wizard.res_id) # filter partner_ids to get the new followers, to avoid sending email to already following partners new_partners = wizard.partner_ids - document.message_partner_ids new_channels = wizard.channel_ids - document.message_channel_ids document.message_subscribe(new_partners.ids, new_channels.ids) model_ids = self.env['ir.model'].search([('model', '=', wizard.res_model)]) model_name = model_ids.name_get()[0][1] # send an email if option checked and if a message exists (do not send void emails) if wizard.send_mail and wizard.message and not wizard.message == '<br>': # when deleting the message, cleditor keeps a <br> message = self.env['mail.message'].create({ 'subject': _('Invitation to follow %s: %s') % (model_name, document.name_get()[0][1]), 'body': wizard.message, 'record_name': document.name_get()[0][1], 'email_from': email_from, 'reply_to': email_from, 'model': wizard.res_model, 'res_id': wizard.res_id, 'no_auto_thread': True, }) new_partners.with_context(auto_delete=True)._notify( message, force_send=True, user_signature=True) message.unlink() return {'type': 'ir.actions.act_window_close'}
class ProjectLifecycle(models.Model): _name = 'compassion.project.ile' _description = 'Project lifecycle event' _order = 'date desc, id desc' project_id = fields.Many2one( 'compassion.project', required=True, ondelete='cascade', readonly=True) date = fields.Date(readonly=True, default=fields.Date.today) type = fields.Selection('_get_type', readonly=True) action_plan = fields.Text(readonly=True) # Reactivation ############## icp_improvement_desc = fields.Text(readonly=True) # Suspension ############ suspension_start_date = fields.Date(readonly=True) suspension_end_date = fields.Date(readonly=True) suspension_detail = fields.Char(readonly=True) suspension_reason_ids = fields.Many2many( 'icp.lifecycle.reason', string='Suspension reason', readonly=True) hold_cdsp_funds = fields.Boolean(readonly=True) hold_csp_funds = fields.Boolean(readonly=True) hold_gifts = fields.Boolean(readonly=True) hold_s2b_letters = fields.Boolean(readonly=True) hold_b2s_letters = fields.Boolean(readonly=True) hold_child_updates = fields.Boolean(readonly=True) is_beneficiary_information_updates_withheld = fields.Boolean( readonly=True) extension_1 = fields.Boolean( help='Suspension is extended by 30 days', readonly=True) extension_1_reason = fields.Text(readonly=True) extension_2 = fields.Boolean( help='Suspension is extended by additional 30 days (60 in total)', readonly=True) extension_2_reason = fields.Text(readonly=True) # Transition ############ transition_date = fields.Date(readonly=True) transition_complete = fields.Boolean(readonly=True) details = fields.Text(readonly=True) transition_reason_ids = fields.Many2many( 'icp.lifecycle.reason', string='Transition reason', readonly=True) projected_transition_date = fields.Date(readonly=True) future_involvement_ids = fields.Many2many( 'icp.involvement', string='Future involvement', readonly=True) name = fields.Char(readonly=True) reactivation_date = fields.Date(readonly=True) project_status = fields.Selection('_get_project_status', readonly=True) @api.model def _get_type(self): return [ ('Suspension', 'Suspension'), ('Reactivation', 'Reactivation'), ('Transition', 'Transition'), ] @api.model def _get_project_status(self): return [ ('Active', 'Active'), ('Phase Out', 'Phase Out'), ('Suspended', 'Suspended'), ('Transitioned', 'Transitioned'), ] @api.model def process_commkit(self, commkit_data): project_mapping = mapping.new_onramp_mapping( self._name, self.env, 'new_project_lifecyle') lifecycle_ids = list() for single_data in commkit_data.get('ICPLifecycleEventList', [commkit_data]): vals = project_mapping.get_vals_from_connect(single_data) lifecycle = self.create(vals) lifecycle_ids.append(lifecycle.id) lifecycle.project_id.status_date = fields.Date.today() return lifecycle_ids