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
Exemple #3
0
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']
Exemple #4
0
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
Exemple #5
0
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")
Exemple #7
0
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()
Exemple #8
0
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
Exemple #10
0
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 ""
Exemple #14
0
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 ?
Exemple #15
0
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()
Exemple #16
0
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)
Exemple #17
0
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)
Exemple #20
0
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
Exemple #22
0
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
Exemple #26
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,
        )
Exemple #27
0
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
Exemple #28
0
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