class MeliCampaignRecordLine(models.Model): _name = 'meli.campaign.record.line' _description = u'Productos o Ofertar en Campañas' meli_campaign_id = fields.Many2one('meli.campaign.record', u'Registro de Campaña', ondelete="cascade", auto_join=True) product_template_id = fields.Many2one('product.template', u'Plantilla de Producto', ondelete="restrict", auto_join=True) price_unit = fields.Float(u'Precio Unitario', digits=dp.get_precision('Product Price')) list_price = fields.Float(u'Precio Unitario(Tarifa)', digits=dp.get_precision('Product Price')) meli_price = fields.Float(u'Precio Unitario(MELI)', digits=dp.get_precision('Product Price')) declared_free_shipping = fields.Boolean(u'Envio Gratis?') declared_oro_premium_full = fields.Boolean(u'Premium?') declared_stock = fields.Float(u'Stock Declarado', digits=dp.get_precision('Product Unit of Measure')) review_reasons_ids = fields.One2many('meli.campaign.record.review.reason', 'meli_campaign_line_id', u'Razones de Revision', readonly=True, copy=False, auto_join=True) state = fields.Selection([ ('draft','Borrador'), ('pending_approval', 'Enviado a Meli/Esperando Aprobacion'), ('published','Publicado en MELI'), ('approved','Aprobado en MELI'), ('done','Campaña Terminada'), ('rejected','Cancelado'), ], string=u'Estado', default = u'draft') @api.multi def _prepare_vals_to_publish(self): post_data = { 'deal_price': self.meli_price, 'regular_price': self.price_unit, 'declared_free_shipping': self.declared_free_shipping, 'declared_oro_premium_full': self.declared_oro_premium_full, } return post_data @api.multi def _prepare_vals_to_update_from_meli(self, response_json, product_template): vals = { 'meli_price': response_json.get('deal_price'), 'list_price': response_json.get('deal_price'), 'price_unit': response_json.get('regular_price'), 'declared_free_shipping': response_json.get('declared_free_shipping'), 'declared_oro_premium_full': response_json.get('declared_oro_premium_full'), 'state': response_json.get('status'), } return vals @api.multi def action_publish_to_meli(self): meli_util_model = self.env['meli.util'] company = self.env.user.company_id meli = meli_util_model.get_new_instance() params = {'access_token': meli.access_token} messages = [] msj = "" for line in self: post_data = line._prepare_vals_to_publish() post_data['item_id'] = line.product_template_id.meli_id url = "/users/%s/deals/%s/proposed_items" % (company.mercadolibre_seller_id, line.meli_campaign_id.campaign_id.meli_id) response = meli.post(url, post_data, params) rjson = response.json() if rjson.get('error'): msj = rjson.get('message') or "Error Publicando Oferta" messages.append("%s. Producto ID: %s" % (msj, line.product_template_id.meli_id)) continue vals_line = { 'state': rjson.get('status'), } review_reason = [] for review in rjson.get('review_reasons', []): review_reason.append((0, 0, { 'reason_type': review.get('reason_type', ''), 'reason_requisite': (review.get('requisite') or {}).get('name', ''), 'message_key': review.get('message_key', ''), })) if review_reason: vals_line['review_reasons_ids'] = [(5, 0)] + review_reason if vals_line: line.write(vals_line) return messages @api.multi def action_unpublish_to_meli(self): meli_util_model = self.env['meli.util'] company = self.env.user.company_id meli = meli_util_model.get_new_instance() params = {'access_token': meli.access_token} messages = [] #los productos publicados en meli enviar a cancelarlos #los que no han sido publicados, solo cambiarle el estado lines_unpublish = self.filtered(lambda x: x.state in ('pending_approval', 'published', 'done')) lines_cancel = self - lines_unpublish if lines_cancel: lines_cancel.write({'state': 'rejected'}) for line in lines_unpublish: url = "/users/%s/deals/%s/proposed_items/%s" % (company.mercadolibre_seller_id, line.meli_campaign_id.campaign_id.meli_id, line.product_template_id.meli_id) response = meli.delete(url, params) rjson = response.json() if rjson.get('error'): msj = rjson.get('message') or "Error Eliminando Oferta" messages.append("%s. Producto ID: %s" % (msj, line.product_template_id.meli_id)) continue vals_line = { 'state': rjson.get('status'), } if vals_line: line.write(vals_line) return messages @api.multi def action_update_to_meli(self): meli_util_model = self.env['meli.util'] company = self.env.user.company_id meli = meli_util_model.get_new_instance() params = {'access_token': meli.access_token} messages = [] for line in self: post_data = line._prepare_vals_to_publish() url = "/users/%s/deals/%s/proposed_items/%s" % (company.mercadolibre_seller_id, line.meli_campaign_id.campaign_id.meli_id, line.product_template_id.meli_id) response = meli.put(url, post_data, params) rjson = response.json() if rjson.get('error'): msj = rjson.get('message') or "Error Actualizando Oferta" messages.append("%s. Producto ID: %s" % (msj, line.product_template_id.meli_id)) continue return messages @api.multi def name_get(self): res = [] for element in self: name = u"%s %s" % (element.meli_campaign_id.display_name or '', element.product_template_id.display_name) res.append((element.id, name)) return res
class PurchaseReport(models.Model): _inherit = "purchase.report" fiscal_operation_id = fields.Many2one( comodel_name="l10n_br_fiscal.operation", string="Fiscal Operation", readonly=True, ) fiscal_operation_line_id = fields.Many2one( comodel_name="l10n_br_fiscal.operation.line", string="Fiscal Operation Line", readonly=True, ) cfop_id = fields.Many2one( comodel_name="l10n_br_fiscal.cfop", string="CFOP", ) fiscal_type = fields.Selection(selection=PRODUCT_FISCAL_TYPE, string="Tipo Fiscal") cest_id = fields.Many2one( comodel_name="l10n_br_fiscal.cest", string="CEST", ) ncm_id = fields.Many2one(comodel_name="l10n_br_fiscal.ncm", string="NCM") nbm_id = fields.Many2one(comodel_name="l10n_br_fiscal.nbm", string="NBM") icms_value = fields.Float( string="ICMS Value", digits=dp.get_precision("Account"), ) icmsst_value = fields.Float( string="ICMS ST Value", digits=dp.get_precision("Account"), ) ipi_value = fields.Float( string="IPI Value", digits=dp.get_precision("Account"), ) pis_value = fields.Float( string="PIS Value", digits=dp.get_precision("Account"), ) cofins_value = fields.Float( string="COFINS Value", digits=dp.get_precision("Account"), ) ii_value = fields.Float( string="II Value", digits=dp.get_precision("Account"), ) freight_value = fields.Float( string="Freight Value", digits=dp.get_precision("Account"), ) insurance_value = fields.Float( string="Insurance Value", digits=dp.get_precision("Account"), ) other_value = fields.Float( string="Other Value", digits=dp.get_precision("Account"), ) total_with_taxes = fields.Float( string="Total with Taxes", digits=dp.get_precision("Account"), ) def _select(self): select_str = super()._select() select_str += """ , l.fiscal_operation_id as fiscal_operation_id , l.fiscal_operation_line_id as fiscal_operation_line_id , l.cfop_id , l.fiscal_type , l.ncm_id , l.nbm_id , l.cest_id , SUM(l.icms_value) as icms_value , SUM(l.icmsst_value) as icmsst_value , SUM(l.ipi_value) as ipi_value , SUM(l.pis_value) as pis_value , SUM(l.cofins_value) as cofins_value , SUM(l.ii_value) as ii_value , SUM(l.freight_value) as freight_value , SUM(l.insurance_value) as insurance_value , SUM(l.other_value) as other_value , SUM(l.price_unit / COALESCE(NULLIF(cr.rate, 0), 1.0) * l.product_qty )::decimal(16,2) + SUM(CASE WHEN l.ipi_value IS NULL THEN 0.00 ELSE l.ipi_value END) + SUM(CASE WHEN l.icmsst_value IS NULL THEN 0.00 ELSE l.icmsst_value END) + SUM(CASE WHEN l.freight_value IS NULL THEN 0.00 ELSE l.freight_value END) + SUM(CASE WHEN l.insurance_value IS NULL THEN 0.00 ELSE l.insurance_value END) + SUM(CASE WHEN l.other_value IS NULL THEN 0.00 ELSE l.other_value END) as total_with_taxes """ return select_str def _group_by(self): group_by_str = super()._group_by() group_by_str += """ , l.fiscal_operation_id , l.fiscal_operation_line_id , l.cfop_id , l.fiscal_type , l.ncm_id , l.nbm_id , l.cest_id """ return group_by_str
class ResPartner(models.Model): _name = 'res.partner' _inherit = 'res.partner' _description = 'Partner' @api.multi def _credit_debit_get(self): tables, where_clause, where_params = self.env['account.move.line']._query_get() where_params = [tuple(self.ids)] + where_params self._cr.execute("""SELECT l.partner_id, act.type, SUM(l.amount_residual) FROM account_move_line l LEFT JOIN account_account a ON (l.account_id=a.id) LEFT JOIN account_account_type act ON (a.user_type_id=act.id) WHERE act.type IN ('receivable','payable') AND l.partner_id IN %s AND l.reconciled IS FALSE """ + where_clause + """ GROUP BY l.partner_id, act.type """, where_params) for pid, type, val in self._cr.fetchall(): partner = self.browse(pid) if type == 'receivable': partner.credit = val elif type == 'payable': partner.debit = -val @api.multi def _asset_difference_search(self, account_type, operator, operand): if operator not in ('<', '=', '>', '>=', '<='): return [] if type(operand) not in (float, int): return [] sign = 1 if account_type == 'payable': sign = -1 res = self._cr.execute(''' SELECT partner.id FROM res_partner partner LEFT JOIN account_move_line aml ON aml.partner_id = partner.id RIGHT JOIN account_account acc ON aml.account_id = acc.id WHERE acc.internal_type = %s AND NOT acc.deprecated GROUP BY partner.id HAVING %s * COALESCE(SUM(aml.amount_residual), 0) ''' + operator + ''' %s''', (account_type, sign, operand)) res = self._cr.fetchall() if not res: return [('id', '=', '0')] return [('id', 'in', map(itemgetter(0), res))] @api.model def _credit_search(self, operator, operand): return self._asset_difference_search('receivable', operator, operand) @api.model def _debit_search(self, operator, operand): return self._asset_difference_search('payable', operator, operand) @api.multi def _invoice_total(self): account_invoice_report = self.env['account.invoice.report'] if not self.ids: self.total_invoiced = 0.0 return True user_currency_id = self.env.user.company_id.currency_id.id all_partners_and_children = {} all_partner_ids = [] for partner in self: # price_total is in the company currency all_partners_and_children[partner] = self.search([('id', 'child_of', partner.id)]).ids all_partner_ids += all_partners_and_children[partner] # searching account.invoice.report via the orm is comparatively expensive # (generates queries "id in []" forcing to build the full table). # In simple cases where all invoices are in the same currency than the user's company # access directly these elements # generate where clause to include multicompany rules where_query = account_invoice_report._where_calc([ ('partner_id', 'in', all_partner_ids), ('state', 'not in', ['draft', 'cancel']), ('company_id', '=', self.env.user.company_id.id), ('type', 'in', ('out_invoice', 'out_refund')) ]) account_invoice_report._apply_ir_rules(where_query, 'read') from_clause, where_clause, where_clause_params = where_query.get_sql() # price_total is in the company currency query = """ SELECT SUM(price_total) as total, partner_id FROM account_invoice_report account_invoice_report WHERE %s GROUP BY partner_id """ % where_clause self.env.cr.execute(query, where_clause_params) price_totals = self.env.cr.dictfetchall() for partner, child_ids in all_partners_and_children.items(): partner.total_invoiced = sum(price['total'] for price in price_totals if price['partner_id'] in child_ids) @api.multi def _journal_item_count(self): for partner in self: partner.journal_item_count = self.env['account.move.line'].search_count([('partner_id', '=', partner.id)]) partner.contracts_count = self.env['account.analytic.account'].search_count([('partner_id', '=', partner.id)]) def get_followup_lines_domain(self, date, overdue_only=False, only_unblocked=False): domain = [('reconciled', '=', False), ('account_id.deprecated', '=', False), ('account_id.internal_type', '=', 'receivable'), '|', ('debit', '!=', 0), ('credit', '!=', 0), ('company_id', '=', self.env.user.company_id.id)] if only_unblocked: domain += [('blocked', '=', False)] if self.ids: if 'exclude_given_ids' in self._context: domain += [('partner_id', 'not in', self.ids)] else: domain += [('partner_id', 'in', self.ids)] #adding the overdue lines overdue_domain = ['|', '&', ('date_maturity', '!=', False), ('date_maturity', '<', date), '&', ('date_maturity', '=', False), ('date', '<', date)] if overdue_only: domain += overdue_domain return domain @api.multi def _compute_issued_total(self): """ Returns the issued total as will be displayed on partner view """ today = fields.Date.context_today(self) for partner in self: domain = partner.get_followup_lines_domain(today, overdue_only=True) issued_total = 0 for aml in self.env['account.move.line'].search(domain): issued_total += aml.amount_residual partner.issued_total = issued_total @api.one def _compute_has_unreconciled_entries(self): # Avoid useless work if has_unreconciled_entries is not relevant for this partner if not self.active or not self.is_company and self.parent_id: return self.env.cr.execute( """ SELECT 1 FROM( SELECT p.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) RIGHT JOIN res_partner p ON (l.partner_id = p.id) WHERE p.id = %s AND EXISTS ( SELECT 1 FROM account_move_line l WHERE l.account_id = a.id AND l.partner_id = p.id AND l.amount_residual > 0 ) AND EXISTS ( SELECT 1 FROM account_move_line l WHERE l.account_id = a.id AND l.partner_id = p.id AND l.amount_residual < 0 ) GROUP BY p.last_time_entries_checked ) as s WHERE (last_time_entries_checked IS NULL OR max_date > last_time_entries_checked) """, (self.id,)) self.has_unreconciled_entries = self.env.cr.rowcount == 1 @api.multi def mark_as_reconciled(self): self.env['account.partial.reconcile'].check_access_rights('write') return self.sudo().write({'last_time_entries_checked': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)}) @api.one def _get_company_currency(self): if self.company_id: self.currency_id = self.sudo().company_id.currency_id else: self.currency_id = self.env.user.company_id.currency_id credit = fields.Monetary(compute='_credit_debit_get', search=_credit_search, string='Total Receivable', help="Total amount this customer owes you.") debit = fields.Monetary(compute='_credit_debit_get', search=_debit_search, string='Total Payable', help="Total amount you have to pay to this vendor.") debit_limit = fields.Monetary('Payable Limit') total_invoiced = fields.Monetary(compute='_invoice_total', string="Total Invoiced", groups='account.group_account_invoice') currency_id = fields.Many2one('res.currency', compute='_get_company_currency', readonly=True, string="Currency", help='Utility field to express amount currency') contracts_count = fields.Integer(compute='_journal_item_count', string="Contracts", type='integer') journal_item_count = fields.Integer(compute='_journal_item_count', string="Journal Items", type="integer") issued_total = fields.Monetary(compute='_compute_issued_total', string="Journal Items") property_account_payable_id = fields.Many2one('account.account', company_dependent=True, string="Account Payable", oldname="property_account_payable", domain="[('internal_type', '=', 'payable'), ('deprecated', '=', False)]", help="This account will be used instead of the default one as the payable account for the current partner", required=True) property_account_receivable_id = fields.Many2one('account.account', company_dependent=True, string="Account Receivable", oldname="property_account_receivable", domain="[('internal_type', '=', 'receivable'), ('deprecated', '=', False)]", help="This account will be used instead of the default one as the receivable account for the current partner", required=True) property_account_position_id = fields.Many2one('account.fiscal.position', company_dependent=True, string="Fiscal Position", help="The fiscal position will determine taxes and accounts used for the partner.", oldname="property_account_position") property_payment_term_id = fields.Many2one('account.payment.term', company_dependent=True, string ='Customer Payment Term', help="This payment term will be used instead of the default one for sale orders and customer invoices", oldname="property_payment_term") property_supplier_payment_term_id = fields.Many2one('account.payment.term', company_dependent=True, string ='Vendor Payment Term', help="This payment term will be used instead of the default one for purchase orders and vendor bills", oldname="property_supplier_payment_term") ref_company_ids = fields.One2many('res.company', 'partner_id', string='Companies that refers to partner', oldname="ref_companies") has_unreconciled_entries = fields.Boolean(compute='_compute_has_unreconciled_entries', help="The partner has at least one unreconciled debit and credit since last time the invoices & payments matching was performed.") last_time_entries_checked = fields.Datetime(oldname='last_reconciliation_date', string='Latest Invoices & Payments Matching Date', readonly=True, copy=False, help='Last time the invoices & payments matching was performed for this partner. ' 'It is set either if there\'s not at least an unreconciled debit and an unreconciled credit ' 'or if you click the "Done" button.') invoice_ids = fields.One2many('account.invoice', 'partner_id', string='Invoices', readonly=True, copy=False) contract_ids = fields.One2many('account.analytic.account', 'partner_id', string='Contracts', readonly=True) bank_account_count = fields.Integer(compute='_compute_bank_count', string="Bank") trust = fields.Selection([('good', 'Good Debtor'), ('normal', 'Normal Debtor'), ('bad', 'Bad Debtor')], string='Degree of trust you have in this debtor', default='normal', company_dependent=True) invoice_warn = fields.Selection(WARNING_MESSAGE, 'Invoice', help=WARNING_HELP, required=True, default="no-message") invoice_warn_msg = fields.Text('Message for Invoice') @api.multi def _compute_bank_count(self): bank_data = self.env['res.partner.bank'].read_group([('partner_id', 'in', self.ids)], ['partner_id'], ['partner_id']) mapped_data = dict([(bank['partner_id'][0], bank['partner_id_count']) for bank in bank_data]) for partner in self: partner.bank_account_count = mapped_data.get(partner.id, 0) def _find_accounting_partner(self, partner): ''' Find the partner for which the accounting entries will be created ''' return partner.commercial_partner_id @api.model def _commercial_fields(self): return super(ResPartner, self)._commercial_fields() + \ ['debit_limit', 'property_account_payable_id', 'property_account_receivable_id', 'property_account_position_id', 'property_payment_term_id', 'property_supplier_payment_term_id', 'last_time_entries_checked']
class StockImportWzd(models.TransientModel): _name = 'stock.import.wzd' name = fields.Char('Importation name', required=True) picking_id = fields.Many2one('stock.picking') file = fields.Binary(string='File', required=True) filename = fields.Char(string='Filename') def _parse_row_vals(self, row, idx): res = { 'barcode': row[1], 'articulo': row[2], 'cantidad': row[3], } return res def _create_xml_id(self, xml_id, res_id, model): virual_module_name = 'PT' if model == 'product.template' else 'PP' self._cr.execute( 'INSERT INTO ir_model_data (module, name, res_id, model) \ VALUES (%s, %s, %s, %s)', (virual_module_name, xml_id, res_id, model)) def _get_product_id(self, barcode): domain = [('barcode', '=', barcode)] product_id = self.env['product.product'].search(domain, limit=1) if not product_id: ref = barcode[2:11] print("No encuentro el codigo de barras: {}".format(barcode)) domain = [('default_code', '=', ref)] product_id = self.env['product.product'].search(domain, limit=1) if not product_id: print("No encuentro la referencia: {}".format(ref)) return product_id def _get_move_line(self, vals, picking_id): product_id = self._get_product_id(str(int(vals['barcode']))) if not product_id: picking_id.message_post( body='No se ha encontrado nada {}'.format(vals)) else: move_vals = { 'product_id': product_id.id, 'name': vals['articulo'], 'picking_id': self.picking_id.id, 'location_id': self.picking_id.location_id.id, 'location_dest_id': self.picking_id.location_dest_id.id, 'product_uom_qty': vals['cantidad'], } move = self.env['stock.move'].new(move_vals) move.onchange_product_id() move_values = move._convert_to_write(move._cache) move.create(move_values) return product_id def import_products(self): self.ensure_one() _logger.info(_('STARTING PRODUCT IMPORTATION')) # get the first worksheet file = base64.b64decode(self.file) book = xlrd.open_workbook(file_contents=file) sh = book.sheet_by_index(0) created_product_ids = [] idx = 1 self.picking_id.origin = 'IMPORTACIÓN' for nline in range(idx, sh.nrows): idx += 1 row = sh.row_values(nline) row_vals = self._parse_row_vals(row, idx) if row_vals['barcode']: product_id = self._get_move_line(row_vals, self.picking_id) if product_id: _logger.info( _('Importado movimeinto: %s (%s / %s)') % (product_id.display_name, idx, sh.nrows - 1)) created_product_ids.append(product_id.id) else: _logger.info( _('Error en línea %s (%s / %s)') % (idx, idx, sh.nrows - 1)) return self.action_view_products(created_product_ids) def action_view_products(self, product_ids): self.ensure_one() action = self.env.ref('product.product_normal_action_sell').read()[0] action['domain'] = [('id', 'in', product_ids)] return action
class GitRepository(models.Model): _name = 'project.git.repository' def _default_secret(self): alphabet = string.ascii_letters + string.digits secret = ''.join( random.SystemRandom().choice(alphabet) for i in range(30) ) return secret name = fields.Char( string='Name', size=256, ) repo_name = fields.Char(related="name") uuid = fields.Char( string='UUID', size=256, index=True, ) full_name = fields.Char( string='Full Name', size=256 ) odoo_uuid = fields.Char( string='UUID', size=256, default=lambda *a: uuid.uuid4(), index=True, ) avatar = fields.Char( string='Avatar', compute='_compute_avatar', ) url = fields.Char( string='URL', default='#' ) project_id = fields.Many2one( comodel_name='project.project', string='Project', required=True, index=True, ) branch_ids = fields.One2many( comodel_name='project.git.branch', string='Branches', inverse_name='repository_id' ) branch_count = fields.Integer( compute="_compute_branch_count" ) user_id = fields.Many2one( comodel_name='project.git.user', string='Owner', ondelete='cascade', index=True, ) type = fields.Selection( selection=[], string='Type', index=True, ) webhook_url = fields.Char( string='Webhook Url', compute='_compute_webhook_url', ) secret = fields.Char( default=lambda s: s._default_secret() ) use_secret = fields.Boolean( compute='_compute_use_secret' ) image_type = fields.Char( string='Type', compute='_compute_image_type' ) @api.multi @api.depends("branch_ids") def _compute_branch_count(self): for rec in self: rec.branch_count = len(rec.branch_ids) @api.multi @api.depends('type') def _compute_avatar(self): get_avatar(self, 'repository') @api.multi @api.depends('type') def _compute_use_secret(self): secret_types = self._secret_visible_for_types() for rec in self: rec.use_secret = rec.type in secret_types def _secret_visible_for_types(self): return [] @api.multi @api.depends('type') def _compute_image_type(self): get_image_type(self) @api.onchange('project_id', 'type') def _onchange_name_components(self): if not self.project_id or self.type: return self.name = '%s - %s' % ( self.project_id.key, self._get_selection_label(self.type) ) def _get_selection_label(self, type): for item in self._fields['type'].selection: if item[0] == type: return item[1] return '' @api.multi @api.depends('odoo_uuid', 'type') def _compute_webhook_url(self): base_url = self.env['ir.config_parameter']\ .sudo()\ .get_param('web.base.url') for record in self: if record.type: record.webhook_url = urljoin( base_url, record.type, 'payload', record.odoo_uuid )
class RoyaltyFeeLinestoInvoice(models.TransientModel): _name = 'royalty.fee.lines.to.invoice' royalty_fee_lines_id = fields.Many2one('royalty.fee.to.invoice') month = fields.Many2one('royalty.fee.month', string="Month") royalty_fee = fields.Float(string="Royalty Fee")
class EventEvent(models.Model): """Event""" _name = 'event.event' _description = 'Event' _inherit = ['mail.thread', 'mail.activity.mixin'] _order = 'date_begin' def _get_default_stage_id(self): event_stages = self.env['event.stage'].search([]) return event_stages[0] if event_stages else False def _default_description(self): return self.env['ir.ui.view']._render_template('event.event_default_descripton') name = fields.Char(string='Event', translate=True, required=True) note = fields.Html(string='Note', store=True, compute="_compute_note", readonly=False) description = fields.Html(string='Description', translate=html_translate, sanitize_attributes=False, sanitize_form=False, default=_default_description) active = fields.Boolean(default=True) user_id = fields.Many2one( 'res.users', string='Responsible', tracking=True, default=lambda self: self.env.user) company_id = fields.Many2one( 'res.company', string='Company', change_default=True, default=lambda self: self.env.company, required=False) organizer_id = fields.Many2one( 'res.partner', string='Organizer', tracking=True, default=lambda self: self.env.company.partner_id, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]") event_type_id = fields.Many2one('event.type', string='Template', ondelete='set null') event_mail_ids = fields.One2many( 'event.mail', 'event_id', string='Mail Schedule', copy=True, compute='_compute_event_mail_ids', readonly=False, store=True) tag_ids = fields.Many2many( 'event.tag', string="Tags", readonly=False, store=True, compute="_compute_tag_ids") # Kanban fields kanban_state = fields.Selection([('normal', 'In Progress'), ('done', 'Done'), ('blocked', 'Blocked')], default='normal', copy=False) kanban_state_label = fields.Char( string='Kanban State Label', compute='_compute_kanban_state_label', store=True, tracking=True) stage_id = fields.Many2one( 'event.stage', ondelete='restrict', default=_get_default_stage_id, group_expand='_read_group_stage_ids', tracking=True, copy=False) legend_blocked = fields.Char(related='stage_id.legend_blocked', string='Kanban Blocked Explanation', readonly=True) legend_done = fields.Char(related='stage_id.legend_done', string='Kanban Valid Explanation', readonly=True) legend_normal = fields.Char(related='stage_id.legend_normal', string='Kanban Ongoing Explanation', readonly=True) # Seats and computation seats_max = fields.Integer( string='Maximum Attendees Number', compute='_compute_seats_max', readonly=False, store=True, help="For each event you can define a maximum registration of seats(number of attendees), above this numbers the registrations are not accepted.") seats_limited = fields.Boolean('Maximum Attendees', required=True, compute='_compute_seats_limited', readonly=False, store=True) seats_reserved = fields.Integer( string='Reserved Seats', store=True, readonly=True, compute='_compute_seats') seats_available = fields.Integer( string='Available Seats', store=True, readonly=True, compute='_compute_seats') seats_unconfirmed = fields.Integer( string='Unconfirmed Seat Reservations', store=True, readonly=True, compute='_compute_seats') seats_used = fields.Integer( string='Number of Participants', store=True, readonly=True, compute='_compute_seats') seats_expected = fields.Integer( string='Number of Expected Attendees', compute_sudo=True, readonly=True, compute='_compute_seats_expected') # Registration fields auto_confirm = fields.Boolean( string='Autoconfirmation', compute='_compute_auto_confirm', readonly=False, store=True, help='Autoconfirm Registrations. Registrations will automatically be confirmed upon creation.') registration_ids = fields.One2many('event.registration', 'event_id', string='Attendees') event_ticket_ids = fields.One2many( 'event.event.ticket', 'event_id', string='Event Ticket', copy=True, compute='_compute_event_ticket_ids', readonly=False, store=True) event_registrations_started = fields.Boolean( 'Registrations started', compute='_compute_event_registrations_started', help="registrations have started if the current datetime is after the earliest starting date of tickets." ) event_registrations_open = fields.Boolean( 'Registration open', compute='_compute_event_registrations_open', compute_sudo=True, help="Registrations are open if:\n" "- the event is not ended\n" "- there are seats available on event\n" "- the tickets are sellable (if ticketing is used)") event_registrations_sold_out = fields.Boolean( 'Sold Out', compute='_compute_event_registrations_sold_out', compute_sudo=True, help='The event is sold out if no more seats are available on event. If ticketing is used and all tickets are sold out, the event will be sold out.') start_sale_datetime = fields.Datetime( 'Start sale date', compute='_compute_start_sale_date', help='If ticketing is used, contains the earliest starting sale date of tickets.') # Date fields date_tz = fields.Selection( _tz_get, string='Timezone', required=True, compute='_compute_date_tz', readonly=False, store=True) date_begin = fields.Datetime(string='Start Date', required=True, tracking=True) date_end = fields.Datetime(string='End Date', required=True, tracking=True) date_begin_located = fields.Char(string='Start Date Located', compute='_compute_date_begin_tz') date_end_located = fields.Char(string='End Date Located', compute='_compute_date_end_tz') is_ongoing = fields.Boolean('Is Ongoing', compute='_compute_is_ongoing', search='_search_is_ongoing') is_one_day = fields.Boolean(compute='_compute_field_is_one_day') # Location and communication address_id = fields.Many2one( 'res.partner', string='Venue', default=lambda self: self.env.company.partner_id.id, tracking=True, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]") country_id = fields.Many2one( 'res.country', 'Country', related='address_id.country_id', readonly=False, store=True) # badge fields badge_front = fields.Html(string='Badge Front') badge_back = fields.Html(string='Badge Back') badge_innerleft = fields.Html(string='Badge Inner Left') badge_innerright = fields.Html(string='Badge Inner Right') event_logo = fields.Html(string='Event Logo') @api.depends('stage_id', 'kanban_state') def _compute_kanban_state_label(self): for event in self: if event.kanban_state == 'normal': event.kanban_state_label = event.stage_id.legend_normal elif event.kanban_state == 'blocked': event.kanban_state_label = event.stage_id.legend_blocked else: event.kanban_state_label = event.stage_id.legend_done @api.depends('seats_max', 'registration_ids.state') def _compute_seats(self): """ Determine reserved, available, reserved but unconfirmed and used seats. """ # initialize fields to 0 for event in self: event.seats_unconfirmed = event.seats_reserved = event.seats_used = event.seats_available = 0 # aggregate registrations by event and by state state_field = { 'draft': 'seats_unconfirmed', 'open': 'seats_reserved', 'done': 'seats_used', } base_vals = dict((fname, 0) for fname in state_field.values()) results = dict((event_id, dict(base_vals)) for event_id in self.ids) if self.ids: query = """ SELECT event_id, state, count(event_id) FROM event_registration WHERE event_id IN %s AND state IN ('draft', 'open', 'done') GROUP BY event_id, state """ self.env['event.registration'].flush(['event_id', 'state']) self._cr.execute(query, (tuple(self.ids),)) res = self._cr.fetchall() for event_id, state, num in res: results[event_id][state_field[state]] += num # compute seats_available for event in self: event.update(results.get(event._origin.id or event.id, base_vals)) if event.seats_max > 0: event.seats_available = event.seats_max - (event.seats_reserved + event.seats_used) @api.depends('seats_unconfirmed', 'seats_reserved', 'seats_used') def _compute_seats_expected(self): for event in self: event.seats_expected = event.seats_unconfirmed + event.seats_reserved + event.seats_used @api.depends('date_tz', 'start_sale_datetime') def _compute_event_registrations_started(self): for event in self: event = event._set_tz_context() if event.start_sale_datetime: current_datetime = fields.Datetime.context_timestamp(event, fields.Datetime.now()) start_sale_datetime = fields.Datetime.context_timestamp(event, event.start_sale_datetime) event.event_registrations_started = (current_datetime >= start_sale_datetime) else: event.event_registrations_started = True @api.depends('date_tz', 'event_registrations_started', 'date_end', 'seats_available', 'seats_limited', 'event_ticket_ids.sale_available') def _compute_event_registrations_open(self): """ Compute whether people may take registrations for this event * event.date_end -> if event is done, registrations are not open anymore; * event.start_sale_datetime -> lowest start date of tickets (if any; start_sale_datetime is False if no ticket are defined, see _compute_start_sale_date); * any ticket is available for sale (seats available) if any; * seats are unlimited or seats are available; """ for event in self: event = event._set_tz_context() current_datetime = fields.Datetime.context_timestamp(event, fields.Datetime.now()) date_end_tz = event.date_end.astimezone(pytz.timezone(event.date_tz or 'UTC')) if event.date_end else False event.event_registrations_open = event.event_registrations_started and \ (date_end_tz >= current_datetime if date_end_tz else True) and \ (not event.seats_limited or event.seats_available) and \ (not event.event_ticket_ids or any(ticket.sale_available for ticket in event.event_ticket_ids)) @api.depends('event_ticket_ids.start_sale_datetime') def _compute_start_sale_date(self): """ Compute the start sale date of an event. Currently lowest starting sale date of tickets if they are used, of False. """ for event in self: start_dates = [ticket.start_sale_datetime for ticket in event.event_ticket_ids if not ticket.is_expired] event.start_sale_datetime = min(start_dates) if start_dates and all(start_dates) else False @api.depends('event_ticket_ids.sale_available') def _compute_event_registrations_sold_out(self): for event in self: if event.seats_limited and not event.seats_available: event.event_registrations_sold_out = True elif event.event_ticket_ids: event.event_registrations_sold_out = not any( ticket.seats_available > 0 if ticket.seats_limited else True for ticket in event.event_ticket_ids ) else: event.event_registrations_sold_out = False @api.depends('date_tz', 'date_begin') def _compute_date_begin_tz(self): for event in self: if event.date_begin: event.date_begin_located = format_datetime( self.env, event.date_begin, tz=event.date_tz, dt_format='medium') else: event.date_begin_located = False @api.depends('date_tz', 'date_end') def _compute_date_end_tz(self): for event in self: if event.date_end: event.date_end_located = format_datetime( self.env, event.date_end, tz=event.date_tz, dt_format='medium') else: event.date_end_located = False @api.depends('date_begin', 'date_end') def _compute_is_ongoing(self): now = fields.Datetime.now() for event in self: event.is_ongoing = event.date_begin <= now < event.date_end def _search_is_ongoing(self, operator, value): if operator not in ['=', '!=']: raise ValueError(_('This operator is not supported')) if not isinstance(value, bool): raise ValueError(_('Value should be True or False (not %s)'), value) now = fields.Datetime.now() if (operator == '=' and value) or (operator == '!=' and not value): domain = [('date_begin', '<=', now), ('date_end', '>', now)] else: domain = ['|', ('date_begin', '>', now), ('date_end', '<=', now)] event_ids = self.env['event.event']._search(domain) return [('id', 'in', event_ids)] @api.depends('date_begin', 'date_end', 'date_tz') def _compute_field_is_one_day(self): for event in self: # Need to localize because it could begin late and finish early in # another timezone event = event._set_tz_context() begin_tz = fields.Datetime.context_timestamp(event, event.date_begin) end_tz = fields.Datetime.context_timestamp(event, event.date_end) event.is_one_day = (begin_tz.date() == end_tz.date()) @api.depends('event_type_id') def _compute_date_tz(self): for event in self: if event.event_type_id.default_timezone: event.date_tz = event.event_type_id.default_timezone if not event.date_tz: event.date_tz = self.env.user.tz or 'UTC' # seats @api.depends('event_type_id') def _compute_seats_max(self): """ Update event configuration from its event type. Depends are set only on event_type_id itself, not its sub fields. Purpose is to emulate an onchange: if event type is changed, update event configuration. Changing event type content itself should not trigger this method. """ for event in self: if not event.event_type_id: event.seats_max = event.seats_max or 0 else: event.seats_max = event.event_type_id.seats_max or 0 @api.depends('event_type_id') def _compute_seats_limited(self): """ Update event configuration from its event type. Depends are set only on event_type_id itself, not its sub fields. Purpose is to emulate an onchange: if event type is changed, update event configuration. Changing event type content itself should not trigger this method. """ for event in self: if event.event_type_id.has_seats_limitation != event.seats_limited: event.seats_limited = event.event_type_id.has_seats_limitation if not event.seats_limited: event.seats_limited = False @api.depends('event_type_id') def _compute_auto_confirm(self): """ Update event configuration from its event type. Depends are set only on event_type_id itself, not its sub fields. Purpose is to emulate an onchange: if event type is changed, update event configuration. Changing event type content itself should not trigger this method. """ for event in self: event.auto_confirm = event.event_type_id.auto_confirm @api.depends('event_type_id') def _compute_event_mail_ids(self): """ Update event configuration from its event type. Depends are set only on event_type_id itself, not its sub fields. Purpose is to emulate an onchange: if event type is changed, update event configuration. Changing event type content itself should not trigger this method. When synchronizing mails: * lines that are not sent and have no registrations linked are remove; * type lines are added; """ for event in self: if not event.event_type_id and not event.event_mail_ids: event.event_mail_ids = False continue # lines to keep: those with already sent emails or registrations mails_to_remove = event.event_mail_ids.filtered( lambda mail: not(mail._origin.mail_done) and not(mail._origin.mail_registration_ids) ) command = [Command.unlink(mail.id) for mail in mails_to_remove] if event.event_type_id.event_type_mail_ids: command += [ Command.create({ attribute_name: line[attribute_name] if not isinstance(line[attribute_name], models.BaseModel) else line[attribute_name].id for attribute_name in self.env['event.type.mail']._get_event_mail_fields_whitelist() }) for line in event.event_type_id.event_type_mail_ids ] if command: event.event_mail_ids = command @api.depends('event_type_id') def _compute_tag_ids(self): """ Update event configuration from its event type. Depends are set only on event_type_id itself, not its sub fields. Purpose is to emulate an onchange: if event type is changed, update event configuration. Changing event type content itself should not trigger this method. """ for event in self: if not event.tag_ids and event.event_type_id.tag_ids: event.tag_ids = event.event_type_id.tag_ids @api.depends('event_type_id') def _compute_event_ticket_ids(self): """ Update event configuration from its event type. Depends are set only on event_type_id itself, not its sub fields. Purpose is to emulate an onchange: if event type is changed, update event configuration. Changing event type content itself should not trigger this method. When synchronizing tickets: * lines that have no registrations linked are remove; * type lines are added; Note that updating event_ticket_ids triggers _compute_start_sale_date (start_sale_datetime computation) so ensure result to avoid cache miss. """ for event in self: if not event.event_type_id and not event.event_ticket_ids: event.event_ticket_ids = False continue # lines to keep: those with existing registrations tickets_to_remove = event.event_ticket_ids.filtered(lambda ticket: not ticket._origin.registration_ids) command = [Command.unlink(ticket.id) for ticket in tickets_to_remove] if event.event_type_id.event_type_ticket_ids: command += [ Command.create({ attribute_name: line[attribute_name] if not isinstance(line[attribute_name], models.BaseModel) else line[attribute_name].id for attribute_name in self.env['event.type.ticket']._get_event_ticket_fields_whitelist() }) for line in event.event_type_id.event_type_ticket_ids ] event.event_ticket_ids = command @api.depends('event_type_id') def _compute_note(self): for event in self: if event.event_type_id and not is_html_empty(event.event_type_id.note): event.note = event.event_type_id.note @api.constrains('seats_max', 'seats_available', 'seats_limited') def _check_seats_limit(self): if any(event.seats_limited and event.seats_max and event.seats_available < 0 for event in self): raise ValidationError(_('No more available seats.')) @api.constrains('date_begin', 'date_end') def _check_closing_date(self): for event in self: if event.date_end < event.date_begin: raise ValidationError(_('The closing date cannot be earlier than the beginning date.')) @api.model def _read_group_stage_ids(self, stages, domain, order): return self.env['event.stage'].search([]) @api.model_create_multi def create(self, vals_list): for vals in vals_list: # Temporary fix for ``seats_limited`` and ``date_tz`` required fields vals.update(self._sync_required_computed(vals)) events = super(EventEvent, self).create(vals_list) for res in events: if res.organizer_id: res.message_subscribe([res.organizer_id.id]) events.flush() return events def write(self, vals): if 'stage_id' in vals and 'kanban_state' not in vals: # reset kanban state when changing stage vals['kanban_state'] = 'normal' res = super(EventEvent, self).write(vals) if vals.get('organizer_id'): self.message_subscribe([vals['organizer_id']]) return res @api.returns('self', lambda value: value.id) def copy(self, default=None): self.ensure_one() default = dict(default or {}, name=_("%s (copy)") % (self.name)) return super(EventEvent, self).copy(default) @api.model def _get_mail_message_access(self, res_ids, operation, model_name=None): if ( operation == 'create' and self.env.user.has_group('event.group_event_registration_desk') and (not model_name or model_name == 'event.event') ): # allow the registration desk users to post messages on Event # can not be done with "_mail_post_access" otherwise public user will be # able to post on published Event (see website_event) return 'read' return super(EventEvent, self)._get_mail_message_access(res_ids, operation, model_name) def _sync_required_computed(self, values): # TODO: See if the change to seats_limited affects this ? """ Call compute fields in cache to find missing values for required fields (seats_limited and date_tz) in case they are not given in values """ missing_fields = list(set(['seats_limited', 'date_tz']).difference(set(values.keys()))) if missing_fields and values: cache_event = self.new(values) cache_event._compute_seats_limited() cache_event._compute_date_tz() return dict((fname, cache_event[fname]) for fname in missing_fields) else: return {} def _set_tz_context(self): self.ensure_one() return self.with_context(tz=self.date_tz or 'UTC') def action_set_done(self): """ Action which will move the events into the first next (by sequence) stage defined as "Ended" (if they are not already in an ended stage) """ first_ended_stage = self.env['event.stage'].search([('pipe_end', '=', True)], order='sequence') if first_ended_stage: self.write({'stage_id': first_ended_stage[0].id}) def mail_attendees(self, template_id, force_send=False, filter_func=lambda self: self.state != 'cancel'): for event in self: for attendee in event.registration_ids.filtered(filter_func): self.env['mail.template'].browse(template_id).send_mail(attendee.id, force_send=force_send) def _get_ics_file(self): """ Returns iCalendar file for the event invitation. :returns a dict of .ics file content for each event """ result = {} if not vobject: return result for event in self: cal = vobject.iCalendar() cal_event = cal.add('vevent') cal_event.add('created').value = fields.Datetime.now().replace(tzinfo=pytz.timezone('UTC')) cal_event.add('dtstart').value = fields.Datetime.from_string(event.date_begin).replace(tzinfo=pytz.timezone('UTC')) cal_event.add('dtend').value = fields.Datetime.from_string(event.date_end).replace(tzinfo=pytz.timezone('UTC')) cal_event.add('summary').value = event.name if event.address_id: cal_event.add('location').value = event.sudo().address_id.contact_address result[event.id] = cal.serialize().encode('utf-8') return result @api.autovacuum def _gc_mark_events_done(self): """ move every ended events in the next 'ended stage' """ ended_events = self.env['event.event'].search([ ('date_end', '<', fields.Datetime.now()), ('stage_id.pipe_end', '=', False), ]) if ended_events: ended_events.action_set_done()
class ProductTemplate(models.Model): _inherit = 'product.template' service_policy = fields.Selection([ ('ordered_timesheet', 'Ordered quantities'), ('delivered_timesheet', 'Timesheets on tasks'), ('delivered_manual', 'Milestones (manually set quantities on order)') ], string="Invoice based on", compute='_compute_service_policy', inverse='_inverse_service_policy') service_type = fields.Selection(selection_add=[ ('timesheet', 'Timesheets on project (one fare per SO/Project)'), ]) service_tracking = fields.Selection( [ ('no', 'Don\'t create task'), ('task_global_project', 'Create a task in an existing project'), ('task_new_project', 'Create a task in a new project'), ('project_only', 'Create a new project but no task'), ], string="Service Tracking", default="no", help= "On Sales order confirmation, this product can generate a project and/or task. From those, you can track the service you are selling." ) project_id = fields.Many2one( 'project.project', 'Project', company_dependent=True, domain=[('sale_line_id', '=', False)], help= 'Select a non billable project on which tasks can be created. This setting must be set for each company.' ) @api.depends('invoice_policy', 'service_type') def _compute_service_policy(self): for product in self: policy = None if product.invoice_policy == 'delivery': policy = 'delivered_manual' if product.service_type == 'manual' else 'delivered_timesheet' elif product.invoice_policy == 'order' and product.service_type == 'timesheet': policy = 'ordered_timesheet' product.service_policy = policy def _inverse_service_policy(self): for product in self: policy = product.service_policy if not policy and not product.invoice_policy == 'delivery': product.invoice_policy = 'order' product.service_type = 'manual' elif policy == 'ordered_timesheet': product.invoice_policy = 'order' product.service_type = 'timesheet' else: product.invoice_policy = 'delivery' product.service_type = 'manual' if policy == 'delivered_manual' else 'timesheet' @api.onchange('service_tracking') def _onchange_service_tracking(self): if self.service_tracking != 'task_global_project': self.project_id = False @api.onchange('type') def _onchange_type(self): if self.type == 'service': self.invoice_policy = 'order' self.service_type = 'timesheet' elif self.type == 'consu': if not self.invoice_policy or self.service_policy == 'ordered_timesheet': self.invoice_policy = 'order' self.service_type = 'manual'
class HrResignation(models.Model): _name = 'hr.resignation' _inherit = 'mail.thread' _rec_name = 'employee_id' def _get_employee_id(self): # assigning the related employee of the logged in user employee_rec = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) return employee_rec.id name = fields.Char(string='Order Reference', required=True, copy=False, readonly=True, index=True, default=lambda self: _('New')) employee_id = fields.Many2one('hr.employee', string="Employee", default=_get_employee_id, help='Name of the employee for whom the request is creating') department_id = fields.Many2one('hr.department', string="Department", related='employee_id.department_id', help='Department of the employee') joined_date = fields.Date(string="Join Date", required=True, help='Joining date of the employee') expected_revealing_date = fields.Date(string="Relieving Date", required=True, help='Date on which he is revealing from the company') stores_clearance_notes = fields.Char(string="Store Clearance Notes", help='Any Store Related Notes') resign_confirm_date = fields.Date(string="Resign confirm date", help='Date on which the request is confirmed') approved_revealing_date = fields.Date(string="Approved Date", help='The date approved for the relieving') reason = fields.Text(string="Reason", help='Specify reason for leaving the company') notice_period = fields.Char(string="Notice Period", compute='_notice_period') state = fields.Selection([('draft', 'Draft'), ('linemanager', 'Line Manager Clearance'), ('storesclearance', 'Stores Clearance'), ('hrapproval', 'HR Approval'), ('directorapproval', 'Director Approval'), ('cancel', 'Cancel'), ('rejected', 'Rejected'), ('approved', 'Approved')], string='Status', default='draft') @api.onchange('employee_id') def set_join_date(self): self.joined_date = self.employee_id.joining_date if self.employee_id.joining_date else '' @api.model def create(self, vals): # assigning the sequence for the record if vals.get('name', _('New')) == _('New'): vals['name'] = self.env['ir.sequence'].next_by_code('hr.resignation') or _('New') res = super(HrResignation, self).create(vals) return res @api.constrains('employee_id') def check_employee(self): # Checking whether the user is creating leave request of his/her own for rec in self: if not self.env.user.has_group('hr.group_hr_user'): if rec.employee_id.user_id.id and rec.employee_id.user_id.id != self.env.uid: raise ValidationError(_('You cannot create request for other employees')) @api.onchange('employee_id') @api.depends('employee_id') def check_request_existence(self): # Check whether any resignation request already exists for rec in self: if rec.employee_id: resignation_request = self.env['hr.resignation'].search([('employee_id', '=', rec.employee_id.id), ('state', 'in', ['confirm', 'approved'])]) if resignation_request: raise ValidationError(_('There is a resignation request in confirmed or' ' approved state for this employee')) @api.multi def _notice_period(self): # calculating the notice period for the employee for rec in self: if rec.approved_revealing_date and rec.resign_confirm_date: approved_date = datetime.strptime(str(rec.approved_revealing_date), date_format) confirmed_date = datetime.strptime(str(rec.resign_confirm_date), date_format) notice_period = approved_date - confirmed_date rec.notice_period = notice_period.days @api.constrains('joined_date', 'expected_revealing_date') def _check_dates(self): # validating the entered dates for rec in self: resignation_request = self.env['hr.resignation'].search([('employee_id', '=', rec.employee_id.id), ('state', 'in', ['confirm', 'approved'])]) if resignation_request: raise ValidationError(_('There is a resignation request in confirmed or' ' approved state for this employee')) if rec.joined_date >= rec.expected_revealing_date: raise ValidationError(_('Relieving date must be anterior to joining date')) @api.multi def confirm_resignation(self): for rec in self: rec.state = 'linemanager' @api.multi def linemanager_approve(self): for rec in self: rec.state = 'storesclearance' @api.multi def storesclearance_approve(self): for rec in self: rec.state = 'hrapproval' @api.multi def hrapproval(self): for rec in self: rec.state = 'directorapproval' @api.multi def directorapproval(self): for rec in self: rec.state = 'approved' rec.resign_confirm_date = str(datetime.now()) @api.multi def cancel_resignation(self): for rec in self: rec.state = 'cancel' @api.multi def reject_resignation(self): for rec in self: rec.state = 'rejected' @api.multi def approve_resignation(self): for rec in self: if not rec.approved_revealing_date: raise ValidationError(_('Enter Approved Relieving Date')) if rec.approved_revealing_date and rec.resign_confirm_date: if rec.approved_revealing_date <= rec.resign_confirm_date: raise ValidationError(_('Approved relieving date must be anterior to confirmed date')) rec.state = 'approved' @api.multi def update_employee_status(self): resignation = self.env['hr.resignation'].search([('state', '=', 'approved')]) for rec in resignation: if rec.approved_revealing_date <= fields.Date.today() and rec.employee_id.active: rec.employee_id.active = False rec.employee_id.resign_date = rec.approved_revealing_date
class account_period(models.Model): _name = "account.period" _description = "Account period" _order = "date_start, special desc" name = fields.Char('Period Name', required=True) code = fields.Char('Code', size=12) special = fields.Boolean('Opening/Closing Period',help="These periods can overlap.") date_start = fields.Date('Start of Period', required=True) date_stop = fields.Date('End of Period', required=True) fiscalyear_id = fields.Many2one('account.fiscalyear', 'Fiscal Year', required=True, select=True) state = fields.Selection([('draft','Open'), ('done','Closed')], 'Status', readonly=True, copy=False, default='draft', help='When monthly periods are created. The status is \'Draft\'. At the end of monthly period it is in \'Done\' status.') company_id = fields.Many2one('res.company',related='fiscalyear_id.company_id',string='Company', store=True, readonly=True) _sql_constraints = [ ('name_company_uniq', 'unique(name, company_id)', 'The name of the period must be unique per company!'), ] def _check_duration(self): obj_period = self.browse() if self.date_stop < self.date_start: return False return True """ states={'done':[('readonly',True)]}, def _check_year_limit(self,cr,uid,ids,context=None): for obj_period in self.browse(cr, uid, ids, context=context): if obj_period.special: continue if obj_period.fiscalyear_id.date_stop < obj_period.date_stop or \ obj_period.fiscalyear_id.date_stop < obj_period.date_start or \ obj_period.fiscalyear_id.date_start > obj_period.date_start or \ obj_period.fiscalyear_id.date_start > obj_period.date_stop: return False pids = self.search(cr, uid, [('date_stop','>=',obj_period.date_start),('date_start','<=',obj_period.date_stop),('special','=',False),('id','<>',obj_period.id)]) for period in self.browse(cr, uid, pids): if period.fiscalyear_id.company_id.id==obj_period.fiscalyear_id.company_id.id: return False return True """ """ _constraints = [ (_check_duration, 'Error!\nThe duration of the Period(s) is/are invalid.', ['date_stop']), (_check_year_limit, 'Error!\nThe period is invalid. Either some periods are overlapping or the period\'s dates are not matching the scope of the fiscal year.', ['date_stop']) ] """ """ @api.returns('self') def next(self, cr, uid, period, step, context=None): ids = self.search(cr, uid, [('date_start','>',period.date_start)]) if len(ids)>=step: return ids[step-1] return False @api.returns('self') def find(self, cr, uid, dt=None, context=None): if context is None: context = {} if not dt: dt = fields.date.context_today(self, cr, uid, context=context) args = [('date_start', '<=' ,dt), ('date_stop', '>=', dt)] if context.get('company_id', False): args.append(('company_id', '=', context['company_id'])) else: company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id args.append(('company_id', '=', company_id)) result = [] if context.get('account_period_prefer_normal', True): # look for non-special periods first, and fallback to all if no result is found result = self.search(cr, uid, args + [('special', '=', False)], context=context) if not result: result = self.search(cr, uid, args, context=context) if not result: model, action_id = self.pool['ir.model.data'].get_object_reference(cr, uid, 'account', 'action_account_period') msg = _('There is no period defined for this date: %s.\nPlease go to Configuration/Periods.') % dt raise openerp.exceptions.RedirectWarning(msg, action_id, _('Go to the configuration panel')) return result def action_draft(self, cr, uid, ids, context=None): mode = 'draft' for period in self.browse(cr, uid, ids): if period.fiscalyear_id.state == 'done': raise osv.except_osv(_('Warning!'), _('You can not re-open a period which belongs to closed fiscal year')) cr.execute('update account_journal_period set state=%s where period_id in %s', (mode, tuple(ids),)) cr.execute('update account_period set state=%s where id in %s', (mode, tuple(ids),)) self.invalidate_cache(cr, uid, context=context) return True def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100): if args is None: args = [] if operator in expression.NEGATIVE_TERM_OPERATORS: domain = [('code', operator, name), ('name', operator, name)] else: domain = ['|', ('code', operator, name), ('name', operator, name)] ids = self.search(cr, user, expression.AND([domain, args]), limit=limit, context=context) return self.name_get(cr, user, ids, context=context) def write(self, cr, uid, ids, vals, context=None): if 'company_id' in vals: move_lines = self.pool.get('account.move.line').search(cr, uid, [('period_id', 'in', ids)]) if move_lines: raise osv.except_osv(_('Warning!'), _('This journal already contains items for this period, therefore you cannot modify its company field.')) return super(account_period, self).write(cr, uid, ids, vals, context=context) def build_ctx_periods(self, cr, uid, period_from_id, period_to_id): if period_from_id == period_to_id: return [period_from_id] period_from = self.browse(cr, uid, period_from_id) period_date_start = period_from.date_start company1_id = period_from.company_id.id period_to = self.browse(cr, uid, period_to_id) period_date_stop = period_to.date_stop company2_id = period_to.company_id.id if company1_id != company2_id: raise osv.except_osv(_('Error!'), _('You should choose the periods that belong to the same company.')) if period_date_start > period_date_stop: raise osv.except_osv(_('Error!'), _('Start period should precede then end period.')) # /!\ We do not include a criterion on the company_id field below, to allow producing consolidated reports # on multiple companies. It will only work when start/end periods are selected and no fiscal year is chosen. #for period from = january, we want to exclude the opening period (but it has same date_from, so we have to check if period_from is special or not to include that clause or not in the search). if period_from.special: return self.search(cr, uid, [('date_start', '>=', period_date_start), ('date_stop', '<=', period_date_stop)]) return self.search(cr, uid, [('date_start', '>=', period_date_start), ('date_stop', '<=', period_date_stop), ('special', '=', False)]) """
class StockProductionLot(models.Model): _name = "stock.production.lot" _inherit = ["stock.production.lot", "mail.thread"] _mail_post_access = "read" locked = fields.Boolean(string="Blocked", tracking=True) product_id = fields.Many2one(track_visibility="onchange") def _get_product_locked(self, product): """Should create locked? (including categories and parents) @param product: browse-record for product.product @return True when the category of the product or one of the parents demand new lots to be locked """ _locked = product.categ_id.lot_default_locked categ = product.categ_id.parent_id while categ and not _locked: _locked = categ.lot_default_locked categ = categ.parent_id return _locked @api.onchange("product_id") def _onchange_product_id(self): """Instruct the client to lock/unlock a lot on product change""" self.locked = self._get_product_locked(self.product_id) @api.constrains("locked") def _check_lock_unlock(self): if not self.user_has_groups( "xtendoo_stock_lock_lot.group_lock_lot" ) and not self.env.context.get("bypass_lock_permission_check"): raise exceptions.AccessError( _("You are not allowed to block/unblock Serial Numbers/Lots") ) reserved_quants = self.env["stock.quant"].search( [("lot_id", "in", self.ids), ("reserved_quantity", "!=", 0.0)] ) if reserved_quants: raise exceptions.ValidationError( _( "You are not allowed to block/unblock, there are" " reserved quantities for these Serial Numbers/Lots" ) ) @api.model def create(self, vals): """Force the locking/unlocking, ignoring the value of 'locked'.""" product = self.env["product.product"].browse( vals.get( "product_id", # Web quick-create provide in context self.env.context.get( "product_id", self.env.context.get("default_product_id", False) ), ) ) vals["locked"] = self._get_product_locked(product) lot = super( StockProductionLot, self.with_context(bypass_lock_permission_check=True) ).create(vals) return self.browse(lot.id) # for cleaning context def write(self, values): """"Lock the lot if changing the product and locking is required""" if "product_id" in values: product = self.env["product.product"].browse(values["product_id"]) values["locked"] = self._get_product_locked(product) return super(StockProductionLot, self).write(values) def _track_subtype(self, init_values): self.ensure_one() if "locked" in init_values: if self.locked: return self.env.ref("xtendoo_stock_lock_lot.mt_lock_lot") return self.env.ref("xtendoo_stock_lock_lot.mt_unlock_lot") return super()._track_subtype(init_values) def action_lock_stock_lot(self): for product in self: product.locked = True def action_unlock_stock_lot(self): for product in self: product.locked = False
class Group(models.Model): _name = 'htc.group' _inherit = 'mail.thread' name = fields.Char("Group Name", required=True) code = fields.Char("Group Code", required=True) site_id = fields.Many2one("htc.site", string="Site", requierd=True) group_sensor_ids = fields.One2many( 'htc.group_sensors', "group_id", string="Group Sensors") # sensor_ids = fields.Many2many( # 'htc.sensor', # string='Sensors', # store=False, # readonly=False, # compute="get_sensors") # compute="get_sensors" @api.onchange('code') def do_stuff(self): if self.code: self.name = str(self.name).upper() else: self.name = "" @api.multi def name_get(self): result = [] for record in self: name = record.name result.append((record.id, name)) return result # @api.multi # def write(self, values): # today = datetime.datetime.today() # addlist = [] # senids = [] # value_sensor_ids = [] # operation_count = 0 # value_sensor_ids_len = 0 # value_sensors = values.get('sensor_ids') # if value_sensors: # value_sensor_ids = value_sensors[0][2] # value_sensor_ids_len = len(value_sensor_ids) # orgin_record = self.env['htc.group_sensors'].search([("group_id", "=", # self.id)]) # for ol_rec in orgin_record: # senids.append(ol_rec.sensor_id.id) # # searching delete records and delete them ### # if value_sensor_ids and len(value_sensor_ids) > 0: # diff_sen_ids = set(value_sensor_ids).symmetric_difference( # set(senids)) # for diff_sen_id in diff_sen_ids: # record_sensor = self.env['htc.group_sensors'].search([ # ("group_id", "=", self.id), ('sensor_id', '=', diff_sen_id) # ]) # if record_sensor: # # remove delete record # # record_sensor.unlink() # else: # if orgin_record: # orgin_record.unlink() # if value_sensor_ids and not len(value_sensors) > 1: # for s_id in value_sensor_ids: # db_record = self.env['htc.group_sensors'].search([ # ('sensor_id', '=', s_id), ('group_id', '=', self.id) # ]) # if not db_record: # addlist.append({ # 'group_id': self.id, # 'sensor_id': s_id, # 'in_status': 5, # 'out_status': 10, # 'enable_alert': False, # 'inform_limit_count': 0, # 'process_count': 1, # 'current_counter_date': today # }) # else: # operation_count += 1 # else: # if value_sensors: # for i in range(len(value_sensors)): # if i == 0: # continue # db_record = self.env['htc.group_sensors'].search([ # '&', ('sensor_id', '=', value_sensors[i][1]), # ('group_id', '=', self.id) # ]) # temp = { # 'id': db_record.id, # 'group_id': self.id, # 'sensor_id': value_sensors[i][1], # 'in_status': 5, # 'out_status': 10, # 'enable_alert': False, # 'inform_limit_count': 0, # 'process_count': 1, # 'current_counter_date': today # } # value = value_sensors[i][2] # # Merging dictionary ### # temp.update(value) # if temp.get('in_status') == 5: # temp['out_status'] = 10 # else: # temp['out_status'] = 5 # if not db_record: # addlist.append(temp) # else: # operation_count += 1 # if value.get('in_status'): # if value.get('in_status') == 5: # value['out_status'] = 10 # else: # value['out_status'] = 5 # db_record.write(value) # # remove duplicate items # # addlist = [dict(t) for t in {tuple(d.items()) for d in addlist}] # self.env['htc.group_sensors'].create(addlist) # is_equal_operation_count = False # if addlist: # is_equal_operation_count = (operation_count + # len(addlist)) == value_sensor_ids_len # # Need to add extra more ### # else: # is_equal_operation_count = operation_count == value_sensor_ids_len # if not is_equal_operation_count: # need_to_adds = [] # addlist_sensor_id = map(lambda x: x.get('sensor_id'), addlist) # need_to_add_ids = set(value_sensor_ids).symmetric_difference( # set(addlist_sensor_id)) # for s_id in need_to_add_ids: # db_record = self.env['htc.group_sensors'].search([ # ('sensor_id', '=', s_id), ('group_id', '=', self.id) # ]) # if not db_record: # temp = { # 'id': db_record.id, # 'group_id': self.id, # 'sensor_id': s_id, # 'in_status': 5, # 'out_status': 10, # 'enable_alert': False, # 'inform_limit_count': 0, # 'process_count': 1, # 'current_counter_date': today # } # need_to_adds.append(temp) # self.env['htc.group_sensors'].create(need_to_adds) # @api.one # @api.depends('group_sensor_ids') # def get_sensors(self): mlist = [] list = [] global_id = None param_name = str(self.env.uid) + '_' + 'group_id_global' self.env['ir.config_parameter'].sudo().set_param(param_name, global_id) record_set = None for gs in self.mapped('group_sensor_ids'): global_id = gs.group_id.id list.append(gs.sensor_id.id) sensors = self.env['htc.sensor'].search([("id", "in", list)]) sensors = self.env['htc.sensor'].browse(list) for gs in self.mapped('group_sensor_ids'): for sensor in sensors: if sensor.id == gs.sensor_id.id: sensor.in_status = gs.in_status sensor.gs_id = gs.id sensor.enable_alert = gs.enable_alert sensor.inform_limit_count = gs.inform_limit_count mlist.append(sensor) self.env['ir.config_parameter'].sudo().set_param(param_name, global_id) self.sensor_ids = sensors
class IrSequenceFolio(models.Model): _name = "ir.sequence.folio" _description = "Ir Sequence Folios" @api.depends("caf_file") def _compute_data(self): for Caf in self: if Caf: Caf.load_caf() name = fields.Char(string="Name", readonly=True, compute="_compute_filename") filename = fields.Char(string="File Name") caf_file = fields.Binary( string="CAF XML File", filters="*.xml", required=True, help="Upload the CAF XML File in this holder") issued_date = fields.Date(string="Issued Date", compute="_compute_data", store=True) expiration_date = fields.Date( string="Expiration Date", compute="_compute_data", store=True) sii_document_class = fields.Integer( string="SII Document Class", compute="_compute_data", store=True) start_nm = fields.Integer( string="Start Number", help="CAF Starts from this number", compute="_compute_data", store=True) final_nm = fields.Integer( string="End Number", help="CAF Ends to this number", compute="_compute_data", store=True) status = fields.Selection( [("draft", "Draft"), ("in_use", "In Use"), ("spent", "Spent")], string="Status", default="draft", help="""Draft: means it has not been used yet. You must put it in used in order to make it available for use. Spent: means that the number interval has been exhausted.""") rut_n = fields.Char(string="VAT", compute="_compute_data", store=True) company_id = fields.Many2one( "res.company", string="Company", required=False, default=lambda self: self.env.user.company_id) sequence_id = fields.Many2one("ir.sequence", string="Sequence") use_level = fields.Float(string="Use Level", compute="_compute_used_level") # TODO: hacerlo configurable nivel_minimo = fields.Integer( string="Nivel Mínimo de Folios", default=5) _sql_constraints = [("filename_unique", "unique(filename)", "Error! Filename Already Exist!")] @api.onchange("caf_file") def load_caf(self, flags=False): if not self.caf_file or not self.sequence_id: return result = self.decode_caf()["AUTORIZACION"]["CAF"]["DA"] self.start_nm = result["RNG"]["D"] self.final_nm = result["RNG"]["H"] self.sii_document_class = result["TD"] self.issued_date = result["FA"] if self.sequence_id.sii_document_class_id.sii_code not in [34, 52]: self.expiration_date = date( int(result["FA"][:4]), int(result["FA"][5:7]), int(result["FA"][8:10]) ) + relativedelta(months=6) self.rut_n = "CL" + result["RE"].replace("-", "") if self.rut_n != self.company_id.vat.replace("L0", "L"): raise UserError( _("Company vat %s should be the same that assigned company's " "vat: %s!") % (self.rut_n, self.company_id.vat) ) elif self.sii_document_class != \ self.sequence_id.sii_document_class_id.sii_code: raise UserError( _("""SII Document Type for this CAF is %s and selected sequence associated document class is %s. This values should be equal for DTE Invoicing to work properly!""") % (self.sii_document_class, self.sequence_id.sii_document_class_id.sii_code) ) if flags: return True self.status = "in_use" self._compute_used_level() def _compute_used_level(self): for r in self: if r.status not in ["draft"]: folio = r.sequence_id.number_next_actual try: if folio > r.final_nm: r.use_level = 100 elif folio < r.start_nm: r.use_level = 0 else: r.use_level = 100.0 * ( (int(folio) - r.start_nm) / float(r.final_nm - r.start_nm + 1) ) except ZeroDivisionError: r.use_level = 0 else: r.use_level = 0 def _compute_filename(self): for r in self: r.name = r.filename def decode_caf(self): post = base64.b64decode(self.caf_file).decode("ISO-8859-1") post = xmltodict.parse(post.replace('<?xml version="1.0"?>', "", 1)) return post def check_nivel(self, folio): if not folio: return "" diff = self.final_nm - int(folio) if diff <= self.nivel_minimo: return "Nivel bajo de CAF para %s, quedan %s folios" % ( self.sequence_id.sii_document_class_id.name, diff, ) return ""
class MailTemplate(models.Model): "Templates for sending email" _name = "mail.template" _description = 'Email Templates' _order = 'name' @api.model def default_get(self, fields): res = super(MailTemplate, self).default_get(fields) if res.get('model'): res['model_id'] = self.env['ir.model']._get(res.pop('model')).id return res name = fields.Char('Name') model_id = fields.Many2one('ir.model', 'Applies to', help="The type of document this template can be used with") model = fields.Char('Related Document Model', related='model_id.model', index=True, store=True, readonly=True) lang = fields.Char('Language', help="Optional translation language (ISO code) to select when sending out an email. " "If not set, the english version will be used. " "This should usually be a placeholder expression " "that provides the appropriate language, e.g. " "${object.partner_id.lang}.", placeholder="${object.partner_id.lang}") user_signature = fields.Boolean('Add Signature', help="If checked, the user's signature will be appended to the text version " "of the message") subject = fields.Char('Subject', translate=True, help="Subject (placeholders may be used here)") email_from = fields.Char('From', help="Sender address (placeholders may be used here). If not set, the default " "value will be the author's email alias if configured, or email address.") use_default_to = fields.Boolean( 'Default recipients', help="Default recipients of the record:\n" "- partner (using id on a partner or the partner_id field) OR\n" "- email (using email_from or email field)") email_to = fields.Char('To (Emails)', help="Comma-separated recipient addresses (placeholders may be used here)") partner_to = fields.Char('To (Partners)', oldname='email_recipients', help="Comma-separated ids of recipient partners (placeholders may be used here)") email_cc = fields.Char('Cc', help="Carbon copy recipients (placeholders may be used here)") reply_to = fields.Char('Reply-To', help="Preferred response address (placeholders may be used here)") mail_server_id = fields.Many2one('ir.mail_server', 'Outgoing Mail Server', readonly=False, help="Optional preferred server for outgoing mails. If not set, the highest " "priority one will be used.") body_html = fields.Html('Body', translate=True, sanitize=False) report_name = fields.Char('Report Filename', translate=True, help="Name to use for the generated report file (may contain placeholders)\n" "The extension can be omitted and will then come from the report type.") report_template = fields.Many2one('ir.actions.report', 'Optional report to print and attach') ref_ir_act_window = fields.Many2one('ir.actions.act_window', 'Sidebar action', readonly=True, copy=False, help="Sidebar action to make this template available on records " "of the related document model") attachment_ids = fields.Many2many('ir.attachment', 'email_template_attachment_rel', 'email_template_id', 'attachment_id', 'Attachments', help="You may attach files to this template, to be added to all " "emails created from this template") auto_delete = fields.Boolean('Auto Delete', default=True, help="Permanently delete this email after sending it, to save space") # Fake fields used to implement the placeholder assistant model_object_field = fields.Many2one('ir.model.fields', string="Field", help="Select target field from the related document model.\n" "If it is a relationship field you will be able to select " "a target field at the destination of the relationship.") sub_object = fields.Many2one('ir.model', 'Sub-model', readonly=True, help="When a relationship field is selected as first field, " "this field shows the document model the relationship goes to.") sub_model_object_field = fields.Many2one('ir.model.fields', 'Sub-field', help="When a relationship field is selected as first field, " "this field lets you select the target field within the " "destination document model (sub-model).") null_value = fields.Char('Default Value', help="Optional value to use if the target field is empty") copyvalue = fields.Char('Placeholder Expression', help="Final placeholder expression, to be copy-pasted in the desired template field.") scheduled_date = fields.Char('Scheduled Date', help="If set, the queue manager will send the email after the date. If not set, the email will be send as soon as possible. Jinja2 placeholders may be used.") @api.onchange('model_id') def onchange_model_id(self): # TDE CLEANME: should'nt it be a stored related ? if self.model_id: self.model = self.model_id.model else: self.model = False def build_expression(self, field_name, sub_field_name, null_value): """Returns a placeholder expression for use in a template field, based on the values provided in the placeholder assistant. :param field_name: main field name :param sub_field_name: sub field name (M2O) :param null_value: default value if the target value is empty :return: final placeholder expression """ expression = '' if field_name: expression = "${object." + field_name if sub_field_name: expression += "." + sub_field_name if null_value: expression += " or '''%s'''" % null_value expression += "}" return expression @api.onchange('model_object_field', 'sub_model_object_field', 'null_value') def onchange_sub_model_object_value_field(self): if self.model_object_field: if self.model_object_field.ttype in ['many2one', 'one2many', 'many2many']: model = self.env['ir.model']._get(self.model_object_field.relation) if model: self.sub_object = model.id self.copyvalue = self.build_expression(self.model_object_field.name, self.sub_model_object_field and self.sub_model_object_field.name or False, self.null_value or False) else: self.sub_object = False self.sub_model_object_field = False self.copyvalue = self.build_expression(self.model_object_field.name, False, self.null_value or False) else: self.sub_object = False self.copyvalue = False self.sub_model_object_field = False self.null_value = False @api.multi def unlink(self): self.unlink_action() return super(MailTemplate, self).unlink() @api.multi @api.returns('self', lambda value: value.id) def copy(self, default=None): default = dict(default or {}, name=_("%s (copy)") % self.name) return super(MailTemplate, self).copy(default=default) @api.multi def unlink_action(self): for template in self: if template.ref_ir_act_window: template.ref_ir_act_window.unlink() return True @api.multi def create_action(self): ActWindow = self.env['ir.actions.act_window'] view = self.env.ref('mail.email_compose_message_wizard_form') for template in self: button_name = _('Send Mail (%s)') % template.name action = ActWindow.create({ 'name': button_name, 'type': 'ir.actions.act_window', 'res_model': 'mail.compose.message', 'view_type': 'form', 'context': "{'default_composition_mode': 'mass_mail', 'default_template_id' : %d, 'default_use_template': True}" % (template.id), 'view_mode': 'form,tree', 'view_id': view.id, 'target': 'new', 'binding_model_id': template.model_id.id, }) template.write({'ref_ir_act_window': action.id}) return True # ---------------------------------------- # RENDERING # ---------------------------------------- @api.model def render_post_process(self, html): html = self.env['mail.thread']._replace_local_links(html) return html @api.model def _render_template(self, template_txt, model, res_ids, post_process=False): """ Render the given template text, replace mako expressions ``${expr}`` with the result of evaluating these expressions with an evaluation context containing: - ``user``: Model of the current user - ``object``: record of the document record this mail is related to - ``context``: the context passed to the mail composition wizard :param str template_txt: the template text to render :param str model: model name of the document record this mail is related to. :param int res_ids: list of ids of document records those mails are related to. """ multi_mode = True if isinstance(res_ids, int): multi_mode = False res_ids = [res_ids] results = dict.fromkeys(res_ids, u"") # try to load the template try: mako_env = mako_safe_template_env if self.env.context.get('safe') else mako_template_env template = mako_env.from_string(tools.ustr(template_txt)) except Exception: _logger.info("Failed to load template %r", template_txt, exc_info=True) return multi_mode and results or results[res_ids[0]] # prepare template variables records = self.env[model].browse(it for it in res_ids if it) # filter to avoid browsing [None] res_to_rec = dict.fromkeys(res_ids, None) for record in records: res_to_rec[record.id] = record variables = { 'format_date': lambda date, format=False, context=self._context: format_date(self.env, date, format), 'format_tz': lambda dt, tz=False, format=False, context=self._context: format_tz(self.env, dt, tz, format), 'format_amount': lambda amount, currency, context=self._context: format_amount(self.env, amount, currency), 'user': self.env.user, 'ctx': self._context, # context kw would clash with mako internals } for res_id, record in res_to_rec.items(): variables['object'] = record try: render_result = template.render(variables) except Exception: _logger.info("Failed to render template %r using values %r" % (template, variables), exc_info=True) raise UserError(_("Failed to render template %r using values %r")% (template, variables)) if render_result == u"False": render_result = u"" results[res_id] = render_result if post_process: for res_id, result in results.items(): results[res_id] = self.render_post_process(result) return multi_mode and results or results[res_ids[0]] @api.multi def get_email_template(self, res_ids): multi_mode = True if isinstance(res_ids, int): res_ids = [res_ids] multi_mode = False if res_ids is None: res_ids = [None] results = dict.fromkeys(res_ids, False) if not self.ids: return results self.ensure_one() if self.env.context.get('template_preview_lang'): lang = self.env.context.get('template_preview_lang') for res_id in res_ids: results[res_id] = self.with_context(lang=lang) else: langs = self._render_template(self.lang, self.model, res_ids) for res_id, lang in langs.items(): if lang: template = self.with_context(lang=lang) else: template = self results[res_id] = template return multi_mode and results or results[res_ids[0]] @api.multi def generate_recipients(self, results, res_ids): """Generates the recipients of the template. Default values can ben generated instead of the template values if requested by template or context. Emails (email_to, email_cc) can be transformed into partners if requested in the context. """ self.ensure_one() if self.use_default_to or self._context.get('tpl_force_default_to'): records = self.env[self.model].browse(res_ids).sudo() default_recipients = self.env['mail.thread']._message_get_default_recipients_on_records(records) for res_id, recipients in default_recipients.items(): results[res_id].pop('partner_to', None) results[res_id].update(recipients) records_company = None if self._context.get('tpl_partners_only') and self.model and results and 'company_id' in self.env[self.model]._fields: records = self.env[self.model].browse(results.keys()).read(['company_id']) records_company = {rec['id']: (rec['company_id'][0] if rec['company_id'] else None) for rec in records} for res_id, values in results.items(): partner_ids = values.get('partner_ids', list()) if self._context.get('tpl_partners_only'): mails = tools.email_split(values.pop('email_to', '')) + tools.email_split(values.pop('email_cc', '')) Partner = self.env['res.partner'] if records_company: Partner = Partner.with_context(default_company_id=records_company[res_id]) for mail in mails: partner_id = Partner.find_or_create(mail) partner_ids.append(partner_id) partner_to = values.pop('partner_to', '') if partner_to: # placeholders could generate '', 3, 2 due to some empty field values tpl_partner_ids = [int(pid) for pid in partner_to.split(',') if pid] partner_ids += self.env['res.partner'].sudo().browse(tpl_partner_ids).exists().ids results[res_id]['partner_ids'] = partner_ids return results @api.multi def generate_email(self, res_ids, fields=None): """Generates an email from the template for given the given model based on records given by res_ids. :param res_id: id of the record to use for rendering the template (model is taken from template definition) :returns: a dict containing all relevant fields for creating a new mail.mail entry, with one extra key ``attachments``, in the format [(report_name, data)] where data is base64 encoded. """ self.ensure_one() multi_mode = True if isinstance(res_ids, int): res_ids = [res_ids] multi_mode = False if fields is None: fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'scheduled_date'] res_ids_to_templates = self.get_email_template(res_ids) # templates: res_id -> template; template -> res_ids templates_to_res_ids = {} for res_id, template in res_ids_to_templates.items(): templates_to_res_ids.setdefault(template, []).append(res_id) results = dict() for template, template_res_ids in templates_to_res_ids.items(): Template = self.env['mail.template'] # generate fields value for all res_ids linked to the current template if template.lang: Template = Template.with_context(lang=template._context.get('lang')) for field in fields: Template = Template.with_context(safe=field in {'subject'}) generated_field_values = Template._render_template( getattr(template, field), template.model, template_res_ids, post_process=(field == 'body_html')) for res_id, field_value in generated_field_values.items(): results.setdefault(res_id, dict())[field] = field_value # compute recipients if any(field in fields for field in ['email_to', 'partner_to', 'email_cc']): results = template.generate_recipients(results, template_res_ids) # update values for all res_ids for res_id in template_res_ids: values = results[res_id] # body: add user signature, sanitize if 'body_html' in fields and template.user_signature: signature = self.env.user.signature if signature: values['body_html'] = tools.append_content_to_html(values['body_html'], signature, plaintext=False) if values.get('body_html'): values['body'] = tools.html_sanitize(values['body_html']) # technical settings values.update( mail_server_id=template.mail_server_id.id or False, auto_delete=template.auto_delete, model=template.model, res_id=res_id or False, attachment_ids=[attach.id for attach in template.attachment_ids], ) # Add report in attachments: generate once for all template_res_ids if template.report_template: for res_id in template_res_ids: attachments = [] report_name = self._render_template(template.report_name, template.model, res_id) report = template.report_template report_service = report.report_name if report.report_type in ['qweb-html', 'qweb-pdf']: result, format = report.render_qweb_pdf([res_id]) else: res = report.render([res_id]) if not res: raise UserError(_('Unsupported report type %s found.') % report.report_type) result, format = res # TODO in trunk, change return format to binary to match message_post expected format result = base64.b64encode(result) if not report_name: report_name = 'report.' + report_service ext = "." + format if not report_name.endswith(ext): report_name += ext attachments.append((report_name, result)) results[res_id]['attachments'] = attachments return multi_mode and results or results[res_ids[0]] @api.multi def send_mail(self, res_id, force_send=False, raise_exception=False, email_values=None, notif_layout=False): """ Generates a new mail.mail. Template is rendered on record given by res_id and model coming from template. :param int res_id: id of the record to render the template :param bool force_send: send email immediately; otherwise use the mail queue (recommended); :param dict email_values: update generated mail with those values to further customize the mail; :param str notif_layout: optional notification layout to encapsulate the generated email; :returns: id of the mail.mail that was created """ self.ensure_one() Mail = self.env['mail.mail'] Attachment = self.env['ir.attachment'] # TDE FIXME: should remove default_type from context # create a mail_mail based on values, without attachments values = self.generate_email(res_id) values['recipient_ids'] = [(4, pid) for pid in values.get('partner_ids', list())] values['attachment_ids'] = [(4, aid) for aid in values.get('attachment_ids', list())] values.update(email_values or {}) attachment_ids = values.pop('attachment_ids', []) attachments = values.pop('attachments', []) # add a protection against void email_from if 'email_from' in values and not values.get('email_from'): values.pop('email_from') # encapsulate body if notif_layout and values['body_html']: try: template = self.env.ref(notif_layout, raise_if_not_found=True) except ValueError: _logger.warning('QWeb template %s not found when sending template %s. Sending without layouting.' % (notif_layout, self.name)) else: record = self.env[self.model].browse(res_id) template_ctx = { 'message': self.env['mail.message'].sudo().new(dict(body=values['body_html'], record_name=record.display_name)), 'model_description': self.env['ir.model']._get(record._name).display_name, 'company': 'company_id' in record and record['company_id'] or self.env.company, } body = template.render(template_ctx, engine='ir.qweb', minimal_qcontext=True) values['body_html'] = self.env['mail.thread']._replace_local_links(body) mail = Mail.create(values) # manage attachments for attachment in attachments: attachment_data = { 'name': attachment[0], 'datas': attachment[1], 'type': 'binary', 'res_model': 'mail.message', 'res_id': mail.mail_message_id.id, } attachment_ids.append((4, Attachment.create(attachment_data).id)) if attachment_ids: mail.write({'attachment_ids': attachment_ids}) if force_send: mail.send(raise_exception=raise_exception) return mail.id # TDE CLEANME: return mail + api.returns ?
class AccountAccount(models.Model): _name = "account.account" _description = "Account" _order = "code, company_id" _check_company_auto = True @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 ValidationError(_('You cannot have a receivable/payable account that is not reconcilable. (account code: %s)', account.code)) @api.constrains('user_type_id') def _check_user_type_id(self): data_unaffected_earnings = self.env.ref('account.data_unaffected_earnings') result = self.read_group([('user_type_id', '=', data_unaffected_earnings.id)], ['company_id'], ['company_id']) for res in result: if res.get('company_id_count', 0) >= 2: account_unaffected_earnings = self.search([('company_id', '=', res['company_id'][0]), ('user_type_id', '=', data_unaffected_earnings.id)]) raise ValidationError(_('You cannot have more than one account with "Current Year Earnings" as type. (accounts: %s)', [a.code for a in account_unaffected_earnings])) name = fields.Char(string="Account Name", 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) used = fields.Boolean(store=False, search='_search_used') user_type_id = fields.Many2one('account.account.type', string='Type', required=True, 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', string="Internal Type", store=True, readonly=True) internal_group = fields.Selection(related='user_type_id.internal_group', string="Internal Group", 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.") 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', check_company=True, context={'append_type_to_tax_name': True}) note = fields.Text('Internal Notes') company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True, default=lambda self: self.env.company) tag_ids = fields.Many2many('account.account.tag', 'account_account_account_tag', string='Tags', help="Optional tags you may want to assign for custom reporting") group_id = fields.Many2one('account.group', compute='_compute_account_group', store=True, readonly=False) root_id = fields.Many2one('account.root', compute='_compute_account_root', store=True) allowed_journal_ids = fields.Many2many('account.journal', string="Allowed Journals", help="Define in which journals this account can be used. If empty, can be used in all journals.") opening_debit = fields.Monetary(string="Opening Debit", compute='_compute_opening_debit_credit', inverse='_set_opening_debit', help="Opening debit value for this account.") opening_credit = fields.Monetary(string="Opening Credit", compute='_compute_opening_debit_credit', inverse='_set_opening_credit', help="Opening credit value for this account.") opening_balance = fields.Monetary(string="Opening Balance", compute='_compute_opening_debit_credit', help="Opening balance value for this account.") _sql_constraints = [ ('code_company_uniq', 'unique (code,company_id)', 'The code of the account must be unique per company !') ] @api.constrains('reconcile', 'internal_group', 'tax_ids') def _constrains_reconcile(self): for record in self: if record.internal_group == 'off_balance': if record.reconcile: raise UserError(_('An Off-Balance account can not be reconcilable')) if record.tax_ids: raise UserError(_('An Off-Balance account can not have taxes')) @api.constrains('allowed_journal_ids') def _constrains_allowed_journal_ids(self): self.env['account.move.line'].flush(['account_id', 'journal_id']) self.flush(['allowed_journal_ids']) self._cr.execute(""" SELECT aml.id FROM account_move_line aml WHERE aml.account_id in %s AND EXISTS (SELECT 1 FROM account_account_account_journal_rel WHERE account_account_id = aml.account_id) AND NOT EXISTS (SELECT 1 FROM account_account_account_journal_rel WHERE account_account_id = aml.account_id AND account_journal_id = aml.journal_id) """, [tuple(self.ids)]) ids = self._cr.fetchall() if ids: raise ValidationError(_('Some journal items already exist with this account but in other journals than the allowed ones.')) @api.constrains('currency_id') def _check_journal_consistency(self): ''' Ensure the currency set on the journal is the same as the currency set on the linked accounts. ''' if not self: return self.env['account.account'].flush(['currency_id']) self.env['account.journal'].flush([ 'currency_id', 'default_account_id', 'payment_debit_account_id', 'payment_credit_account_id', 'suspense_account_id', ]) self._cr.execute(''' SELECT account.id, journal.id FROM account_account account JOIN res_company company ON company.id = account.company_id JOIN account_journal journal ON journal.default_account_id = account.id WHERE account.id IN %s AND journal.type IN ('bank', 'cash') AND journal.currency_id IS NOT NULL AND journal.currency_id != company.currency_id AND account.currency_id != journal.currency_id ''', [tuple(self.ids)]) res = self._cr.fetchone() if res: account = self.env['account.account'].browse(res[0]) journal = self.env['account.journal'].browse(res[1]) raise ValidationError(_( "The foreign currency set on the journal '%(journal)s' and the account '%(account)s' must be the same.", journal=journal.display_name, account=account.display_name )) @api.constrains('company_id') def _check_company_consistency(self): if not self: return self.flush(['company_id']) self._cr.execute(''' SELECT line.id FROM account_move_line line JOIN account_account account ON account.id = line.account_id WHERE line.account_id IN %s AND line.company_id != account.company_id ''', [tuple(self.ids)]) if self._cr.fetchone(): raise UserError(_("You can't change the company of your account since there are some journal items linked to it.")) @api.constrains('user_type_id') def _check_user_type_id(self): if not self: return self.flush(['user_type_id']) self._cr.execute(''' SELECT account.id FROM account_account account JOIN account_account_type acc_type ON account.user_type_id = acc_type.id JOIN account_journal journal ON journal.default_account_id = account.id WHERE account.id IN %s AND acc_type.type IN ('receivable', 'payable') AND journal.type IN ('sale', 'purchase') LIMIT 1; ''', [tuple(self.ids)]) if self._cr.fetchone(): raise ValidationError(_("The account is already in use in a 'sale' or 'purchase' journal. This means that the account's type couldn't be 'receivable' or 'payable'.")) @api.depends('code') def _compute_account_root(self): # this computes the first 2 digits of the account. # This field should have been a char, but the aim is to use it in a side panel view with hierarchy, and it's only supported by many2one fields so far. # So instead, we make it a many2one to a psql view with what we need as records. for record in self: record.root_id = (ord(record.code[0]) * 1000 + ord(record.code[1:2] or '\x00')) if record.code else False @api.depends('code') def _compute_account_group(self): if self.ids: self.env['account.group']._adapt_accounts_for_account_groups(self) else: self.group_id = False def _search_used(self, operator, value): if operator not in ['=', '!='] or not isinstance(value, bool): raise UserError(_('Operation not supported')) if operator != '=': value = not value self._cr.execute(""" SELECT id FROM account_account account WHERE EXISTS (SELECT * FROM account_move_line aml WHERE aml.account_id = account.id LIMIT 1) """) return [('id', 'in' if value else 'not in', [r[0] for r in self._cr.fetchall()])] @api.model def _search_new_account_code(self, company, digits, prefix): for num in range(1, 10000): new_code = str(prefix.ljust(digits - 1, '0')) + str(num) rec = self.search([('code', '=', new_code), ('company_id', '=', company.id)], limit=1) if not rec: return new_code raise UserError(_('Cannot generate an unused account code.')) def _compute_opening_debit_credit(self): if not self: return self.env.cr.execute(""" SELECT line.account_id, SUM(line.balance) AS balance, SUM(line.debit) AS debit, SUM(line.credit) AS credit FROM account_move_line line JOIN res_company comp ON comp.id = line.company_id WHERE line.move_id = comp.account_opening_move_id AND line.account_id IN %s GROUP BY line.account_id """, [tuple(self.ids)]) result = {r['account_id']: r for r in self.env.cr.dictfetchall()} for record in self: res = result.get(record.id) or {'debit': 0, 'credit': 0, 'balance': 0} record.opening_debit = res['debit'] record.opening_credit = res['credit'] record.opening_balance = res['balance'] def _set_opening_debit(self): self._set_opening_debit_credit(self.opening_debit, 'debit') def _set_opening_credit(self): self._set_opening_debit_credit(self.opening_credit, 'credit') def _set_opening_debit_credit(self, amount, field): """ Generic function called by both opening_debit and opening_credit's inverse function. 'Amount' parameter is the value to be set, and field either 'debit' or 'credit', depending on which one of these two fields got assigned. """ self.company_id.create_op_move_if_non_existant() opening_move = self.company_id.account_opening_move_id if opening_move.state == 'draft': # check whether we should create a new move line or modify an existing one account_op_lines = self.env['account.move.line'].search([('account_id', '=', self.id), ('move_id','=', opening_move.id), (field,'!=', False), (field,'!=', 0.0)]) # 0.0 condition important for import if account_op_lines: op_aml_debit = sum(account_op_lines.mapped('debit')) op_aml_credit = sum(account_op_lines.mapped('credit')) # There might be more than one line on this account if the opening entry was manually edited # If so, we need to merge all those lines into one before modifying its balance opening_move_line = account_op_lines[0] if len(account_op_lines) > 1: merge_write_cmd = [(1, opening_move_line.id, {'debit': op_aml_debit, 'credit': op_aml_credit, 'partner_id': None ,'name': _("Opening balance")})] unlink_write_cmd = [(2, line.id) for line in account_op_lines[1:]] opening_move.write({'line_ids': merge_write_cmd + unlink_write_cmd}) if amount: # modify the line opening_move_line.with_context(check_move_validity=False)[field] = amount else: # delete the line (no need to keep a line with value = 0) opening_move_line.with_context(check_move_validity=False).unlink() elif amount: # create a new line, as none existed before self.env['account.move.line'].with_context(check_move_validity=False).create({ 'name': _('Opening balance'), field: amount, 'move_id': opening_move.id, 'account_id': self.id, }) # Then, we automatically balance the opening move, to make sure it stays valid if not 'import_file' in self.env.context: # When importing a file, avoid recomputing the opening move for each account and do it at the end, for better performances self.company_id._auto_balance_opening_move() @api.model def default_get(self, default_fields): """If we're creating a new account through a many2one, there are chances that we typed the account code instead of its name. In that case, switch both fields values. """ if 'name' not in default_fields and 'code' not in default_fields: return super().default_get(default_fields) default_name = self._context.get('default_name') default_code = self._context.get('default_code') if default_name and not default_code: try: default_code = int(default_name) except ValueError: pass if default_code: default_name = False contextual_self = self.with_context(default_name=default_name, default_code=default_code) return super(AccountAccount, contextual_self).default_get(default_fields) @api.model def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None): args = args or [] domain = [] if name: domain = ['|', ('code', '=ilike', name.split(' ')[0] + '%'), ('name', operator, name)] if operator in expression.NEGATIVE_TERM_OPERATORS: domain = ['&', '!'] + domain[1:] account_ids = self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid) return models.lazy_name_get(self.browse(account_ids).with_user(name_get_uid)) @api.onchange('user_type_id') def _onchange_user_type_id(self): self.reconcile = self.internal_type in ('receivable', 'payable') if self.internal_type == 'liquidity': self.reconcile = False elif self.internal_group == 'off_balance': self.reconcile = False self.tax_ids = False elif self.internal_group == 'income' and not self.tax_ids: self.tax_ids = self.company_id.account_sale_tax_id elif self.internal_group == 'expense' and not self.tax_ids: self.tax_ids = self.company_id.account_purchase_tax_id def name_get(self): result = [] for account in self: name = account.code + ' ' + account.name result.append((account.id, name)) return result @api.returns('self', lambda value: value.id) def copy(self, default=None): default = dict(default or {}) if default.get('code', False): return super(AccountAccount, self).copy(default) try: default['code'] = (str(int(self.code) + 10) or '').zfill(len(self.code)) default.setdefault('name', _("%s (copy)") % (self.name or '')) while self.env['account.account'].search([('code', '=', default['code']), ('company_id', '=', default.get('company_id', False) or self.company_id.id)], limit=1): default['code'] = (str(int(default['code']) + 10) or '') default['name'] = _("%s (copy)") % (self.name or '') except ValueError: default['code'] = _("%s (copy)") % (self.code or '') default['name'] = self.name return super(AccountAccount, self).copy(default) @api.model def load(self, fields, data): """ Overridden for better performances when importing a list of account with opening debit/credit. In that case, the auto-balance is postpone until the whole file has been imported. """ rslt = super(AccountAccount, self).load(fields, data) if 'import_file' in self.env.context: companies = self.search([('id', 'in', rslt['ids'])]).mapped('company_id') for company in companies: company._auto_balance_opening_move() return rslt def _toggle_reconcile_to_true(self): '''Toggle the `reconcile´ boolean from False -> True Note that: lines with debit = credit = amount_currency = 0 are set to `reconciled´ = True ''' if not self.ids: return None query = """ UPDATE account_move_line SET reconciled = CASE WHEN debit = 0 AND credit = 0 AND amount_currency = 0 THEN true ELSE false END, amount_residual = (debit-credit), amount_residual_currency = amount_currency WHERE full_reconcile_id IS NULL and account_id IN %s """ self.env.cr.execute(query, [tuple(self.ids)]) def _toggle_reconcile_to_false(self): '''Toggle the `reconcile´ boolean from True -> False Note that it is disallowed if some lines are partially reconciled. ''' if not self.ids: return None partial_lines_count = self.env['account.move.line'].search_count([ ('account_id', 'in', self.ids), ('full_reconcile_id', '=', False), ('|'), ('matched_debit_ids', '!=', False), ('matched_credit_ids', '!=', False), ]) if partial_lines_count > 0: raise UserError(_('You cannot switch an account to prevent the reconciliation ' 'if some partial reconciliations are still pending.')) query = """ UPDATE account_move_line SET amount_residual = 0, amount_residual_currency = 0 WHERE full_reconcile_id IS NULL AND account_id IN %s """ self.env.cr.execute(query, [tuple(self.ids)]) def write(self, vals): # Do not 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.')) if 'reconcile' in vals: if vals['reconcile']: self.filtered(lambda r: not r.reconcile)._toggle_reconcile_to_true() else: self.filtered(lambda r: r.reconcile)._toggle_reconcile_to_false() if vals.get('currency_id'): for account in self: if self.env['account.move.line'].search_count([('account_id', '=', account.id), ('currency_id', 'not in', (False, vals['currency_id']))]): raise UserError(_('You cannot set a currency on this account as it already has some journal entries having a different foreign currency.')) return super(AccountAccount, self).write(vals) def unlink(self): if self.env['account.move.line'].search([('account_id', 'in', self.ids)], limit=1): raise UserError(_('You cannot perform this action 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'].sudo().search([('value_reference', 'in', values)], limit=1) if partner_prop_acc: account_name = partner_prop_acc.get_by_record().display_name raise UserError( _('You cannot remove/deactivate the account %s which is set on a customer or vendor.', account_name) ) return super(AccountAccount, self).unlink() def action_read_account(self): self.ensure_one() return { 'name': self.display_name, 'type': 'ir.actions.act_window', 'view_type': 'form', 'view_mode': 'form', 'res_model': 'account.account', 'res_id': self.id, } def action_duplicate_accounts(self): for account in self.browse(self.env.context['active_ids']): account.copy()
class PurchaseOrder(models.Model): _inherit = 'purchase.order' @api.model def _prepare_picking(self): result = super(PurchaseOrder, self)._prepare_picking() if result: result.update({ 'report_template_id': self.partner_id and self.partner_id.report_picking_template_id and self.partner_id.report_picking_template_id.id or self.partner_id and self.partner_id.report_delivery_template_id and self.partner_id.report_delivery_template_id.id or False }) return result @api.onchange('partner_id', 'company_id') def onchange_partner_id(self): res = super(PurchaseOrder, self).onchange_partner_id() if self.partner_id: self.report_template_id = self.partner_id.report_rfq_template_id and self.partner_id.report_rfq_template_id.id \ or self.partner_id.report_po_template_id and self.partner_id.report_po_template_id.id or False return res @api.multi def _prepare_invoice(self): invoice_vals = super(PurchaseOrder, self)._prepare_invoice() if invoice_vals: invoice_vals.update({ 'report_template_id': self.partner_id and self.partner_id.report_rfq_template_id and self.partner_id.report_rfq_template_id.id or self.partner_id and self.partner_id.report_po_template_id and self.partner_id.report_po_template_id.id or False }) return invoice_vals @api.model def _default_report_template(self): report_obj = self.env['ir.actions.report.xml'] report_id = report_obj.search([ ('model', '=', 'purchase.order'), ('report_name', '=', 'general_template.report_purchase_custom') ]) if report_id: report_id = report_id[0] else: report_id = report_obj.search([('model', '=', 'purchase.order') ])[0] return report_id @api.one @api.depends('partner_id') def _default_report_template1(self): report_obj = self.env['ir.actions.report.xml'] report_id = report_obj.search([ ('model', '=', 'purchase.order'), ('report_name', '=', 'general_template.report_purchase_custom') ]) if report_id: report_id = report_id[0] else: report_id = report_obj.search([('model', '=', 'purchase.order') ])[0] if self.report_template_id and self.report_template_id.id < report_id.id: self.write( {'report_template_id': report_id and report_id.id or False}) #self.report_template_id = report_id and report_id.id or False self.report_template_id = self.partner_id and self.partner_id.report_rfq_template_id and self.partner_id.report_rfq_template_id.id or self.partner_id and self.partner_id.report_po_template_id and self.partner_id.report_po_template_id.id or False self.report_template_id1 = report_id and report_id.id or False @api.multi def print_quotation(self): """ Print the invoice and mark it as sent, so that we can see more easily the next step of the workflow """ self.ensure_one() self.sent = True res = super(PurchaseOrder, self).print_quotation() if self.report_template_id or self.partner_id and self.partner_id.report_rfq_template_id or self.company_id and self.company_id.report_rfq_template_id: report_name = self.report_template_id and self.report_template_id.report_name or self.partner_id and self.partner_id.report_rfq_template_id.report_name or self.company_id and self.company_id.report_rfq_template_id.report_name report = self.env['report'].get_action( self, self.report_template_id and self.report_template_id.report_name or self.partner_id and self.partner_id.report_rfq_template_id.report_name or self.company_id and self.company_id.report_rfq_template_id.report_name) report.update({'report_name': 'purchase.report_purchasequotation'}) return report return res @api.multi def _get_street(self, partner): self.ensure_one() res = {} address = '' if partner.street: address = "%s" % (partner.street) if partner.street2: address += ", %s" % (partner.street2) reload(sys) sys.setdefaultencoding("utf-8") html_text = str(tools.plaintext2html(address, container_tag=True)) data = html_text.split('p>') if data: return data[1][:-2] return False @api.multi def _get_address_details(self, partner): self.ensure_one() res = {} address = '' if partner.city: address = "%s" % (partner.city) if partner.state_id.name: address += ", %s" % (partner.state_id.name) if partner.zip: address += ", %s" % (partner.zip) if partner.country_id.name: address += ", %s" % (partner.country_id.name) reload(sys) sys.setdefaultencoding("utf-8") html_text = str(tools.plaintext2html(address, container_tag=True)) data = html_text.split('p>') if data: return data[1][:-2] return False @api.multi def _get_origin_date(self, origin): self.ensure_one() res = {} sale_obj = self.env['purchase.order'] lang = self._context.get("lang") lang_obj = self.env['res.lang'] ids = lang_obj.search([("code", "=", lang or 'en_US')]) sale = sale_obj.search([('name', '=', origin)]) if sale: timestamp = datetime.datetime.strptime( sale.date_order, tools.DEFAULT_SERVER_DATETIME_FORMAT) ts = odoo.fields.datetime.context_timestamp(self, timestamp) n_date = ts.strftime(ids.date_format).decode('utf-8') if sale: return n_date return False @api.multi def _get_invoice_date(self): self.ensure_one() res = {} sale_obj = self.env['purchase.order'] lang = self._context.get("lang") lang_obj = self.env['res.lang'] ids = lang_obj.search([("code", "=", lang or 'en_US')]) if self.date_invoice: timestamp = datetime.datetime.strptime( self.date_invoice, tools.DEFAULT_SERVER_DATE_FORMAT) ts = odoo.fields.Datetime.context_timestamp(self, timestamp) n_date = ts.strftime(ids.date_format).decode('utf-8') if self: return n_date return False @api.multi def _get_invoice_due_date(self): self.ensure_one() res = {} sale_obj = self.env['purchase.order'] lang = self._context.get("lang") lang_obj = self.env['res.lang'] ids = lang_obj.search([("code", "=", lang or 'en_US')]) if self.date_due: timestamp = datetime.datetime.strptime( self.date_due, tools.DEFAULT_SERVER_DATE_FORMAT) ts = odoo.fields.Datetime.context_timestamp(self, timestamp) n_date = ts.strftime(ids.date_format).decode('utf-8') if self: return n_date return False @api.multi def _get_tax_amount(self, amount): self.ensure_one() res = {} currency = self.currency_id or self.company_id.currency_id res = formatLang(self.env, amount, currency_obj=currency) return res report_template_id1 = fields.Many2one( 'ir.actions.report.xml', string="Purchase Template", compute='_default_report_template1', help="Please select Template report for Purchase Order", domain=[('model', '=', 'purchase.order')]) report_template_id = fields.Many2one( 'ir.actions.report.xml', string="Purchase Template", help="Please select Template report for Purchase Order", domain=[('model', '=', 'purchase.order')]) amount_to_text = fields.Char(compute='_amount_in_words', string='In Words', help="The amount in words") @api.depends('amount_total') @api.one def _amount_in_words(self): if self.partner_id.lang == 'fr_FR': self.amount_to_text = amount_to_text_fr(self.amount_total, currency='euro') elif self.partner_id.lang == 'nl_NL': self.amount_to_text = amount_to_text_nl(self.amount_total, currency='euro') elif self.partner_id.lang == 'ar_SY': self.amount_to_text = amount_to_text_ar.amount_to_text_ar( self.amount_total, currency='درهم') elif self.partner_id.lang == 'de_DE': self.amount_to_text = amount_to_text_de.amount_to_text_de( self.amount_total, currency='euro') else: self.amount_to_text = amount_to_text_en.amount_to_text( nbr=self.amount_total, currency=self.currency_id.name)
class AccountGroup(models.Model): _name = "account.group" _description = 'Account Group' _parent_store = True _order = 'code_prefix_start' parent_id = fields.Many2one('account.group', index=True, ondelete='cascade', readonly=True) parent_path = fields.Char(index=True) name = fields.Char(required=True) code_prefix_start = fields.Char() code_prefix_end = fields.Char() company_id = fields.Many2one('res.company', required=True, readonly=True, default=lambda self: self.env.company) _sql_constraints = [ ( 'check_length_prefix', 'CHECK(char_length(COALESCE(code_prefix_start, \'\')) = char_length(COALESCE(code_prefix_end, \'\')))', 'The length of the starting and the ending code prefix must be the same' ), ] @api.onchange('code_prefix_start') def _onchange_code_prefix_start(self): if not self.code_prefix_end or self.code_prefix_end < self.code_prefix_start: self.code_prefix_end = self.code_prefix_start @api.onchange('code_prefix_end') def _onchange_code_prefix_end(self): if not self.code_prefix_start or self.code_prefix_start > self.code_prefix_end: self.code_prefix_start = self.code_prefix_end def name_get(self): result = [] for group in self: prefix = group.code_prefix_start and str(group.code_prefix_start) if prefix and group.code_prefix_end != group.code_prefix_start: prefix += '-' + str(group.code_prefix_end) name = (prefix and (prefix + ' ') or '') + group.name result.append((group.id, name)) return result @api.model def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None): args = args or [] if operator == 'ilike' and not (name or '').strip(): domain = [] else: criteria_operator = ['|'] if operator not in expression.NEGATIVE_TERM_OPERATORS else ['&', '!'] domain = criteria_operator + [('code_prefix_start', '=ilike', name + '%'), ('name', operator, name)] group_ids = self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid) return models.lazy_name_get(self.browse(group_ids).with_user(name_get_uid)) @api.constrains('code_prefix_start', 'code_prefix_end') def _constraint_prefix_overlap(self): self.env['account.group'].flush() query = """ SELECT other.id FROM account_group this JOIN account_group other ON char_length(other.code_prefix_start) = char_length(this.code_prefix_start) AND other.id != this.id AND other.company_id = this.company_id AND ( other.code_prefix_start <= this.code_prefix_start AND this.code_prefix_start <= other.code_prefix_end OR other.code_prefix_start >= this.code_prefix_start AND this.code_prefix_end >= other.code_prefix_start ) WHERE this.id IN %(ids)s """ self.env.cr.execute(query, {'ids': tuple(self.ids)}) res = self.env.cr.fetchall() if res: raise ValidationError(_('Account Groups with the same granularity can\'t overlap')) @api.model_create_multi def create(self, vals_list): for vals in vals_list: if 'code_prefix_start' in vals and not vals.get('code_prefix_end'): vals['code_prefix_end'] = vals['code_prefix_start'] res_ids = super(AccountGroup, self).create(vals_list) res_ids._adapt_accounts_for_account_groups() res_ids._adapt_parent_account_group() return res_ids def write(self, vals): res = super(AccountGroup, self).write(vals) if 'code_prefix_start' in vals or 'code_prefix_end' in vals: self._adapt_accounts_for_account_groups() self._adapt_parent_account_group() return res def unlink(self): for record in self: account_ids = self.env['account.account'].search([('group_id', '=', record.id)]) account_ids.write({'group_id': record.parent_id.id}) children_ids = self.env['account.group'].search([('parent_id', '=', record.id)]) children_ids.write({'parent_id': record.parent_id.id}) super(AccountGroup, self).unlink() def _adapt_accounts_for_account_groups(self, account_ids=None): """Ensure consistency between accounts and account groups. Find and set the most specific group matching the code of the account. The most specific is the one with the longest prefixes and with the starting prefix being smaller than the account code and the ending prefix being greater. """ if not self and not account_ids: return self.env['account.group'].flush() self.env['account.account'].flush() query = """ UPDATE account_account account SET group_id = ( SELECT agroup.id FROM account_group agroup WHERE agroup.code_prefix_start <= LEFT(account.code, char_length(agroup.code_prefix_start)) AND agroup.code_prefix_end >= LEFT(account.code, char_length(agroup.code_prefix_end)) AND agroup.company_id = account.company_id ORDER BY char_length(agroup.code_prefix_start) DESC LIMIT 1 ) WHERE account.company_id in %(company_ids)s {where_account}; """.format( where_account=account_ids and 'AND account.id IN %(account_ids)s' or '' ) self.env.cr.execute(query, {'company_ids': tuple((self.company_id or account_ids.company_id).ids), 'account_ids': account_ids and tuple(account_ids.ids)}) self.env['account.account'].invalidate_cache(fnames=['group_id']) def _adapt_parent_account_group(self): """Ensure consistency of the hierarchy of account groups. Find and set the most specific parent for each group. The most specific is the one with the longest prefixes and with the starting prefix being smaller than the child prefixes and the ending prefix being greater. """ if not self: return self.env['account.group'].flush() query = """ UPDATE account_group agroup SET parent_id = ( SELECT parent.id FROM account_group parent WHERE char_length(parent.code_prefix_start) < char_length(agroup.code_prefix_start) AND parent.code_prefix_start <= LEFT(agroup.code_prefix_start, char_length(parent.code_prefix_start)) AND parent.code_prefix_end >= LEFT(agroup.code_prefix_end, char_length(parent.code_prefix_end)) AND parent.id != agroup.id AND parent.company_id = %(company_id)s ORDER BY char_length(parent.code_prefix_start) DESC LIMIT 1 ) WHERE agroup.company_id = %(company_id)s; """ self.env.cr.execute(query, {'company_id': self.company_id.id}) self.env['account.group'].invalidate_cache(fnames=['parent_id']) self.env['account.group'].search([('company_id', '=', self.company_id.id)])._parent_store_update()
class cash_flow_wizard(models.TransientModel): _name = "cash.flow.wizard" def _default_period_id_impl(self): """ 默认是当前会计期间 :return: 当前会计期间的对象 """ return self.env['finance.period'].get_date_now_period_id() @api.model def _default_period_id(self): return self._default_period_id_impl() period_id = fields.Many2one('finance.period', string=u'会计期间', default=_default_period_id) @api.model def get_amount(self, tem, report_ids, period_id): ''' [('get',u'销售收款'), ('pay',u'采购付款'), ('category',u'其他收支'), ('begin',u'科目期初'), ('end',u'科目期末'), ('lines',u'表行计算')] ''' date_start, date_end = self.env['finance.period'].get_period_month_date_range(period_id) ret = 0 if tem.line_type == 'get' or tem.line_type == 'pay': # 收款单或付款单金额合计 ret = sum([order.amount for order in self.env['money.order'].search([('type','=',tem.line_type), ('state','=','done'), ('date','>=',date_start), ('date','<=',date_end)])]) if tem.line_type == 'category': # 其他收支单金额合计 ret = sum([line.amount for line in self.env['other.money.order.line'].search([('category_id','in',[c.id for c in tem.category_ids]), ('other_money_id.state','=','done'), ('other_money_id.date','>=',date_start), ('other_money_id.date','<=',date_end)])]) if tem.line_type == 'begin': # 科目期初金额合计 ret = sum([ acc.initial_balance_debit - acc.initial_balance_credit for acc in self.env['trial.balance'].search([('period_id', '=', period_id.id), ('subject_name_id','in',[b.id for b in tem.begin_ids])])]) if tem.line_type == 'end': # 科目期末金额合计 ret = sum([ acc.ending_balance_debit - acc.ending_balance_credit for acc in self.env['trial.balance'].search([('period_id', '=', period_id.id), ('subject_name_id','in',[e.id for e in tem.end_ids])])]) if tem.line_type == 'lines': # 根据其他报表行计算 for line in self.env['cash.flow.statement'].browse(report_ids): for l in tem.plus_ids: if l.line_num == line.line_num: ret += line.amount for l in tem.nega_ids: if l.line_num == line.line_num: ret -= line.amount return ret @api.multi def show(self): """生成现金流量表""" rep_ids = [] ''' old_report = self.env['cash.flow.statement'].search([('period_id','=',self.period_id.id)]) if old_report: rep_ids = [rep.id for rep in old_report] else: ''' if self.period_id: templates = self.env['cash.flow.template'].search([]) for tem in templates: new_rep = self.env['cash.flow.statement'].create( { 'name':tem.name, 'line_num':tem.line_num, 'amount':self.get_amount(tem,rep_ids,self.period_id), } ) rep_ids.append(new_rep.id) view_id = self.env.ref('money.cash_flow_statement_tree').id attachment_information = u'编制单位:' + self.env.user.company_id.name + u',,' + self.period_id.year\ + u'年' + self.period_id.month + u'月' + u',' + u'单位:元' return { 'type': 'ir.actions.act_window', 'name': u'现金流量表:' + self.period_id.name, 'view_type': 'form', 'view_mode': 'tree', 'res_model': 'cash.flow.statement', 'target': 'current', 'view_id': False, 'views': [(view_id, 'tree')], 'context': {'period_id': self.period_id.id, 'attachment_information': attachment_information}, 'domain': [('id', 'in', rep_ids)], 'limit': 65535, }
class AccountPaymentGroupInvoiceWizard(models.TransientModel): _name = "account.payment.group.invoice.wizard" @api.model def default_payment_group(self): return self.env['account.payment.group'].browse( self._context.get('active_id', False)) payment_group_id = fields.Many2one( 'account.payment.group', default=default_payment_group, ondelete='cascade', required=True, ) journal_id = fields.Many2one( 'account.journal', 'Journal', required=True, ondelete='cascade', ) date_invoice = fields.Date(string='Refund Date', default=fields.Date.context_today, required=True) currency_id = fields.Many2one(related='payment_group_id.currency_id', ) date = fields.Date(string='Accounting Date') product_id = fields.Many2one( 'product.product', required=True, domain=[('sale_ok', '=', True)], ) tax_ids = fields.Many2many( 'account.tax', string='Taxes', ) amount_untaxed = fields.Monetary( string='Untaxed Amount', required=True, compute='_compute_amount_untaxed', inverse='_inverse_amount_untaxed', ) # we make amount total the main one and the other computed because the # normal case of use would be to know the total amount and also this amount # is the suggested one on creating the wizard amount_total = fields.Monetary(string='Total Amount', required=True) description = fields.Char(string='Reason', ) company_id = fields.Many2one(related='payment_group_id.company_id', ) account_analytic_id = fields.Many2one( 'account.analytic.account', 'Analytic Account', ) @api.onchange('product_id') def change_product(self): self.ensure_one() if self.payment_group_id.partner_type == 'supplier': taxes = self.product_id.supplier_taxes_id else: taxes = self.product_id.taxes_id company = self.company_id or self.env.user.company_id taxes = taxes.filtered(lambda r: r.company_id == company) self.tax_ids = self.payment_group_id.partner_id.with_context( force_company=company.id).property_account_position_id.map_tax( taxes) @api.onchange('amount_untaxed', 'tax_ids') def _inverse_amount_untaxed(self): self.ensure_one() if self.tax_ids: taxes = self.tax_ids.compute_all( self.amount_untaxed, self.company_id.currency_id, 1.0, product=self.product_id, partner=self.payment_group_id.partner_id) self.amount_total = taxes['total_included'] else: self.amount_total = self.amount_untaxed @api.depends('tax_ids', 'amount_total') def _compute_amount_untaxed(self): """ For now we implement inverse only for percent taxes. We could extend to other by simulating tax.price_include = True, computing tax and then restoring tax.price_include = False. """ self.ensure_one() tax_percent = 0.0 for tax in self.tax_ids.filtered(lambda x: not x.price_include): if tax.amount_type == 'percent': tax_percent += tax.amount elif tax.amount_type == 'partner_tax': # ugly compatibility with l10n_ar l10n_ar_account_withholding tax_percent += tax.get_partner_alicuot( self.payment_group_id.partner_id, fields.Date.context_today(self)).alicuota_percepcion else: raise ValidationError( _('You can only set amount total if taxes are of type ' 'percentage')) total_percent = (1 + tax_percent / 100) or 1.0 self.amount_untaxed = self.amount_total / total_percent @api.onchange('payment_group_id') def change_payment_group(self): journal_type = 'sale' type_tax_use = 'sale' if self.payment_group_id.partner_type == 'supplier': journal_type = 'purchase' type_tax_use = 'purchase' journal_domain = [ ('type', '=', journal_type), ('company_id', '=', self.payment_group_id.company_id.id), ] tax_domain = [('type_tax_use', '=', type_tax_use), ('company_id', '=', self.payment_group_id.company_id.id)] self.journal_id = self.env['account.journal'].search(journal_domain, limit=1) # usually debit/credit note will be for the payment difference self.amount_total = abs(self.payment_group_id.payment_difference) return { 'domain': { 'journal_id': journal_domain, 'tax_ids': tax_domain, } } @api.multi def get_invoice_vals(self): self.ensure_one() payment_group = self.payment_group_id if payment_group.partner_type == 'supplier': invoice_type = 'in_' else: invoice_type = 'out_' if self._context.get('refund'): invoice_type += 'refund' else: invoice_type += 'invoice' return { 'name': self.description, 'date': self.date, 'date_invoice': self.date_invoice, 'origin': _('Payment id %s') % payment_group.id, 'journal_id': self.journal_id.id, 'user_id': payment_group.partner_id.user_id.id, 'partner_id': payment_group.partner_id.id, 'type': invoice_type, # 'invoice_line_ids': [('invoice_type')], } @api.multi def confirm(self): self.ensure_one() invoice = self.env['account.invoice'].create(self.get_invoice_vals()) inv_line_vals = { 'product_id': self.product_id.id, 'price_unit': self.amount_untaxed, 'invoice_id': invoice.id, 'invoice_line_tax_ids': [(6, 0, self.tax_ids.ids)], } invoice_line = self.env['account.invoice.line'].new(inv_line_vals) invoice_line._onchange_product_id() # restore chosen taxes (changed by _onchange_product_id) invoice_line.invoice_line_tax_ids = self.tax_ids line_values = invoice_line._convert_to_write(invoice_line._cache) line_values['price_unit'] = self.amount_untaxed if self.account_analytic_id: line_values['account_analytic_id'] = self.account_analytic_id.id invoice.write({'invoice_line_ids': [(0, 0, line_values)]}) invoice.compute_taxes() invoice.action_invoice_open() self.payment_group_id.to_pay_move_line_ids += ( invoice.open_move_line_ids)
class MassObject(models.Model): _name = "mass.object" _description = "Mass Editing Object" name = fields.Char('Name', required=True, index=1) model_id = fields.Many2one('ir.model', 'Model', required=True, ondelete="cascade", help="Model is used for Selecting Fields. " "This is editable until Sidebar menu " "is not created.") field_ids = fields.Many2many('ir.model.fields', 'mass_field_rel', 'mass_id', 'field_id', 'Fields') ref_ir_act_window_id = fields.Many2one('ir.actions.act_window', 'Sidebar action', readonly=True, help="Sidebar action to make this " "template available on " "records of the related " "document model.") model_list = fields.Char('Model List') group_ids = fields.Many2many( comodel_name="res.groups", relation="mass_group_rel", column1="mass_id", column2="group_id", string="Groups", ) _sql_constraints = [ ('name_uniq', 'unique (name)', 'Name must be unique!'), ] @api.onchange('model_id') def _onchange_model_id(self): self.field_ids = [(6, 0, [])] model_list = [] if self.model_id: model_obj = self.env['ir.model'] model_list = [self.model_id.id] active_model_obj = self.env[self.model_id.model] if active_model_obj._inherits: keys = list(active_model_obj._inherits.keys()) inherits_model_list = model_obj.search([('model', 'in', keys)]) model_list.extend((inherits_model_list and inherits_model_list.ids or [])) self.model_list = model_list # # @api.multi def create_action(self): self.ensure_one() vals = {} action_obj = self.env['ir.actions.act_window'] src_obj = self.model_id.model button_name = _('Mass Editing (%s)') % self.name vals['ref_ir_act_window_id'] = action_obj.create({ 'name': button_name, 'type': 'ir.actions.act_window', 'res_model': 'mass.editing.wizard', 'src_model': src_obj, 'groups_id': [(4, x.id) for x in self.group_ids], 'view_type': 'form', 'context': "{'mass_editing_object' : %d}" % (self.id), 'view_mode': 'form', 'target': 'new', 'binding_model_id': self.model_id.id, 'binding_type': 'action', 'multi': True, }).id self.write(vals) return True # @api.multi def unlink_action(self): self.mapped('ref_ir_act_window_id').unlink() return True # @api.multi def unlink(self): self.unlink_action() return super(MassObject, self).unlink() # @api.multi @api.returns('self', lambda value: value.id) def copy(self, default=None): if default is None: default = {} default.update({'name': _("%s (copy)" % self.name), 'field_ids': []}) return super(MassObject, self).copy(default)
class Partner(models.Model): _inherit = "res.partner" def _get_l10n_do_dgii_payer_types_selection(self): """Return the list of payer types needed in invoices to clasify accordingly to DGII requirements.""" return [ ("taxpayer", _("Fiscal Tax Payer")), ("non_payer", _("Non Tax Payer")), ("nonprofit", _("Nonprofit Organization")), ("special", _("special from Tax Paying")), ("governmental", _("Governmental")), ("foreigner", _("Foreigner")), ] def _get_l10n_do_expense_type(self): """Return the list of expenses needed in invoices to clasify accordingly to DGII requirements.""" return [ ("01", _("01 - Personal")), ("02", _("02 - Work, Supplies and Services")), ("03", _("03 - Leasing")), ("04", _("04 - Fixed Assets")), ("05", _("05 - Representation")), ("06", _("06 - Admitted Deductions")), ("07", _("07 - Financial Expenses")), ("08", _("08 - Extraordinary Expenses")), ("09", _("09 - Cost & Expenses part of Sales")), ("10", _("10 - Assets Acquisitions")), ("11", _("11 - Insurance Expenses")), ] l10n_do_dgii_tax_payer_type = fields.Selection( selection="_get_l10n_do_dgii_payer_types_selection", compute="_compute_l10n_do_dgii_payer_type", inverse="_inverse_l10n_do_dgii_tax_payer_type", string="Taxpayer Type", index=True, store=True, ) l10n_do_expense_type = fields.Selection( selection="_get_l10n_do_expense_type", string="Cost & Expense Type", store=True, ) country_id = fields.Many2one( default=lambda self: self.env.ref("base.do") if self.env.user.company_id.country_id == self.env.ref("base.do") else False ) def _check_l10n_do_fiscal_fields(self, vals): if not self or self.parent_id: # Do not perform any check because child contacts # have readonly fiscal field. This also allows set # contacts parent, even if this changes any of its # fiscal fields. return fiscal_fields = [ field for field in ["name", "vat", "country_id"] # l10n_do_dgii_tax_payer_type ? if field in vals ] if ( fiscal_fields and not self.env.user.has_group( "l10n_do_accounting.group_l10n_do_edit_fiscal_partner" ) and self.env["account.move"] .sudo() .search( [ ("l10n_latam_use_documents", "=", True), ("country_code", "=", "DO"), ("commercial_partner_id", "=", self.id), ("state", "=", "posted"), ], limit=1, ) ): raise AccessError( _( "You are not allowed to modify %s after partner " "fiscal document issuing" ) % (", ".join(self._fields[f].string for f in fiscal_fields)) ) def write(self, vals): res = super(Partner, self).write(vals) self._check_l10n_do_fiscal_fields(vals) return res @api.depends("vat", "country_id", "name") def _compute_l10n_do_dgii_payer_type(self): """ Compute the type of partner depending on soft decisions""" company_id = self.env["res.company"].search( [("id", "=", self.env.user.company_id.id)] ) for partner in self: vat = str(partner.vat if partner.vat else partner.name) is_dominican_partner = bool(partner.country_id == self.env.ref("base.do")) if partner.country_id and not is_dominican_partner: partner.l10n_do_dgii_tax_payer_type = "foreigner" elif vat and ( not partner.l10n_do_dgii_tax_payer_type or partner.l10n_do_dgii_tax_payer_type == "non_payer" ): if partner.country_id and is_dominican_partner: if vat.isdigit() and len(vat) == 9: if not partner.vat: partner.vat = vat if partner.name and "MINISTERIO" in partner.name: partner.l10n_do_dgii_tax_payer_type = "governmental" elif partner.name and any( [n for n in ("IGLESIA", "ZONA FRANCA") if n in partner.name] ): partner.l10n_do_dgii_tax_payer_type = "special" elif vat.startswith("1"): partner.l10n_do_dgii_tax_payer_type = "taxpayer" elif vat.startswith("4"): partner.l10n_do_dgii_tax_payer_type = "nonprofit" else: partner.l10n_do_dgii_tax_payer_type = "taxpayer" elif len(vat) == 11: if vat.isdigit(): if not partner.vat: partner.vat = vat payer_type = ( "taxpayer" if company_id.l10n_do_default_client == "fiscal" else "non_payer" ) partner.l10n_do_dgii_tax_payer_type = payer_type else: partner.l10n_do_dgii_tax_payer_type = "non_payer" else: partner.l10n_do_dgii_tax_payer_type = "non_payer" elif not partner.l10n_do_dgii_tax_payer_type: partner.l10n_do_dgii_tax_payer_type = "non_payer" else: partner.l10n_do_dgii_tax_payer_type = ( partner.l10n_do_dgii_tax_payer_type ) def _inverse_l10n_do_dgii_tax_payer_type(self): for partner in self: partner.l10n_do_dgii_tax_payer_type = partner.l10n_do_dgii_tax_payer_type
class StorageThumbnail(models.Model): _name = "storage.thumbnail" _description = "Storage Thumbnail" _inherits = {"storage.file": "file_id"} size_x = fields.Integer("weight") size_y = fields.Integer("height") url_key = fields.Char( "Url key", help="Specific URL key for generating the url of the image") file_id = fields.Many2one("storage.file", "File", required=True, ondelete="cascade") res_model = fields.Char(readonly=False, index=True) res_id = fields.Integer(readonly=False, index=True) def _prepare_thumbnail(self, image, size_x, size_y, url_key): image_resize_format = (self.env["ir.config_parameter"].sudo(). get_param("storage.image.resize.format")) if image_resize_format: extension = image_resize_format else: extension = image.extension return { "data": self._resize(image, size_x, size_y, extension), "res_model": image._name, "res_id": image.id, "name": "%s_%s_%s%s" % (url_key or image.filename, size_x, size_y, extension), "size_x": size_x, "size_y": size_y, "url_key": url_key, } def _resize(self, image, size_x, size_y, fmt): image_resize_server = (self.env["ir.config_parameter"].sudo(). get_param("storage.image.resize.server")) if image_resize_server and image.backend_id.served_by != "odoo": values = { "url": image.url, "width": size_x, "height": size_y, "fmt": fmt, } url = image_resize_server.format(**values) return base64.encodestring(requests.get(url).content) return image_resize_image(image.data, size=(size_x, size_y)) def _get_backend_id(self): """Choose the correct backend. By default : it's the one configured as ir.config_parameter Overload this method if you need something more powerfull """ return int(self.env["ir.config_parameter"].sudo().get_param( "storage.thumbnail.backend_id")) @api.model def create(self, vals): vals.update({ "backend_id": self._get_backend_id(), "file_type": "thumbnail" }) return super().create(vals) def unlink(self): files = self.mapped("file_id") result = super().unlink() files.unlink() return result
class Session(models.Model): _name = 'openacademy.session' _description = "OpenAcademy Sessions" 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) color = fields.Integer() instructor_id = fields.Many2one('res.partner', string="Instructor", domain=[('instructor', '=', True)]) course_id = fields.Many2one('openacademy.course', ondelete='cascade', string="Course", required=True) 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') attendees_count = fields.Integer( string="Attendees count", compute='_get_attendees_count', store=True) status = fields.Selection([ ('draft', "Draft"), ('started', "Started"), ('done', "Done"), ('cancelled', "Cancelled"), ], string="Progress", default='draft', translate=True) @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 = (len(r.attendee_ids) / r.seats) * 100.0 @api.onchange('seats', 'attendee_ids') def _verify_valid_seats(self): 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.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 duration = timedelta(days=r.duration, seconds=-1) r.end_date = r.start_date + duration def _set_end_date(self): for r in self: if not (r.start_date and r.end_date): continue # Compute the difference between dates, but: Friday - Monday = 4 days, # so add one day to get 5 days instead r.duration = (r.end_date - r.start_date).days + 1 @api.depends('attendee_ids') def _get_attendees_count(self): for r in self: r.attendees_count = len(r.attendee_ids) @api.constrains('instructor_id', 'attendee_ids') def _check_instructor_not_in_attendees(self): for r in self: if r.instructor_id and r.instructor_id in r.attendee_ids: raise exceptions.ValidationError("A session's instructor can't be an attendee") def send_session_report(self): # Find the e-mail template template = self.env.ref('openacademy.openacademy_session_mail_template') # You can also find the e-mail template like this: # template = self.env['ir.model.data'].get_object('send_mail_template_demo', 'example_email_template') # Send out the e-mail template to the user self.env['mail.template'].browse(template.id).send_mail(self.id) def start_session(self): for record in self: record.status = 'started'
class QualityCheckTest(models.Model): _name = "quality.check.test" _rec_name = 'product_id' product_id = fields.Many2one("product.product") quality_test_ids = fields.One2many("quality.test.lines", "quality_check_id")
class StockRequestAllocation(models.Model): _name = 'stock.request.allocation' _description = 'Stock Request Allocation' stock_request_id = fields.Many2one(string='Stock Request', comodel_name='stock.request', required=True, ondelete='cascade', ) company_id = fields.Many2one(string='Company', comodel_name='res.company', readonly=True, related='stock_request_id.company_id', store=True, ) stock_move_id = fields.Many2one(string='Stock Move', comodel_name='stock.move', required=True, ondelete='cascade', ) product_id = fields.Many2one(string='Product', comodel_name='product.product', related='stock_request_id.product_id', readonly=True, ) product_uom_id = fields.Many2one(string='UoM', comodel_name='product.uom', related='stock_request_id.product_uom_id', readonly=True, ) requested_product_uom_qty = fields.Float( 'Requested Quantity (UoM)', help='Quantity of the stock request allocated to the stock move, ' 'in the UoM of the Stock Request', ) requested_product_qty = fields.Float( 'Requested Quantity', help='Quantity of the stock request allocated to the stock move, ' 'in the default UoM of the product', compute='_compute_requested_product_qty' ) allocated_product_qty = fields.Float( 'Allocated Quantity', copy=False, help='Quantity of the stock request allocated to the stock move, ' 'in the default UoM of the product', ) open_product_qty = fields.Float('Open Quantity', compute='_compute_open_product_qty') @api.depends('stock_request_id.product_id', 'stock_request_id.product_uom_id', 'requested_product_uom_qty') def _compute_requested_product_qty(self): for rec in self: rec.requested_product_qty = rec.product_uom_id._compute_quantity( rec.requested_product_uom_qty, rec.product_id.uom_id) @api.depends('requested_product_qty', 'allocated_product_qty', 'stock_move_id', 'stock_move_id.state') def _compute_open_product_qty(self): for rec in self: if rec.stock_move_id.state in ['cancel', 'done']: rec.open_product_qty = 0.0 else: rec.open_product_qty = \ rec.requested_product_qty - rec.allocated_product_qty if rec.open_product_qty < 0.0: rec.open_product_qty = 0.0
class L10nEsAeatMod303Report(models.Model): _inherit = "l10n.es.aeat.report.tax.mapping" _name = "l10n.es.aeat.mod303.report" _description = "AEAT 303 Report" _aeat_number = "303" devolucion_mensual = fields.Boolean( string="Montly Return", states=NON_EDITABLE_ON_DONE, help="Registered in the Register of Monthly Return", ) total_devengado = fields.Float( string="[27] VAT payable", readonly=True, compute_sudo=True, compute="_compute_total_devengado", store=True, ) total_deducir = fields.Float( string="[45] VAT receivable", readonly=True, compute_sudo=True, compute="_compute_total_deducir", store=True, ) casilla_46 = fields.Float( string="[46] General scheme result", readonly=True, store=True, help="(VAT payable - VAT receivable)", compute="_compute_casilla_46", ) porcentaje_atribuible_estado = fields.Float( string="[65] % attributable to State", default=100, states=NON_EDITABLE_ON_DONE, help="Taxpayers who pay jointly to the Central Government and " "the Provincial Councils of the Basque Country or the " "Autonomous Community of Navarra, will enter in this box the " "percentage of volume operations in the common territory. " "Other taxpayers will enter in this box 100%", ) atribuible_estado = fields.Float( string="[66] Attributable to the Administration", readonly=True, compute="_compute_atribuible_estado", store=True, ) cuota_compensar = fields.Float( string="[67] Fees to compensate", default=0, states=NON_EDITABLE_ON_DONE, help="Fee to compensate for prior periods, in which his statement " "was to return and compensation back option was chosen", ) regularizacion_anual = fields.Float( string="[68] Annual regularization", states=NON_EDITABLE_ON_DONE, compute="_compute_regularizacion_anual", readonly=False, store=True, help="In the last auto settlement of the year, shall be recorded " "(the fourth period or 12th month), with the appropriate sign, " "the result of the annual adjustment as have the laws by the " "Economic Agreement approved between the State and the " "Autonomous Community the Basque Country and the " "Economic Agreement between the State and Navarre.", ) casilla_69 = fields.Float( string="[69] Result", readonly=True, compute="_compute_casilla_69", help="[66] Attributable to the Administration - " "[67] Fees to compensate + " "[68] Annual regularization", store=True, ) casilla_77 = fields.Float( string="[77] VAT deferred (Settle by customs)", help="Contributions of import tax included in the documents " "evidencing the payment made by the Administration and received " "in the settlement period. You can only complete this box " "when the requirements of Article 74.1 of the Tax Regulations " "Value Added are met.", ) previous_result = fields.Float( string="[70] To be deducted", help="Result of the previous or prior statements of the same concept, " "exercise and period", states=NON_EDITABLE_ON_DONE, ) resultado_liquidacion = fields.Float( string="[71] Settlement result", readonly=True, compute="_compute_resultado_liquidacion", store=True, ) result_type = fields.Selection( selection=[ ("I", "To enter"), ("D", "To return"), ("C", "To compensate"), ("N", "No activity/Zero result"), ], string="Result type", compute="_compute_result_type", ) counterpart_account_id = fields.Many2one( comodel_name="account.account", string="Counterpart account", compute="_compute_counterpart_account_id", domain="[('company_id', '=', company_id)]", store=True, readonly=False, ) allow_posting = fields.Boolean(string="Allow posting", default=True) exonerated_390 = fields.Selection( selection=[("1", u"Exonerado"), ("2", u"No exonerado")], default="2", required=True, states=NON_EDITABLE_ON_DONE, compute="_compute_exonerated_390", store=True, readonly=False, string=u"Exonerado mod. 390", help=u"Exonerado de la Declaración-resumen anual del IVA, modelo 390: " u"Volumen de operaciones (art. 121 LIVA)", ) has_operation_volume = fields.Boolean( string=u"¿Volumen de operaciones?", default=True, states=NON_EDITABLE_ON_DONE, help=u"¿Existe volumen de operaciones (art. 121 LIVA)?", ) has_347 = fields.Boolean( string=u"¿Obligación del 347?", default=True, states=NON_EDITABLE_ON_DONE, help=u"Marque la casilla si el sujeto pasivo ha efectuado con alguna " u"persona o entidad operaciones por las que tenga obligación de " u"presentar la declaración anual de operaciones con terceras " u"personas (modelo 347).", ) is_voluntary_sii = fields.Boolean( string=u"¿SII voluntario?", states=NON_EDITABLE_ON_DONE, help=u"¿Ha llevado voluntariamente los Libros registro del IVA a " u"través de la Sede electrónica de la AEAT durante el ejercicio?", ) main_activity_code = fields.Many2one( comodel_name="l10n.es.aeat.mod303.report.activity.code", domain="[('period_type', '=', period_type)]", states=NON_EDITABLE_ON_DONE, string=u"Código actividad principal", ) main_activity_iae = fields.Char( states=NON_EDITABLE_ON_DONE, string=u"Epígrafe I.A.E. actividad principal", size=4, ) other_first_activity_code = fields.Many2one( comodel_name="l10n.es.aeat.mod303.report.activity.code", domain="[('period_type', '=', period_type)]", states=NON_EDITABLE_ON_DONE, string=u"Código 1ª actividad", ) other_first_activity_iae = fields.Char( string=u"Epígrafe I.A.E. 1ª actividad", states=NON_EDITABLE_ON_DONE, size=4, ) other_second_activity_code = fields.Many2one( comodel_name="l10n.es.aeat.mod303.report.activity.code", domain="[('period_type', '=', period_type)]", states=NON_EDITABLE_ON_DONE, string=u"Código 2ª actividad", ) other_second_activity_iae = fields.Char( string=u"Epígrafe I.A.E. 2ª actividad", states=NON_EDITABLE_ON_DONE, size=4, ) other_third_activity_code = fields.Many2one( comodel_name="l10n.es.aeat.mod303.report.activity.code", domain="[('period_type', '=', period_type)]", states=NON_EDITABLE_ON_DONE, string=u"Código 3ª actividad", ) other_third_activity_iae = fields.Char( string=u"Epígrafe I.A.E. 3ª actividad", states=NON_EDITABLE_ON_DONE, size=4, ) other_fourth_activity_code = fields.Many2one( comodel_name="l10n.es.aeat.mod303.report.activity.code", domain="[('period_type', '=', period_type)]", states=NON_EDITABLE_ON_DONE, string=u"Código 4ª actividad", ) other_fourth_activity_iae = fields.Char( string=u"Epígrafe I.A.E. 4ª actividad", states=NON_EDITABLE_ON_DONE, size=4, ) other_fifth_activity_code = fields.Many2one( comodel_name="l10n.es.aeat.mod303.report.activity.code", domain="[('period_type', '=', period_type)]", states=NON_EDITABLE_ON_DONE, string=u"Código 5ª actividad", ) other_fifth_activity_iae = fields.Char( string=u"Epígrafe I.A.E. 5ª actividad", states=NON_EDITABLE_ON_DONE, size=4, ) casilla_88 = fields.Float( string=u"[88] Total volumen operaciones", compute="_compute_casilla_88", help=u"Información adicional - Operaciones realizadas en el ejercicio" u" - Total volumen de operaciones ([80]+[81]+[93]+[94]+[83]+[84]+" u"[85]+[86]+[95]+[96]+[97]+[98]-[79]-[99])", store=True, ) @api.depends("date_start", "cuota_compensar") def _compute_exception_msg(self): super(L10nEsAeatMod303Report, self)._compute_exception_msg() for mod303 in self.filtered(lambda x: x.state != "draft"): # Get result from previous declarations, in order to identify if # there is an amount to compensate. prev_reports = mod303._get_previous_fiscalyear_reports( mod303.date_start ).filtered(lambda x: x.state not in ["draft", "cancelled"]) if not prev_reports: continue prev_report = min( prev_reports, key=lambda x: abs( fields.Date.to_date(x.date_end) - fields.Date.to_date(mod303.date_start) ), ) if prev_report.result_type == "C" and not mod303.cuota_compensar: if mod303.exception_msg: mod303.exception_msg += "\n" else: mod303.exception_msg = "" mod303.exception_msg += _( "In previous declarations this year you reported a " "Result Type 'To Compensate'. You might need to fill " "field '[67] Fees to compensate' in this declaration." ) @api.depends("company_id", "result_type") def _compute_counterpart_account_id(self): for record in self: code = ("%s%%" % _ACCOUNT_PATTERN_MAP.get(record.result_type, "4750"),) record.counterpart_account_id = self.env["account.account"].search( [("code", "like", code[0]), ("company_id", "=", record.company_id.id)], limit=1, ) @api.depends("period_type") def _compute_regularizacion_anual(self): for record in self: if record.period_type not in ("4T", "12"): record.regularizacion_anual = 0 @api.depends("period_type") def _compute_exonerated_390(self): for record in self: if record.period_type not in ("4T", "12"): record.exonerated_390 = "2" @api.depends("tax_line_ids", "tax_line_ids.amount") def _compute_total_devengado(self): casillas_devengado = (3, 6, 9, 11, 13, 15, 18, 21, 24, 26) for report in self: tax_lines = report.tax_line_ids.filtered( lambda x: x.field_number in casillas_devengado ) report.total_devengado = sum(tax_lines.mapped("amount")) @api.depends("tax_line_ids", "tax_line_ids.amount") def _compute_total_deducir(self): casillas_deducir = (29, 31, 33, 35, 37, 39, 41, 42, 43, 44) for report in self: tax_lines = report.tax_line_ids.filtered( lambda x: x.field_number in casillas_deducir ) report.total_deducir = sum(tax_lines.mapped("amount")) @api.depends("total_devengado", "total_deducir") def _compute_casilla_46(self): for report in self: report.casilla_46 = report.total_devengado - report.total_deducir @api.depends("porcentaje_atribuible_estado", "casilla_46") def _compute_atribuible_estado(self): for report in self: report.atribuible_estado = ( report.casilla_46 * report.porcentaje_atribuible_estado / 100.0 ) @api.depends( "atribuible_estado", "cuota_compensar", "regularizacion_anual", "casilla_77" ) def _compute_casilla_69(self): for report in self: report.casilla_69 = ( report.atribuible_estado + report.casilla_77 - report.cuota_compensar + report.regularizacion_anual ) @api.depends("casilla_69", "previous_result") def _compute_resultado_liquidacion(self): for report in self: report.resultado_liquidacion = report.casilla_69 - report.previous_result @api.depends("tax_line_ids", "tax_line_ids.amount") def _compute_casilla_88(self): for report in self: report.casilla_88 = sum( report.tax_line_ids.filtered( lambda x: x.field_number in (80, 81, 83, 84, 85, 86, 93, 94, 95, 96, 97, 98,) ).mapped("amount") ) - sum( report.tax_line_ids.filtered( lambda x: x.field_number in (79, 99,) ).mapped("amount") ) def _compute_allow_posting(self): for report in self: report.allow_posting = True @api.depends( "resultado_liquidacion", "period_type", "devolucion_mensual", ) def _compute_result_type(self): for report in self: if report.resultado_liquidacion == 0: report.result_type = "N" elif report.resultado_liquidacion > 0: report.result_type = "I" else: if report.devolucion_mensual or report.period_type in ("4T", "12"): report.result_type = "D" else: report.result_type = "C" @api.onchange("statement_type") def onchange_type(self): if self.statement_type != "C": self.previous_result = 0 def calculate(self): res = super(L10nEsAeatMod303Report, self).calculate() for mod303 in self: prev_reports = mod303._get_previous_fiscalyear_reports( mod303.date_start ).filtered(lambda x: x.state not in ["draft", "cancelled"]) if not prev_reports: continue prev_report = min( prev_reports, key=lambda x: abs( fields.Date.to_date(x.date_end) - fields.Date.to_date(mod303.date_start) ), ) if prev_report.result_type == "C": mod303.cuota_compensar = abs(prev_report.resultado_liquidacion) return res def button_confirm(self): """Check records""" msg = "" for mod303 in self: if mod303.result_type == "D" and not mod303.partner_bank_id: msg = _("Select an account for receiving the money") if msg: raise exceptions.Warning(msg) return super(L10nEsAeatMod303Report, self).button_confirm() @api.constrains("cuota_compensar") def check_qty(self): if self.filtered(lambda x: x.cuota_compensar < 0.0): raise exceptions.ValidationError( _("The fee to compensate must be indicated as a positive number.") ) def _get_tax_lines(self, date_start, date_end, map_line): """Don't populate results for fields 79-99 for reports different from last of the year one or when not exonerated of presenting model 390. """ if 79 <= map_line.field_number <= 99: if ( self.exonerated_390 == "2" or not self.has_operation_volume or self.period_type not in ("4T", "12") ): return self.env["account.move.line"] return super(L10nEsAeatMod303Report, self)._get_tax_lines( date_start, date_end, map_line, ) def _get_move_line_domain(self, date_start, date_end, map_line): """Changes dates to full year when the summary on last report of the year for the corresponding fields. Only field number is checked as the complete check for not bringing results is done on `_get_tax_lines`. """ if 79 <= map_line.field_number <= 99: date_start = date_start.replace(day=1, month=1) date_end = date_end.replace(day=31, month=12) return super(L10nEsAeatMod303Report, self)._get_move_line_domain( date_start, date_end, map_line, )
class AccountFiscalPosition(models.Model): _name = 'account.fiscal.position' _description = 'Fiscal Position' _order = 'sequence' sequence = fields.Integer() name = fields.Char(string='Fiscal Position', required=True) active = fields.Boolean(default=True, help="By unchecking the active field, you may hide a fiscal position without deleting it.") company_id = fields.Many2one('res.company', string='Company') account_ids = fields.One2many('account.fiscal.position.account', 'position_id', string='Account Mapping', copy=True) tax_ids = fields.One2many('account.fiscal.position.tax', 'position_id', string='Tax Mapping', copy=True) note = fields.Text('Notes', help="Legal mentions that have to be printed on the invoices.") auto_apply = fields.Boolean(string='Detect Automatically', help="Apply automatically this fiscal position.") vat_required = fields.Boolean(string='VAT required', help="Apply only if partner has a VAT number.") country_id = fields.Many2one('res.country', string='Country', help="Apply only if delivery or invoicing country match.") country_group_id = fields.Many2one('res.country.group', string='Country Group', help="Apply only if delivery or invocing country match the group.") state_ids = fields.Many2many('res.country.state', string='Federal States') zip_from = fields.Integer(string='Zip Range From', default=0) zip_to = fields.Integer(string='Zip Range To', default=0) # To be used in hiding the 'Federal States' field('attrs' in view side) when selected 'Country' has 0 states. states_count = fields.Integer(compute='_compute_states_count') @api.one def _compute_states_count(self): self.states_count = len(self.country_id.state_ids) @api.one @api.constrains('zip_from', 'zip_to') def _check_zip(self): if self.zip_from > self.zip_to: raise ValidationError(_('Invalid "Zip Range", please configure it properly.')) return True @api.model # noqa def map_tax(self, taxes, product=None, partner=None): result = self.env['account.tax'].browse() for tax in taxes: tax_count = 0 for t in self.tax_ids: if t.tax_src_id == tax: tax_count += 1 if t.tax_dest_id: result |= t.tax_dest_id if not tax_count: result |= tax return result @api.model def map_account(self, account): for pos in self.account_ids: if pos.account_src_id == account: return pos.account_dest_id return account @api.model def map_accounts(self, accounts): """ Receive a dictionary having accounts in values and try to replace those accounts accordingly to the fiscal position. """ ref_dict = {} for line in self.account_ids: ref_dict[line.account_src_id] = line.account_dest_id for key, acc in accounts.items(): if acc in ref_dict: accounts[key] = ref_dict[acc] return accounts @api.onchange('country_id') def _onchange_country_id(self): if self.country_id: self.zip_from = self.zip_to = self.country_group_id = False self.state_ids = [(5,)] self.states_count = len(self.country_id.state_ids) @api.onchange('country_group_id') def _onchange_country_group_id(self): if self.country_group_id: self.zip_from = self.zip_to = self.country_id = False self.state_ids = [(5,)] @api.model def _get_fpos_by_region(self, country_id=False, state_id=False, zipcode=False, vat_required=False): if not country_id: return False base_domain = [('auto_apply', '=', True), ('vat_required', '=', vat_required)] if self.env.context.get('force_company'): base_domain.append(('company_id', '=', self.env.context.get('force_company'))) null_state_dom = state_domain = [('state_ids', '=', False)] null_zip_dom = zip_domain = [('zip_from', '=', 0), ('zip_to', '=', 0)] null_country_dom = [('country_id', '=', False), ('country_group_id', '=', False)] if zipcode and zipcode.isdigit(): zipcode = int(zipcode) zip_domain = [('zip_from', '<=', zipcode), ('zip_to', '>=', zipcode)] else: zipcode = 0 if state_id: state_domain = [('state_ids', '=', state_id)] domain_country = base_domain + [('country_id', '=', country_id)] domain_group = base_domain + [('country_group_id.country_ids', '=', country_id)] # Build domain to search records with exact matching criteria fpos = self.search(domain_country + state_domain + zip_domain, limit=1) # return records that fit the most the criteria, and fallback on less specific fiscal positions if any can be found if not fpos and state_id: fpos = self.search(domain_country + null_state_dom + zip_domain, limit=1) if not fpos and zipcode: fpos = self.search(domain_country + state_domain + null_zip_dom, limit=1) if not fpos and state_id and zipcode: fpos = self.search(domain_country + null_state_dom + null_zip_dom, limit=1) # fallback: country group with no state/zip range if not fpos: fpos = self.search(domain_group + null_state_dom + null_zip_dom, limit=1) if not fpos: # Fallback on catchall (no country, no group) fpos = self.search(base_domain + null_country_dom, limit=1) return fpos or False @api.model def get_fiscal_position(self, partner_id, delivery_id=None): if not partner_id: return False # This can be easily overriden to apply more complex fiscal rules PartnerObj = self.env['res.partner'] partner = PartnerObj.browse(partner_id) # if no delivery use invoicing if delivery_id: delivery = PartnerObj.browse(delivery_id) else: delivery = partner # partner manually set fiscal position always win if delivery.property_account_position_id or partner.property_account_position_id: return delivery.property_account_position_id.id or partner.property_account_position_id.id # First search only matching VAT positions vat_required = bool(partner.vat) fp = self._get_fpos_by_region(delivery.country_id.id, delivery.state_id.id, delivery.zip, vat_required) # Then if VAT required found no match, try positions that do not require it if not fp and vat_required: fp = self._get_fpos_by_region(delivery.country_id.id, delivery.state_id.id, delivery.zip, False) return fp.id if fp else False
class AfipwsCertificateAlias(models.Model): _name = "afipws.certificate_alias" _description = "AFIP Distingish Name / Alias" _rec_name = "common_name" """ Para poder acceder a un servicio, la aplicación a programar debe utilizar un certificado de seguridad, que se obtiene en la web de afip. Entre otras cosas, el certificado contiene un Distinguished Name (DN) que incluye una CUIT. Cada DN será identificado por un "alias" o "nombre simbólico", que actúa como una abreviación. EJ alias: AFIP WS Prod - ADHOC SA EJ DN: C=ar, ST=santa fe, L=rosario, O=adhoc s.a., OU=it, SERIALNUMBER=CUIT 30714295698, CN=afip web services - adhoc s.a. """ common_name = fields.Char( 'Common Name', size=64, default='AFIP WS', help='Just a name, you can leave it this way', states={'draft': [('readonly', False)]}, readonly=True, required=True, ) key = fields.Text( 'Private Key', readonly=True, states={'draft': [('readonly', False)]}, ) company_id = fields.Many2one( 'res.company', 'Company', required=True, states={'draft': [('readonly', False)]}, readonly=True, default=lambda self: self.env.user.company_id, auto_join=True, index=True, ) country_id = fields.Many2one( 'res.country', 'Country', states={'draft': [('readonly', False)]}, readonly=True, required=True, ) state_id = fields.Many2one( 'res.country.state', 'State', states={'draft': [('readonly', False)]}, readonly=True, ) city = fields.Char( 'City', states={'draft': [('readonly', False)]}, readonly=True, required=True, ) department = fields.Char( 'Department', default='IT', states={'draft': [('readonly', False)]}, readonly=True, required=True, ) cuit = fields.Char( 'CUIT', compute='get_cuit', required=True, ) company_cuit = fields.Char( 'Company CUIT', size=16, states={'draft': [('readonly', False)]}, readonly=True, ) service_provider_cuit = fields.Char( 'Service Provider CUIT', size=16, states={'draft': [('readonly', False)]}, readonly=True, ) certificate_ids = fields.One2many( 'afipws.certificate', 'alias_id', 'Certificates', states={'cancel': [('readonly', True)]}, auto_join=True, ) service_type = fields.Selection( [('in_house', 'In House'), ('outsourced', 'Outsourced')], 'Service Type', default='in_house', required=True, readonly=True, states={'draft': [('readonly', False)]}, ) state = fields.Selection( [ ('draft', 'Draft'), ('confirmed', 'Confirmed'), ('cancel', 'Cancelled'), ], 'State', index=True, readonly=True, default='draft', help="* The 'Draft' state is used when a user is creating a new pair " "key. Warning: everybody can see the key." "\n* The 'Confirmed' state is used when the key is completed with " "public or private key." "\n* The 'Canceled' state is used when the key is not more used. " "You cant use this key again.") type = fields.Selection( [('production', 'Production'), ('homologation', 'Homologation')], 'Type', required=True, default='production', readonly=True, states={'draft': [('readonly', False)]}, ) @api.onchange('company_id') def change_company_name(self): if self.company_id: self.common_name = 'AFIP WS %s - %s' % (self.type, self.company_id.name) @api.multi @api.depends('company_cuit', 'service_provider_cuit', 'service_type') def get_cuit(self): for rec in self: if rec.service_type == 'outsourced': rec.cuit = rec.service_provider_cuit else: rec.cuit = rec.company_cuit @api.onchange('company_id') def change_company_id(self): if self.company_id: self.country_id = self.company_id.country_id.id self.state_id = self.company_id.state_id.id self.city = self.company_id.city self.company_cuit = self.company_id.cuit @api.multi def action_confirm(self): if not self.key: self.generate_key() self.write({'state': 'confirmed'}) return True @api.multi def generate_key(self, key_length=2048): """ """ # TODO reemplazar todo esto por las funciones nativas de pyafipws for rec in self: k = crypto.PKey() k.generate_key(crypto.TYPE_RSA, key_length) rec.key = crypto.dump_privatekey(crypto.FILETYPE_PEM, k) @api.multi def action_to_draft(self): self.write({'state': 'draft'}) return True @api.multi def action_cancel(self): self.write({'state': 'cancel'}) self.certificate_ids.write({'state': 'cancel'}) return True @api.multi def action_create_certificate_request(self): """ TODO agregar descripcion y ver si usamos pyafipsw para generar esto """ for record in self: req = crypto.X509Req() req.get_subject().C = self.country_id.code.encode( 'ascii', 'ignore') if self.state_id: req.get_subject().ST = self.state_id.name.encode( 'ascii', 'ignore') req.get_subject().L = self.city.encode('ascii', 'ignore') req.get_subject().O = self.company_id.name.encode( 'ascii', 'ignore') req.get_subject().OU = self.department.encode('ascii', 'ignore') req.get_subject().CN = self.common_name.encode('ascii', 'ignore') req.get_subject().serialNumber = 'CUIT %s' % self.cuit.encode( 'ascii', 'ignore') k = crypto.load_privatekey(crypto.FILETYPE_PEM, self.key) self.key = crypto.dump_privatekey(crypto.FILETYPE_PEM, k) req.set_pubkey(k) req.sign(k, 'sha256') csr = crypto.dump_certificate_request(crypto.FILETYPE_PEM, req) vals = { 'csr': csr, 'alias_id': record.id, } self.certificate_ids.create(vals) return True
class MaterialPurchaseRequisition(models.Model): _name = 'material.purchase.requisition' _description = 'Purchase Requisition' # _inherit = ['mail.thread', 'ir.needaction_mixin'] _inherit = ['mail.thread', 'mail.activity.mixin', 'portal.mixin'] # odoo11 _order = 'id desc' def unlink(self): for rec in self: if rec.state not in ('draft', 'cancel', 'reject'): raise Warning( _('You can not delete Purchase Requisition which is not in draft or cancelled or rejected state.')) return super(MaterialPurchaseRequisition, self).unlink() name = fields.Char( string='Number', index=True, readonly=1, ) state = fields.Selection([ ('draft', 'New'), ('dept_confirm', 'Waiting Department Approval'), ('ir_approve', 'Waiting IR Approved'), ('approve', 'Approved'), ('stock', 'Purchase Order Created'), ('receive', 'Received'), ('cancel', 'Cancelled'), ('reject', 'Rejected')], default='draft', track_visibility='onchange', ) request_date = fields.Date( string='Requisition Date', default=fields.Date.today(), required=True, ) department_id = fields.Many2one( 'hr.department', string='Department', required=True, copy=True, ) employee_id = fields.Many2one( 'hr.employee', string='Employee', default=lambda self: self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1), required=True, copy=True, ) approve_manager_id = fields.Many2one( 'hr.employee', string='Department Manager', readonly=True, copy=False, ) reject_manager_id = fields.Many2one( 'hr.employee', string='Department Manager Reject', readonly=True, ) approve_employee_id = fields.Many2one( 'hr.employee', string='Approved by', readonly=True, copy=False, ) reject_employee_id = fields.Many2one( 'hr.employee', string='Rejected by', readonly=True, copy=False, ) company_id = fields.Many2one( 'res.company', string='Company', default=lambda self: self.env.user.company_id, required=True, copy=True, ) location_id = fields.Many2one( 'stock.location', string='Source Location', copy=True, ) requisition_line_ids = fields.One2many( 'material.purchase.requisition.line', 'requisition_id', string='Purchase Requisitions Line', copy=True, ) date_end = fields.Date( string='Requisition Deadline', readonly=True, help='Last date for the product to be needed', copy=True, ) date_done = fields.Date( string='Date Done', readonly=True, help='Date of Completion of Purchase Requisition', ) managerapp_date = fields.Date( string='Department Approval Date', readonly=True, copy=False, ) manareject_date = fields.Date( string='Department Manager Reject Date', readonly=True, ) userreject_date = fields.Date( string='Rejected Date', readonly=True, copy=False, ) userrapp_date = fields.Date( string='Approved Date', readonly=True, copy=False, ) receive_date = fields.Date( string='Received Date', readonly=True, copy=False, ) reason = fields.Text( string='Reason for Requisitions', required=False, copy=True, ) analytic_account_id = fields.Many2one( 'account.analytic.account', string='Analytic Account', copy=True, ) dest_location_id = fields.Many2one( 'stock.location', string='Destination Location', required=False, copy=True, ) delivery_picking_id = fields.Many2one( 'stock.picking', string='Internal Picking', readonly=True, copy=False, ) requisiton_responsible_id = fields.Many2one( 'hr.employee', string='Requisition Responsible', copy=True, ) employee_confirm_id = fields.Many2one( 'hr.employee', string='Confirmed by', readonly=True, copy=False, ) confirm_date = fields.Date( string='Confirmed Date', readonly=True, copy=False, ) purchase_order_ids = fields.One2many( 'purchase.order', 'custom_requisition_id', string='Purchase Ordes', ) custom_picking_type_id = fields.Many2one( 'stock.picking.type', string='Picking Type', copy=False, ) @api.model def create(self, vals): name = self.env['ir.sequence'].next_by_code('purchase.requisition.seq') vals.update({ 'name': name }) res = super(MaterialPurchaseRequisition, self).create(vals) return res def requisition_confirm(self): for rec in self: manager_mail_template = self.env.ref( 'material_purchase_requisitions.email_confirm_material_purchase_requistion') rec.employee_confirm_id = rec.employee_id.id rec.confirm_date = fields.Date.today() rec.state = 'dept_confirm' if manager_mail_template: manager_mail_template.send_mail(self.id) def requisition_reject(self): for rec in self: rec.state = 'reject' rec.reject_employee_id = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) rec.userreject_date = fields.Date.today() def manager_approve(self): for rec in self: rec.managerapp_date = fields.Date.today() rec.approve_manager_id = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) employee_mail_template = self.env.ref( 'material_purchase_requisitions.email_purchase_requisition_iruser_custom') email_iruser_template = self.env.ref('material_purchase_requisitions.email_purchase_requisition') employee_mail_template.send_mail(self.id) email_iruser_template.send_mail(self.id) rec.state = 'ir_approve' def user_approve(self): for rec in self: rec.userrapp_date = fields.Date.today() rec.approve_employee_id = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) rec.state = 'approve' def reset_draft(self): for rec in self: rec.state = 'draft' def _prepare_pick_vals(self, line=False, stock_id=False): pick_vals = { 'product_id': line.product_id.id, 'product_uom_qty': line.qty, 'product_uom': line.uom.id, 'location_id': self.location_id.id, 'location_dest_id': self.dest_location_id.id, 'name': line.product_id.name, 'picking_type_id': self.custom_picking_type_id.id, 'picking_id': stock_id.id, 'custom_requisition_line_id': line.id } return pick_vals def _prepare_po_line(self, line=False, purchase_order=False): po_line_vals = { 'product_id': line.product_id.id, 'name': line.product_id.name, 'product_qty': line.qty, 'product_uom': line.uom.id, 'date_planned': fields.Date.today(), 'price_unit': line.product_id.lst_price, 'order_id': purchase_order.id, 'account_analytic_id': self.analytic_account_id.id, 'custom_requisition_line_id': line.id } return po_line_vals def request_stock(self): stock_obj = self.env['stock.picking'] move_obj = self.env['stock.move'] # internal_obj = self.env['stock.picking.type'].search([('code','=', 'internal')], limit=1) # internal_obj = self.env['stock.location'].search([('usage','=', 'internal')], limit=1) purchase_obj = self.env['purchase.order'] purchase_line_obj = self.env['purchase.order.line'] # if not internal_obj: # raise UserError(_('Please Specified Internal Picking Type.')) for rec in self: if not rec.requisition_line_ids: raise Warning(_('Please create some requisition lines.')) if any(line.requisition_type == 'internal' for line in rec.requisition_line_ids): if not rec.location_id.id: raise Warning(_('Select Source location under the picking details.')) if not rec.custom_picking_type_id.id: raise Warning(_('Select Picking Type under the picking details.')) if not rec.dest_location_id: raise Warning(_('Select Destination location under the picking details.')) # if not rec.employee_id.dest_location_id.id or not rec.employee_id.department_id.dest_location_id.id: # raise Warning(_('Select Destination location under the picking details.')) picking_vals = { 'partner_id': rec.employee_id.address_home_id.id, 'scheduled_date': fields.Date.today(), 'location_id': rec.location_id.id, 'location_dest_id': rec.dest_location_id and rec.dest_location_id.id or rec.employee_id.dest_location_id.id or rec.employee_id.department_id.dest_location_id.id, 'picking_type_id': rec.custom_picking_type_id.id, # internal_obj.id, 'note': rec.reason, 'custom_requisition_id': rec.id, 'origin': rec.name, } stock_id = stock_obj.sudo().create(picking_vals) delivery_vals = { 'delivery_picking_id': stock_id.id, } rec.write(delivery_vals) po_dict = {} for line in rec.requisition_line_ids: if line.requisition_type == 'internal': pick_vals = rec._prepare_pick_vals(line, stock_id) move_id = move_obj.sudo().create(pick_vals) else: if not line.partner_id: raise Warning(_('PLease Enter Atleast One Vendor on Requisition Lines')) for partner in line.partner_id: if partner not in po_dict: po_vals = { 'partner_id': partner.id, 'currency_id': rec.env.user.company_id.currency_id.id, 'date_order': fields.Date.today(), 'company_id': rec.env.user.company_id.id, 'custom_requisition_id': rec.id, 'origin': rec.name, } purchase_order = purchase_obj.create(po_vals) po_dict.update({partner: purchase_order}) po_line_vals = rec._prepare_po_line(line, purchase_order) # { # 'product_id': line.product_id.id, # 'name':line.product_id.name, # 'product_qty': line.qty, # 'product_uom': line.uom.id, # 'date_planned': fields.Date.today(), # 'price_unit': line.product_id.lst_price, # 'order_id': purchase_order.id, # 'account_analytic_id': rec.analytic_account_id.id, # } purchase_line_obj.sudo().create(po_line_vals) else: purchase_order = po_dict.get(partner) po_line_vals = rec._prepare_po_line(line, purchase_order) # po_line_vals = { # 'product_id': line.product_id.id, # 'name':line.product_id.name, # 'product_qty': line.qty, # 'product_uom': line.uom.id, # 'date_planned': fields.Date.today(), # 'price_unit': line.product_id.lst_price, # 'order_id': purchase_order.id, # 'account_analytic_id': rec.analytic_account_id.id, # } purchase_line_obj.sudo().create(po_line_vals) rec.state = 'stock' def action_received(self): for rec in self: rec.receive_date = fields.Date.today() rec.state = 'receive' def action_cancel(self): for rec in self: rec.state = 'cancel' @api.onchange('employee_id') def set_department(self): for rec in self: rec.department_id = rec.employee_id.department_id.id rec.dest_location_id = rec.employee_id.dest_location_id.id or rec.employee_id.department_id.dest_location_id.id def show_picking(self): for rec in self: res = self.env.ref('stock.action_picking_tree_all') res = res.read()[0] res['domain'] = str([('custom_requisition_id', '=', rec.id)]) return res def action_show_po(self): for rec in self: purchase_action = self.env.ref('purchase.purchase_rfq') purchase_action = purchase_action.read()[0] purchase_action['domain'] = str([('custom_requisition_id', '=', rec.id)]) return purchase_action
class MeliCampaignRecord(models.Model): _name = 'meli.campaign.record' _description = u'Registros de Campañas MELI' campaign_id = fields.Many2one('meli.campaign', u'Campaña', required=True, readonly=True, states={'draft':[('readonly',False)]}, ondelete="restrict") pricelist_id = fields.Many2one('product.pricelist', u'Tarifa de Venta', required=True, ondelete="restrict", readonly=False, states={'done':[('readonly', True)], 'rejected':[('readonly', True)]}) name = fields.Char(u'Nombre', required=True, readonly=True, states={'draft':[('readonly',False)]}) description = fields.Text(string=u'Descripcion', readonly=True, states={'draft':[('readonly',False)]}) line_ids = fields.One2many('meli.campaign.record.line', 'meli_campaign_id', u'Productos en Oferta', copy=False, auto_join=True, readonly=False, states={'done':[('readonly', True)], 'rejected':[('readonly', True)]}) state = fields.Selection([ ('draft','Borrador'), ('pending_approval','Enviado a Meli/Esperando Aprobacion'), ('published','Publicado en MELI'), ('approved','Aprobado en MELI'), ('done','Campaña Terminada'), ('rejected','Cancelado'), ], string=u'Estado', index=True, readonly=True, default = u'draft', ) @api.multi def action_set_products(self): self.ensure_one() wizard_model = self.env['wizard.set.products.campaign'] wizard = wizard_model.create({ 'meli_campaign_id': self.id, 'action_type': self.env.context.get('action_type') or 'set', }) action = self.env.ref('meli_oerp.action_wizard_set_products_campaign').read()[0] action['res_id'] = wizard.id return action @api.multi def action_publish_to_meli(self): self.ensure_one() warning_model = self.env['warning'] messages = self.line_ids.filtered(lambda x: x.state == 'draft').action_publish_to_meli() state = 'published' #si algun producto se quedo esperando aprobacion, #el estado general sera esperando aprobacion de Meli if self.line_ids.filtered(lambda x: x.state == 'pending_approval'): state = 'pending_approval' if messages: return warning_model.info(title='Ofertas', message=u"\n".join(messages)) self.state = state return True @api.multi def action_done_publish(self): self.mapped('line_ids').filtered(lambda x: x.state != 'rejected').write({'state': 'done'}) self.write({'state': 'done'}) return True @api.multi def action_cancel_publish(self): self.ensure_one() warning_model = self.env['warning'] messages = self.line_ids.filtered(lambda x: x.state != 'rejected').action_unpublish_to_meli() if messages: return warning_model.info(title='Cancelar Oferta', message=u"\n".join(messages)) self.write({'state': 'rejected'}) return True @api.multi def action_recompute_prices(self): self.ensure_one() #pasar la lista de precios y actualizar los precios for line in self.with_context(pricelist=self.pricelist_id.id).line_ids: line.write({ 'price_unit': line.product_template_id.list_price, 'list_price': line.product_template_id.price, 'meli_price': line.product_template_id.price, }) return True @api.multi def action_update_prices_to_meli(self): warning_model = self.env['warning'] #los nuevos productos publicarlos messages = self.mapped('line_ids').filtered(lambda x: x.state == 'draft').action_publish_to_meli() #actualizar todas las lineas que esten activas messages.extend(self.mapped('line_ids').filtered(lambda x: x.state in ('pending_approval', 'published')).action_update_to_meli()) if messages: return warning_model.info(title='Actualizar Ofertas', message=u"\n".join(messages)) return True @api.multi def _find_create_campaign_detail(self, response_json): campaign_line_model = self.env['meli.campaign.record.line'] campaign_line = campaign_line_model.browse() messages = [] ProductTemplateModel = self.env['product.template'] item_id = response_json.get('item_id') if not item_id: messages.append("No se encuentra un producto con ID: %s" % item_id) return campaign_line, messages product_template = ProductTemplateModel.search([('meli_id', '=', item_id)], limit=1) if not product_template: messages.append("No se encuentra un producto con ID: %s" % item_id) return campaign_line, messages vals = campaign_line_model._prepare_vals_to_update_from_meli(response_json, product_template) #buscar una linea de campaña para el producto # puede darse el caso que en odoo crean varios registros para la misma campaña # es decir 1 registro con productos de 1 categoria especial con un % de descuento # y otro registro para otros productos con otro descuento # pero al final a meli se suben en la misma campaña # asi que en caso de no existir en la oferta actual, # buscar si esta en otra oferta ese producto pero con la misma campaña domain = [ ('product_template_id', '=', product_template.id), ('meli_campaign_id', '=', self.id), ] campaign_line = campaign_line_model.search(domain, limit=1) if campaign_line: campaign_line.write(vals) else: domain = [ ('product_template_id', '=', product_template.id), ('meli_campaign_id.campaign_id', '=', self.campaign_id.id), ] campaign_line = campaign_line_model.search(domain, limit=1) if campaign_line: messages.append("El producto: %s ID: %s esta en otro registro de campaña: %s ID: %s, se actualizo dicho registro" % (product_template.name, item_id, campaign_line.meli_campaign_id.name, campaign_line.meli_campaign_id.id)) campaign_line.write(vals) else: messages.append("El producto: %s ID: %s No existe en la campaña: %s ID: %s, se creara" % (product_template.name, item_id, self.name, self.id)) vals.update({ 'meli_campaign_id': self.id, 'product_template_id': product_template.id, }) campaign_line = campaign_line_model.create(vals) return campaign_line, messages @api.multi def _query_iterate_campaign(self, total_downloaded=0, offset=0): meli_util_model = self.env['meli.util'] campaign_line_model = self.env['meli.campaign.record.line'] campaign_lines = campaign_line_model.browse() company = self.env.user.company_id meli = meli_util_model.get_new_instance(company) message_list = [] campaign_query = "/users/%s/deals/%s/proposed_items/search" % (company.mercadolibre_seller_id, self.campaign_id.meli_id) params = { 'access_token': meli.access_token, } if offset: params['offset'] = str(offset).strip() response = meli.get(campaign_query, params) orders_json = response.json() if "error" in orders_json: _logger.error(orders_json["error"]) if orders_json["message"] == "invalid_token": _logger.error(orders_json["message"]) message_list.append(orders_json["message"]) return campaign_lines, message_list counter = 0 total = 0 if "results" in orders_json: total_downloaded += len(orders_json["results"]) if "paging" in orders_json: if "total" in orders_json["paging"]: counter = offset + 1 total = orders_json["paging"]["total"] if orders_json["paging"]["total"] == 0: return campaign_lines, message_list else: if total_downloaded < total: offset += orders_json["paging"]["limit"] else: offset = 0 if "results" in orders_json: for response_json in orders_json["results"]: _logger.info("Procesando Producto %s de %s", counter, total) counter += 1 campaign_line, msj = self._find_create_campaign_detail(response_json) campaign_lines |= campaign_line message_list.extend(msj) if offset > 0: campaign_lines_tmp, message_list_tmp = self._query_iterate_campaign(total_downloaded, offset) message_list.extend(message_list_tmp) campaign_lines |= campaign_lines_tmp return campaign_lines, message_list @api.multi def _action_recompute_state(self): self.ensure_one() new_state = '' # todas las lineas tienen el mismo estado, la cabecera debe tener el mismo estado states_lines = self.line_ids.mapped('state') for state in ['pending_approval', 'published', 'approved', 'done', 'rejected']: if all([line_state == state for line_state in states_lines]): new_state = state break if not new_state: if 'done' in states_lines: new_state = 'done' if 'approved' in states_lines: new_state = 'approved' elif 'published' in states_lines: new_state = 'published' elif 'pending_approval' in states_lines: new_state = 'pending_approval' if new_state and self.state != new_state: self.state = new_state @api.multi def action_download_campaign(self): self.ensure_one() warning_model = self.env['warning'] campaign_lines, message_list = self._query_iterate_campaign() if campaign_lines: campaigns = campaign_lines.mapped('meli_campaign_id') for campaign in campaigns: campaign._action_recompute_state() res = True if message_list: res = warning_model.info(title='Mensajes de informacion', message="Se obtuvieron los siguientes mensajes en la actualizacion de la Oferta: %s(%s)" % (self.name, self.campaign_id.name), message_html="<br/>".join(message_list)) return res @api.multi def unlink(self): for campaign in self: if campaign.state not in ('draft',): raise UserError(u"No puede Eliminar esta Campaña, intente cancelarla") res = super(MeliCampaignRecord, self).unlink() return res