Esempio n. 1
0
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
Esempio n. 2
0
class BaseModuleRecord(models.TransientModel):
    _name = 'base.module.record'
    _description = "Base Module Record"

    @api.model
    def _get_default_objects(self):
        names = ('ir.ui.view', 'ir.ui.menu', 'ir.model', 'ir.model.fields',
                 'ir.model.access', 'res.partner', 'res.partner.address',
                 'res.partner.category', 'workflow', 'workflow.activity',
                 'workflow.transition', 'ir.actions.server',
                 'ir.server.object.lines')
        return self.env['ir.model'].search([('model', 'in', names)])

    check_date = fields.Datetime('Record from Date',
                                 required=True,
                                 default=fields.Datetime.now)
    objects = fields.Many2many('ir.model',
                               'base_module_record_object_rel',
                               'objects',
                               'model_id',
                               'Objects',
                               default=_get_default_objects)
    filter_cond = fields.Selection(
        [('created', 'Created'), ('modified', 'Modified'),
         ('created_modified', 'Created & Modified')],
        'Records only',
        required=True,
        default='created')

    @api.multi
    def record_objects(self):
        data = self.read([])[0]
        check_date = data['check_date']
        filter_cond = data['filter_cond']
        mod_obj = self.env['ir.model']
        recording_data = []
        for obj_id in data['objects']:
            obj_name = (mod_obj.browse(obj_id)).model
            obj_pool = self.env[obj_name]
            if filter_cond == 'created':
                search_condition = [('create_date', '>', check_date)]
            elif filter_cond == 'modified':
                search_condition = [('write_date', '>', check_date)]
            elif filter_cond == 'created_modified':
                search_condition = [
                    '|', ('create_date', '>', check_date),
                    ('write_date', '>', check_date)
                ]
            if '_log_access' in dir(obj_pool):
                if not (obj_pool._log_access):
                    search_condition = []
                if '_auto' in dir(obj_pool):
                    if not obj_pool._auto:
                        continue
            search_ids = obj_pool.search(search_condition)
            for s_id in search_ids:
                dbname = self.env.cr.dbname
                args = (dbname, self.env.user.id, obj_name, 'copy', s_id.id,
                        {})
                recording_data.append(('query', args, {}, s_id.id))
        if len(recording_data):
            res_id = self.env.ref('base_module_record.info_start_form_view').id
            self = self.with_context({'recording_data': recording_data})
            return {
                'name': _('Module Recording'),
                'context': self._context,
                'view_type': 'form',
                'view_mode': 'form',
                'res_model': 'base.module.record.objects',
                'views': [(res_id, 'form')],
                'type': 'ir.actions.act_window',
                'target': 'new',
            }
        res_id = self.env.ref(
            'base_module_record.module_recording_message_view').id
        return {
            'name': _('Module Recording'),
            'context': self._context,
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'base.module.record.objects',
            'views': [(res_id, 'form')],
            'type': 'ir.actions.act_window',
            'target': 'new',
        }
Esempio n. 3
0
class SaleInvoicePlan(models.Model):
    _name = "sale.invoice.plan"
    _description = "Invoice Planning Detail"
    _order = "installment"

    sale_id = fields.Many2one(
        comodel_name="sale.order",
        string="Sales Order",
        index=True,
        readonly=True,
        ondelete="cascade",
    )
    partner_id = fields.Many2one(
        comodel_name="res.partner",
        string="Customer",
        related="sale_id.partner_id",
        store=True,
        index=True,
    )
    state = fields.Selection(
        [
            ("draft", "Quotation"),
            ("sent", "Quotation Sent"),
            ("sale", "Sales Order"),
            ("done", "Locked"),
            ("cancel", "Cancelled"),
        ],
        string="Status",
        related="sale_id.state",
        store=True,
        index=True,
    )
    installment = fields.Integer(string="Installment")
    plan_date = fields.Date(string="Plan Date", required=True)
    invoice_type = fields.Selection(
        [("advance", "Advance"), ("installment", "Installment")],
        string="Type",
        required=True,
        default="installment",
    )
    last = fields.Boolean(
        string="Last Installment",
        compute="_compute_last",
        help="Last installment will create invoice use remaining amount",
    )
    percent = fields.Float(
        string="Percent",
        digits="Product Unit of Measure",
        help="This percent will be used to calculate new quantity",
    )
    invoice_move_ids = fields.Many2many(
        "account.move",
        relation="sale_invoice_plan_invoice_rel",
        column1="plan_id",
        column2="move_id",
        string="Invoices",
        readonly=True,
    )
    to_invoice = fields.Boolean(
        string="Next Invoice",
        compute="_compute_to_invoice",
        help="If this line is ready to create new invoice",
    )
    invoiced = fields.Boolean(
        string="Invoice Created",
        compute="_compute_invoiced",
        help="If this line already invoiced",
    )
    _sql_constraint = [(
        "unique_instalment",
        "UNIQUE (sale_id, installment)",
        "Installment must be unique on invoice plan",
    )]

    def _compute_to_invoice(self):
        """ If any invoice is in draft/open/paid do not allow to create inv.
            Only if previous to_invoice is False, it is eligible to_invoice.
        """
        for rec in self:
            rec.to_invoice = False
        for rec in self.sorted("installment"):
            if rec.state != "sale":  # Not confirmed, no to_invoice
                continue
            if not rec.invoiced:
                rec.to_invoice = True
                break

    def _compute_invoiced(self):
        for rec in self:
            invoiced = rec.invoice_move_ids.filtered(lambda l: l.state in
                                                     ("draft", "posted"))
            rec.invoiced = invoiced and True or False

    def _compute_last(self):
        for rec in self:
            last = max(rec.sale_id.invoice_plan_ids.mapped("installment"))
            rec.last = rec.installment == last

    def _compute_new_invoice_quantity(self, invoice_move):
        self.ensure_one()
        if self.last:  # For last install, let the system do the calc.
            return
        percent = self.percent
        move = invoice_move.with_context({"check_move_validity": False})
        for line in move.invoice_line_ids:
            assert (len(line.sale_line_ids) >=
                    0), "No matched order line for invoice line"
            order_line = fields.first(line.sale_line_ids)
            if order_line.is_downpayment:  # based on 1 unit
                line.write({"quantity": -percent / 100})
            else:
                plan_qty = order_line.product_uom_qty * (percent / 100)
                prec = order_line.product_uom.rounding
                if float_compare(plan_qty, line.quantity, prec) == 1:
                    raise ValidationError(
                        _("Plan quantity: %s, exceed invoiceable quantity: %s"
                          "\nProduct should be delivered before invoice") %
                        (plan_qty, line.quantity))
                line.write({"quantity": plan_qty})
        # Call this method to recompute dr/cr lines
        move._move_autocomplete_invoice_lines_values()
class HrPayrollReport(models.Model):
    _name = "hr.payroll.report"
    _description = "Payroll Analysis Report"
    _auto = False
    _rec_name = 'date_from'
    _order = 'date_from desc'

    count = fields.Integer('# Payslip', group_operator="sum", readonly=True)
    count_work = fields.Integer('Work Days',
                                group_operator="sum",
                                readonly=True)
    count_work_hours = fields.Integer('Work Hours',
                                      group_operator="sum",
                                      readonly=True)
    count_leave = fields.Integer('Days of Paid Time Off',
                                 group_operator="sum",
                                 readonly=True)
    count_leave_unpaid = fields.Integer('Days of Unpaid Time Off',
                                        group_operator="sum",
                                        readonly=True)
    count_unforeseen_absence = fields.Integer('Days of Unforeseen Absence',
                                              group_operator="sum",
                                              readonly=True)

    name = fields.Char('Payslip Name', readonly=True)
    date_from = fields.Date('Start Date', readonly=True)
    date_to = fields.Date('End Date', readonly=True)
    company_id = fields.Many2one('res.company', 'Company', readonly=True)

    employee_id = fields.Many2one('hr.employee', 'Employee', readonly=True)
    department_id = fields.Many2one('hr.department',
                                    'Department',
                                    readonly=True)
    job_id = fields.Many2one('hr.job', 'Job Position', readonly=True)
    number_of_days = fields.Float('Number of Days', readonly=True)
    number_of_hours = fields.Float('Number of Hours', readonly=True)
    net_wage = fields.Float('Net Wage', readonly=True)
    basic_wage = fields.Float('Basic Wage', readonly=True)
    gross_wage = fields.Float('Gross Wage', readonly=True)
    leave_basic_wage = fields.Float('Basic Wage for Time Off', readonly=True)

    work_code = fields.Many2one('hr.work.entry.type',
                                'Work type',
                                readonly=True)
    work_type = fields.Selection([('1', 'Regular Working Day'),
                                  ('2', 'Paid Time Off'),
                                  ('3', 'Unpaid Time Off')],
                                 string='Work, (un)paid Time Off',
                                 readonly=True)

    def init(self):
        query = """
            SELECT
                p.id as id,
                CASE WHEN wd.id = min_id.min_line THEN 1 ELSE 0 END as count,
                CASE WHEN wet.is_leave THEN 0 ELSE wd.number_of_days END as count_work,
                CASE WHEN wet.is_leave THEN 0 ELSE wd.number_of_hours END as count_work_hours,
                CASE WHEN wet.is_leave and wd.amount <> 0 THEN wd.number_of_days ELSE 0 END as count_leave,
                CASE WHEN wet.is_leave and wd.amount = 0 THEN wd.number_of_days ELSE 0 END as count_leave_unpaid,
                CASE WHEN wet.is_unforeseen THEN wd.number_of_days ELSE 0 END as count_unforeseen_absence,
                CASE WHEN wet.is_leave THEN wd.amount ELSE 0 END as leave_basic_wage,
                p.name as name,
                p.date_from as date_from,
                p.date_to as date_to,
                e.id as employee_id,
                e.department_id as department_id,
                c.job_id as job_id,
                e.company_id as company_id,
                wet.id as work_code,
                CASE WHEN wet.is_leave IS NOT TRUE THEN '1' WHEN wd.amount = 0 THEN '3' ELSE '2' END as work_type,
                wd.number_of_days as number_of_days,
                wd.number_of_hours as number_of_hours,
                CASE WHEN wd.id = min_id.min_line THEN pln.total ELSE 0 END as net_wage,
                CASE WHEN wd.id = min_id.min_line THEN plb.total ELSE 0 END as basic_wage,
                CASE WHEN wd.id = min_id.min_line THEN plg.total ELSE 0 END as gross_wage
            FROM
                (SELECT * FROM hr_payslip WHERE state IN ('done', 'paid')) p
                    left join hr_employee e on (p.employee_id = e.id)
                    left join hr_payslip_worked_days wd on (wd.payslip_id = p.id)
                    left join hr_work_entry_type wet on (wet.id = wd.work_entry_type_id)
                    left join (select payslip_id, min(id) as min_line from hr_payslip_worked_days group by payslip_id) min_id on (min_id.payslip_id = p.id)
                    left join hr_payslip_line pln on (pln.slip_id = p.id and  pln.code = 'NET')
                    left join hr_payslip_line plb on (plb.slip_id = p.id and plb.code = 'BASIC')
                    left join hr_payslip_line plg on (plg.slip_id = p.id and plg.code = 'GROSS')
                    left join hr_contract c on (p.contract_id = c.id)
            GROUP BY
                e.id,
                e.department_id,
                e.company_id,
                wd.id,
                wet.id,
                p.id,
                p.name,
                p.date_from,
                p.date_to,
                pln.total,
                plb.total,
                plg.total,
                min_id.min_line,
                c.id"""
        tools.drop_view_if_exists(self.env.cr, self._table)
        self.env.cr.execute(
            sql.SQL("CREATE or REPLACE VIEW {} as ({})").format(
                sql.Identifier(self._table), sql.SQL(query)))
Esempio n. 5
0
class deposito_serivice_products(models.Model):
    _name = "deposito.service.products"
    _description = "Servicios Deposito"
    _order = "id ASC"

    name = fields.Char()
    deposito_srv_id = fields.Many2one(comodel_name='deposito.service.base',
                                      string='Carpeta Relacionada')
    state = fields.Selection(
        [
            ('draft', 'Borrador'),
            ('confirm', 'Confirmado'),
            ('inprocess', 'En proceso'),
            ('cancel', 'Cancelado'),
            ('done', 'Realizado'),
        ],
        string='Status',
        index=True,
        readonly=True,
        default='draft',
        track_visibility='onchange',
        copy=False,
    )
    product_type = fields.Selection([('propio', 'Propio'),
                                     ('terceros', 'Terceros')],
                                    string='Origen del Servicio')
    partner_invoice_id = fields.Many2one(comodel_name='res.partner',
                                         string='Cliente')
    pricelist_id = fields.Many2one('product.pricelist.item', string='Tarifa')
    product_id = fields.Many2one(comodel_name='product.product',
                                 string='Servicio',
                                 domain=[('product_tmpl_id.type', '=',
                                          'service'), ('sale_ok', '=', True)],
                                 required=False,
                                 change_default=True,
                                 ondelete='restrict')
    supplier_id = fields.Many2one(comodel_name='res.partner',
                                  string='Proveedor',
                                  domain=[('supplier', '=', True)])
    origin_id = fields.Many2one(comodel_name='res.partner.address.ext',
                                string='Origen')
    destiny_id = fields.Many2one(comodel_name='res.partner.address.ext',
                                 string='Destino')
    matricula = fields.Char(string=u'Matricula')
    matricula_dos_id = fields.Many2one(comodel_name='fleet.vehicle',
                                       string=u'Matrícula dos')
    vehicle_id = fields.Many2one(comodel_name='fleet.vehicle',
                                 string=u'Matrícula',
                                 domain=[('is_ras_property', '=', True)])
    vehicle_type = fields.Selection(related='vehicle_id.vehicle_type',
                                    type='char',
                                    readonly=True)
    driver_id = fields.Many2one('hr.employee',
                                'Chofer',
                                help=u'Chofer del Vehículo')
    chofer = fields.Char('Chofer')
    matricula_dos_fletero = fields.Char(string='Matricula Dos Fletero')
    matricula_fletero = fields.Many2one(comodel_name='fleet.vehicle',
                                        string='Matricula Fletero',
                                        domain=[('is_ras_property', '=', False)
                                                ])
    currency_id = fields.Many2one(comodel_name='res.currency', string='Moneda')
    importe = fields.Float(string='Importe')
    valor_compra_currency_id = fields.Many2one(comodel_name='res.currency',
                                               string='Moneda Compra')
    valor_compra = fields.Monetary(string='Valor Compra',
                                   currency_field='valor_compra_currency_id')
    currency_id_chofer = fields.Many2one(comodel_name='res.currency',
                                         string='Moneda Comisión Chofer')
    driver_commission = fields.Float('Comisión de chofer')

    is_invoiced = fields.Boolean(
        'Facturable',
        help='Marque esta casilla si este servicio no se factura',
        default=True)
    is_outgoing = fields.Boolean(
        '¿Es Gasto?',
        help='Marque esta casilla si este servicio es un Gasto',
        default=True)
    invoiced = fields.Boolean(string='¿Facturado?', copy=False)
    oc = fields.Char(string='Orden de Compra')
    invoiced_rejected = fields.Boolean(string='Factura Rechazada')
    supplier_ids = fields.One2many('rt.service.product.supplier',
                                   'rt_deposito_product_id',
                                   'Proveedores',
                                   copy=True)
    start = fields.Datetime('Inicio', required=True)
    stop = fields.Datetime('Fin', required=True)
    action_type_id = fields.Many2one('tipo.accion', string="Tipo de Acción")
    alquilado = fields.Boolean(string='Alquilado', track_visibility='always')
    partner_seller_id = fields.Many2one(comodel_name='res.partner',
                                        string='Vendedor',
                                        domain=[('seller', '=', True)],
                                        track_visibility='always')
    currency_id_vendedor = fields.Many2one(comodel_name='res.currency',
                                           string='Moneda',
                                           track_visibility='always')
    seller_commission = fields.Float(string='Comisión Vendedor',
                                     track_visibility='always')
    load_type = fields.Selection([('bulk', 'Bulk-Carga Suelta'),
                                  ('contenedor', 'Contenedor'),
                                  ('liquido_granel', u'Granel Líquido'),
                                  ('solido_granel', u'Granel Solido')],
                                 string='Tipo de Carga')
    container_type = fields.Many2one(comodel_name='fleet.vehicle',
                                     string='Tipo de Contenedor')
    container_number = fields.Char(string=u'Número de contenedor', size=13)
    make_container_number_invisible = fields.Boolean(string='Exception',
                                                     default=False)
    container_number_exception = fields.Char(
        string=u'Nº de contenedor Excepción', size=13)
    valid_cointaner_number_text = fields.Boolean(
        help=
        'Este booleano es para que se muestre un texto si el número de container es válido'
    )
    invalid_cointaner_number_text = fields.Boolean(
        help=
        'Este booleano es para que se muestre un texto si el número de container no es válido'
    )

    @api.onchange('driver_commission')
    def carga_linea_comision(self):
        for rec in self:
            if rec.driver_commission:
                self.genera_comision_chofer(linea=rec, chofer=rec.driver_id)

    def genera_comision_chofer(self, linea, chofer):
        """
            Se generan comisiones si se cumple la siguiente casuistica:
            Chofer Pertenece a categoria 'Camion Grande' A3
            Producto = Flete
            Tipo de Accion = Viaje, Retiro de Vacío, Ingreso Cargado, Devolución de Vacío, Retiro de Cargado
            PARA CHOFERES DE CATEGORIA A2 (CAMION CHICO) LA COMISION ES EVENTUAL - NORMALMENTE NO CORRESPONDE
            :return:
            """
        print(
            '-------------------------entro a la funcion genera_comision_chofer---------------------------------'
        )
        flete = 'Flete'
        hr_job_obj = self.env['hr.job']
        action_type_obj = self.env['tipo.accion']
        categoria_corresponde_comision = hr_job_obj.search([
            ('x_studio_categora_mtss', '=', 'A3 - Chofer de semirremolque')
        ])
        categoria_comision_opcional = hr_job_obj.search([
            ('x_studio_categora_mtss', '=',
             'A2 - Chofer de Camión y Camioneta')
        ])
        tipo_accion_corresponde_comision = action_type_obj.search([
            ('corresponde_comision', '=', True)
        ])
        if chofer.job_id.id in categoria_corresponde_comision.ids and linea.product_id.name == flete and linea.action_type_id.id in tipo_accion_corresponde_comision.ids:
            # Corresponde crear la comision
            linea.add_driver_commission()
        else:
            linea.add_driver_commission()

    @api.onchange('product_type', 'vehicle_id', 'matricula_dos_id')
    def _onchange_vehicle(self):
        """
            Funcion temporaria que impide al usuario cargas vehiculos que no debe
            :return:
            """
        domain = {}
        res = {}
        employee_obj = self.env['hr.employee']
        condiciones_busqueda = []
        condiciones_busqueda.append(('category_ids.name', '=', 'Chofer'))
        if condiciones_busqueda:
            employee = employee_obj.search(condiciones_busqueda)
        else:
            employee = employee_obj.search([])

        fleet_obj = self.env['fleet.vehicle']
        fleet_vehicle_id = fleet_obj.search([
            ('state_id', 'in', ('Tractores', 'Camiones', 'Camionetas'))
        ])
        fleet_matricula_dos_id = fleet_obj.search([
            ('state_id', 'in', 'Semi Remolques y Remolques')
        ])

        domain = {
            'driver_id': [('id', 'in', employee.ids)],
            'matricula_dos_id': [('id', 'in', fleet_matricula_dos_id.ids)],
            'vehicle_id': [('id', 'in', fleet_vehicle_id.ids)],
        }

        if self.vehicle_id:
            if self.vehicle_id.state_id.name not in ('Tractores', 'Camiones',
                                                     'Camionetas'):
                self.vehicle_id = False
                raise Warning(
                    'El vehiculo debe tener una matricula \n No puede elegir un contenedor \n Selecione: Tractores, Camiones o Camionetas'
                )

        if self.matricula_dos_id:
            if self.matricula_dos_id.state_id.name not in (
                    'Semi Remolques y Remolques'):
                self.matricula_dos_id = False
                raise Warning(
                    'Solo puede selecionar Semi Remolques y Remolques')

        if self.vehicle_id:
            self.driver_id = self.vehicle_id.driver_id.id

        if not self.currency_id_chofer:
            self.currency_id_chofer = 46

        if domain:
            res['domain'] = domain
        return res

    @api.onchange('container_number')
    def check_container_number(self):
        for rec in self:
            # Valida existencia
            if rec.container_number != False:
                # Valida largo correcto
                if len(rec.container_number) != 13:
                    rec.container_number = False
                    return {
                        'warning': {
                            'title':
                            "Error",
                            'message':
                            "Se espera un número de 13 cifras ej: BMOU-123456-7"
                        }
                    }
                # Valida existencia de - para poder realizar split
                if rec.container_number.count('-') != 2:
                    rec.container_number = False
                    return {
                        'warning': {
                            'title': "Error",
                            'message':
                            "Formato inválido, se espera BMOU-123456-7"
                        }
                    }
                # letras_c,numeros_c,digitov_c = rec.container_number.split("-") Si es necesario utlizar para verificar otras cosas
                string_container, numeros_container, digitov_container = rec.container_number.split(
                    "-")
                if not string_container.isalpha():
                    rec.container_number = False
                    return {
                        'warning': {
                            'title':
                            "Error",
                            'message':
                            "Se espera un número de 13 cifras ej: BMOU-123456-7"
                        }
                    }
                try:
                    type(int(numeros_container)) == int
                except ValueError:
                    rec.container_number = False
                    return {
                        'warning': {
                            'title':
                            "Error",
                            'message':
                            "Se espera un número de 13 cifras ej: BMOU-123456-7"
                        }
                    }
                try:
                    type(int(numeros_container)) == int
                except ValueError:
                    rec.container_number = False
                    return {
                        'warning': {
                            'title':
                            "Error",
                            'message':
                            "Se espera un número de 13 cifras ej: BMOU-123456-7"
                        }
                    }
                try:
                    type(int(digitov_container)) == int
                except ValueError:
                    rec.container_number = False
                    return {
                        'warning': {
                            'title':
                            "Error",
                            'message':
                            "Se espera un número de 13 cifras ej: BMOU-123456-7"
                        }
                    }
                # validar el numero con el algoritmo iso6346
                container_number = rec.container_number.replace('-', '')
                if not iso6346.is_valid(container_number):
                    rec.container_number = False
                    rec.valid_cointaner_number_text = False
                    rec.invalid_cointaner_number_text = True
                else:
                    rec.valid_cointaner_number_text = True
                    rec.invalid_cointaner_number_text = False

    @api.onchange('container_number_exception')
    def check_container_number_exception(self):
        for rec in self:
            # Valida existencia
            if rec.container_number_exception != False:
                # Valida largo correcto
                if len(rec.container_number_exception) != 13:
                    rec.container_number_exception = False
                    return {
                        'warning': {
                            'title':
                            "Error",
                            'message':
                            "Se espera un número de 13 cifras ej: BMOU-123456-7"
                        }
                    }
                # Valida existencia de - para poder realizar split
                if rec.container_number_exception.count('-') != 2:
                    rec.container_number_exception = False
                    return {
                        'warning': {
                            'title': "Error",
                            'message':
                            "Formato inválido, se espera BMOU-123456-7"
                        }
                    }
                # letras_c,numeros_c,digitov_c = rec.container_number_exception.split("-") Si es necesario utlizar para verificar otras cosas
                string_container, numeros_container, digitov_container = rec.container_number_exception.split(
                    "-")
                if not string_container.isalpha():
                    rec.container_number_exception = False
                    return {
                        'warning': {
                            'title':
                            "Error",
                            'message':
                            "Se espera un número de 13 cifras ej: BMOU-123456-7"
                        }
                    }
                try:
                    type(int(numeros_container)) == int
                except ValueError:
                    rec.container_number_exception = False
                    return {
                        'warning': {
                            'title':
                            "Error",
                            'message':
                            "Se espera un número de 13 cifras ej: BMOU-123456-7"
                        }
                    }
                try:
                    type(int(numeros_container)) == int
                except ValueError:
                    rec.container_number_exception = False
                    return {
                        'warning': {
                            'title':
                            "Error",
                            'message':
                            "Se espera un número de 13 cifras ej: BMOU-123456-7"
                        }
                    }
                try:
                    type(int(digitov_container)) == int
                except ValueError:
                    rec.container_number_exception = False
                    return {
                        'warning': {
                            'title':
                            "Error",
                            'message':
                            "Se espera un número de 13 cifras ej: BMOU-123456-7"
                        }
                    }

    @api.multi
    def get_container_number(self):
        for rec in self:
            rec.make_container_number_invisible = True
        return

    @api.multi
    def cancel_get_container_number(self):
        for rec in self:
            rec.make_container_number_invisible = False
        return

    @api.onchange('load_type')
    def _onchange_load_type(self):
        domain = {}
        warning = {}
        res = {}
        vehicles_obj = self.env['fleet.vehicle']
        contenedores = vehicles_obj.search([('vehicle_type', '=', 'container')
                                            ]).ids
        domain = {'container_type': [('id', 'in', contenedores)]}

        if warning:
            res['warning'] = warning
        if domain:
            res['domain'] = domain
        return res

    @api.multi
    def add_supplier_to_product_line(self):
        if self._module:
            if self._module == 'deposito':
                tax_obj = self.env['account.tax']
                taxes = tax_obj.search([('name', '=', 'IVA Directo Op  Grav B')
                                        ])
                if self.product_id.name == 'Alquiler':
                    taxes = tax_obj.search([('name', '=',
                                             'Compras Exentos IVA')])
                lineas = []
                for rec in self:
                    line_dict = {}
                    line_dict['deposito_id'] = rec.deposito_srv_id.id
                    line_dict['supplier_id'] = rec.supplier_id.id
                    line_dict['currency_id'] = rec.valor_compra_currency_id.id
                    line_dict['amount'] = rec.valor_compra

                    if self.product_id.name == 'Alquiler':
                        line_dict['price_subtotal'] = rec.valor_compra
                    else:
                        line_dict['price_subtotal'] = rec.valor_compra * 1.22
                    line_dict['ref'] = self.deposito_srv_id.referencia
                    line_dict['rt_service_id'] = False
                    line_dict['rt_consol_product_id'] = False
                    line_dict['rt_marfrig_product_id'] = False
                    line_dict['rt_deposito_product_id'] = self.id
                    line_dict['service_state'] = rec.state
                    line_dict['tax_ids'] = [(6, 0, taxes.ids)]
                    line_dict['service_date'] = rec.start
                    # line_dict['tack_id'] = rec.container_number
                    # line_dict['dua'] = self.get_dua()
                    # line_dict['mic'] = ' '
                    line_dict['origin_id'] = rec.origin_id.id
                    line_dict['destiny_id'] = rec.destiny_id.id
                    line_dict['product_id'] = rec.product_id.id
                    line_dict['output_reference'] = self.name
                    line_dict[
                        'partner_invoice_id'] = self.deposito_srv_id.partner_invoice_id.id
                    lineas.append((0, 0, line_dict))

                self.supplier_ids = lineas
Esempio n. 6
0
class PurchaseOrder(models.Model):
    _inherit = 'purchase.order'

    @api.model
    def _default_picking_type(self):
        return self._get_picking_type(self.env.context.get('company_id') or self.env.company.id)

    incoterm_id = fields.Many2one('account.incoterms', 'Incoterm', states={'done': [('readonly', True)]}, help="International Commercial Terms are a series of predefined commercial terms used in international transactions.")

    incoming_picking_count = fields.Integer("Incoming Shipment count", compute='_compute_incoming_picking_count')
    picking_ids = fields.Many2many('stock.picking', compute='_compute_picking_ids', string='Receptions', copy=False, store=True)

    picking_type_id = fields.Many2one('stock.picking.type', 'Deliver To', states=Purchase.READONLY_STATES, required=True, default=_default_picking_type, domain="['|', ('warehouse_id', '=', False), ('warehouse_id.company_id', '=', company_id)]",
        help="This will determine operation type of incoming shipment")
    default_location_dest_id_usage = fields.Selection(related='picking_type_id.default_location_dest_id.usage', string='Destination Location Type',
        help="Technical field used to display the Drop Ship Address", readonly=True)
    group_id = fields.Many2one('procurement.group', string="Procurement Group", copy=False)
    is_shipped = fields.Boolean(compute="_compute_is_shipped")
    effective_date = fields.Datetime("Effective Date", compute='_compute_effective_date', store=True, copy=False,
        help="Completion date of the first receipt order.")
    on_time_rate = fields.Float(related='partner_id.on_time_rate', compute_sudo=False)

    @api.depends('order_line.move_ids.picking_id')
    def _compute_picking_ids(self):
        for order in self:
            order.picking_ids = order.order_line.move_ids.picking_id

    @api.depends('picking_ids')
    def _compute_incoming_picking_count(self):
        for order in self:
            order.incoming_picking_count = len(order.picking_ids)

    @api.depends('picking_ids.date_done')
    def _compute_effective_date(self):
        for order in self:
            pickings = order.picking_ids.filtered(lambda x: x.state == 'done' and x.location_dest_id.usage == 'internal' and x.date_done)
            order.effective_date = min(pickings.mapped('date_done'), default=False)

    @api.depends('picking_ids', 'picking_ids.state')
    def _compute_is_shipped(self):
        for order in self:
            if order.picking_ids and all(x.state in ['done', 'cancel'] for x in order.picking_ids):
                order.is_shipped = True
            else:
                order.is_shipped = False

    @api.onchange('picking_type_id')
    def _onchange_picking_type_id(self):
        if self.picking_type_id.default_location_dest_id.usage != 'customer':
            self.dest_address_id = False

    @api.onchange('company_id')
    def _onchange_company_id(self):
        p_type = self.picking_type_id
        if not(p_type and p_type.code == 'incoming' and (p_type.warehouse_id.company_id == self.company_id or not p_type.warehouse_id)):
            self.picking_type_id = self._get_picking_type(self.company_id.id)

    # --------------------------------------------------
    # CRUD
    # --------------------------------------------------

    def write(self, vals):
        if vals.get('order_line') and self.state == 'purchase':
            for order in self:
                pre_order_line_qty = {order_line: order_line.product_qty for order_line in order.mapped('order_line')}
        res = super(PurchaseOrder, self).write(vals)
        if vals.get('order_line') and self.state == 'purchase':
            for order in self:
                to_log = {}
                for order_line in order.order_line:
                    if pre_order_line_qty.get(order_line, False) and float_compare(pre_order_line_qty[order_line], order_line.product_qty, precision_rounding=order_line.product_uom.rounding) > 0:
                        to_log[order_line] = (order_line.product_qty, pre_order_line_qty[order_line])
                if to_log:
                    order._log_decrease_ordered_quantity(to_log)
        return res

    # --------------------------------------------------
    # Actions
    # --------------------------------------------------

    def button_approve(self, force=False):
        result = super(PurchaseOrder, self).button_approve(force=force)
        self._create_picking()
        return result

    def button_cancel(self):
        for order in self:
            for move in order.order_line.mapped('move_ids'):
                if move.state == 'done':
                    raise UserError(_('Unable to cancel purchase order %s as some receptions have already been done.') % (order.name))
            # If the product is MTO, change the procure_method of the closest move to purchase to MTS.
            # The purpose is to link the po that the user will manually generate to the existing moves's chain.
            if order.state in ('draft', 'sent', 'to approve', 'purchase'):
                for order_line in order.order_line:
                    order_line.move_ids._action_cancel()
                    if order_line.move_dest_ids:
                        move_dest_ids = order_line.move_dest_ids
                        if order_line.propagate_cancel:
                            move_dest_ids._action_cancel()
                        else:
                            move_dest_ids.write({'procure_method': 'make_to_stock'})
                            move_dest_ids._recompute_state()

            for pick in order.picking_ids.filtered(lambda r: r.state != 'cancel'):
                pick.action_cancel()

            order.order_line.write({'move_dest_ids':[(5,0,0)]})

        return super(PurchaseOrder, self).button_cancel()

    def action_view_picking(self):
        return self._get_action_view_picking(self.picking_ids)

    def _get_action_view_picking(self, pickings):
        """ This function returns an action that display existing picking orders of given purchase order ids. When only one found, show the picking immediately.
        """
        self.ensure_one()
        result = self.env["ir.actions.actions"]._for_xml_id('stock.action_picking_tree_all')
        # override the context to get rid of the default filtering on operation type
        result['context'] = {'default_partner_id': self.partner_id.id, 'default_origin': self.name, 'default_picking_type_id': self.picking_type_id.id}
        # choose the view_mode accordingly
        if not pickings or len(pickings) > 1:
            result['domain'] = [('id', 'in', pickings.ids)]
        elif len(pickings) == 1:
            res = self.env.ref('stock.view_picking_form', False)
            form_view = [(res and res.id or False, 'form')]
            result['views'] = form_view + [(state, view) for state, view in result.get('views', []) if view != 'form']
            result['res_id'] = pickings.id
        return result

    def _prepare_invoice(self):
        invoice_vals = super()._prepare_invoice()
        invoice_vals['invoice_incoterm_id'] = self.incoterm_id.id
        return invoice_vals

    # --------------------------------------------------
    # Business methods
    # --------------------------------------------------

    def _log_decrease_ordered_quantity(self, purchase_order_lines_quantities):

        def _keys_in_groupby(move):
            """ group by picking and the responsible for the product the
            move.
            """
            return (move.picking_id, move.product_id.responsible_id)

        def _render_note_exception_quantity_po(order_exceptions):
            order_line_ids = self.env['purchase.order.line'].browse([order_line.id for order in order_exceptions.values() for order_line in order[0]])
            purchase_order_ids = order_line_ids.mapped('order_id')
            move_ids = self.env['stock.move'].concat(*rendering_context.keys())
            impacted_pickings = move_ids.mapped('picking_id')._get_impacted_pickings(move_ids) - move_ids.mapped('picking_id')
            values = {
                'purchase_order_ids': purchase_order_ids,
                'order_exceptions': order_exceptions.values(),
                'impacted_pickings': impacted_pickings,
            }
            return self.env.ref('purchase_stock.exception_on_po')._render(values=values)

        documents = self.env['stock.picking']._log_activity_get_documents(purchase_order_lines_quantities, 'move_ids', 'DOWN', _keys_in_groupby)
        filtered_documents = {}
        for (parent, responsible), rendering_context in documents.items():
            if parent._name == 'stock.picking':
                if parent.state == 'cancel':
                    continue
            filtered_documents[(parent, responsible)] = rendering_context
        self.env['stock.picking']._log_activity(_render_note_exception_quantity_po, filtered_documents)

    def _get_destination_location(self):
        self.ensure_one()
        if self.dest_address_id:
            return self.dest_address_id.property_stock_customer.id
        return self.picking_type_id.default_location_dest_id.id

    @api.model
    def _get_picking_type(self, company_id):
        picking_type = self.env['stock.picking.type'].search([('code', '=', 'incoming'), ('warehouse_id.company_id', '=', company_id)])
        if not picking_type:
            picking_type = self.env['stock.picking.type'].search([('code', '=', 'incoming'), ('warehouse_id', '=', False)])
        return picking_type[:1]

    def _prepare_picking(self):
        if not self.group_id:
            self.group_id = self.group_id.create({
                'name': self.name,
                'partner_id': self.partner_id.id
            })
        if not self.partner_id.property_stock_supplier.id:
            raise UserError(_("You must set a Vendor Location for this partner %s", self.partner_id.name))
        return {
            'picking_type_id': self.picking_type_id.id,
            'partner_id': self.partner_id.id,
            'user_id': False,
            'date': self.date_order,
            'origin': self.name,
            'location_dest_id': self._get_destination_location(),
            'location_id': self.partner_id.property_stock_supplier.id,
            'company_id': self.company_id.id,
        }

    def _create_picking(self):
        StockPicking = self.env['stock.picking']
        for order in self.filtered(lambda po: po.state in ('purchase', 'done')):
            if any(product.type in ['product', 'consu'] for product in order.order_line.product_id):
                order = order.with_company(order.company_id)
                pickings = order.picking_ids.filtered(lambda x: x.state not in ('done', 'cancel'))
                if not pickings:
                    res = order._prepare_picking()
                    picking = StockPicking.with_user(SUPERUSER_ID).create(res)
                else:
                    picking = pickings[0]
                moves = order.order_line._create_stock_moves(picking)
                moves = moves.filtered(lambda x: x.state not in ('done', 'cancel'))._action_confirm()
                seq = 0
                for move in sorted(moves, key=lambda move: move.date):
                    seq += 5
                    move.sequence = seq
                moves._action_assign()
                picking.message_post_with_view('mail.message_origin_link',
                    values={'self': picking, 'origin': order},
                    subtype_id=self.env.ref('mail.mt_note').id)
        return True

    def _add_picking_info(self, activity):
        """Helper method to add picking info to the Date Updated activity when
        vender updates date_planned of the po lines.
        """
        validated_picking = self.picking_ids.filtered(lambda p: p.state == 'done')
        if validated_picking:
            message = _("Those dates couldn’t be modified accordingly on the receipt %s which had already been validated.", validated_picking[0].name)
        elif not self.picking_ids:
            message = _("Corresponding receipt not found.")
        else:
            message = _("Those dates have been updated accordingly on the receipt %s.", self.picking_ids[0].name)
        activity.note += Markup('<p>{}</p>').format(message)

    def _create_update_date_activity(self, updated_dates):
        activity = super()._create_update_date_activity(updated_dates)
        self._add_picking_info(activity)

    def _update_update_date_activity(self, updated_dates, activity):
        # remove old picking info to update it
        note_lines = activity.note.split('<p>')
        note_lines.pop()
        activity.note = Markup('<p>').join(note_lines)
        super()._update_update_date_activity(updated_dates, activity)
        self._add_picking_info(activity)

    @api.model
    def _get_orders_to_remind(self):
        """When auto sending reminder mails, don't send for purchase order with
        validated receipts."""
        return super()._get_orders_to_remind().filtered(lambda p: not p.effective_date)
Esempio n. 7
0
class PosOrder(models.Model):
    _name = "pos.order"
    _description = "Point of Sale Orders"
    _order = "id desc"

    @api.model
    def _amount_line_tax(self, line, fiscal_position_id):
        taxes = line.tax_ids.filtered(lambda t: t.company_id.id == line.order_id.company_id.id)
        if fiscal_position_id:
            taxes = fiscal_position_id.map_tax(taxes, line.product_id, line.order_id.partner_id)
        price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
        taxes = taxes.compute_all(price, line.order_id.pricelist_id.currency_id, line.qty, product=line.product_id, partner=line.order_id.partner_id or False)['taxes']
        return sum(tax.get('amount', 0.0) for tax in taxes)

    @api.model
    def _order_fields(self, ui_order):
        process_line = partial(self.env['pos.order.line']._order_line_fields, session_id=ui_order['pos_session_id'])
        return {
            'name':         ui_order['name'],
            'user_id':      ui_order['user_id'] or False,
            'session_id':   ui_order['pos_session_id'],
            'lines':        [process_line(l) for l in ui_order['lines']] if ui_order['lines'] else False,
            'pos_reference': ui_order['name'],
            'partner_id':   ui_order['partner_id'] or False,
            'date_order':   ui_order['creation_date'],
            'fiscal_position_id': ui_order['fiscal_position_id']
        }

    def _payment_fields(self, ui_paymentline):
        return {
            'amount':       ui_paymentline['amount'] or 0.0,
            'payment_date': ui_paymentline['name'],
            'statement_id': ui_paymentline['statement_id'],
            'payment_name': ui_paymentline.get('note', False),
            'journal':      ui_paymentline['journal_id'],
        }

    # This deals with orders that belong to a closed session. In order
    # to recover from this situation we create a new rescue session,
    # making it obvious that something went wrong.
    # A new, separate, rescue session is preferred for every such recovery,
    # to avoid adding unrelated orders to live sessions.
    def _get_valid_session(self, order):
        PosSession = self.env['pos.session']
        closed_session = PosSession.browse(order['pos_session_id'])

        _logger.warning('session %s (ID: %s) was closed but received order %s (total: %s) belonging to it',
                        closed_session.name,
                        closed_session.id,
                        order['name'],
                        order['amount_total'])
        rescue_session = PosSession.search([
            ('state', 'not in', ('closed', 'closing_control')),
            ('rescue', '=', True),
            ('config_id', '=', closed_session.config_id.id),
        ], limit=1)
        if rescue_session:
            _logger.warning('reusing recovery session %s for saving order %s', rescue_session.name, order['name'])
            return rescue_session

        _logger.warning('attempting to create recovery session for saving order %s', order['name'])
        new_session = PosSession.create({
            'config_id': closed_session.config_id.id,
            'name': _('(RESCUE FOR %(session)s)') % {'session': closed_session.name},
            'rescue': True,  # avoid conflict with live sessions
        })
        # bypass opening_control (necessary when using cash control)
        new_session.action_pos_session_open()

        return new_session

    def _match_payment_to_invoice(self, order):
        account_precision = self.env['decimal.precision'].precision_get('Account')

        # ignore orders with an amount_paid of 0 because those are returns through the POS
        if not float_is_zero(order['amount_return'], account_precision) and not float_is_zero(order['amount_paid'], account_precision):
            cur_amount_paid = 0
            payments_to_keep = []
            for payment in order.get('statement_ids'):
                if cur_amount_paid + payment[2]['amount'] > order['amount_total']:
                    payment[2]['amount'] = order['amount_total'] - cur_amount_paid
                    payments_to_keep.append(payment)
                    break
                cur_amount_paid += payment[2]['amount']
                payments_to_keep.append(payment)
            order['statement_ids'] = payments_to_keep
            order['amount_return'] = 0

    @api.model
    def _process_order(self, pos_order):
        prec_acc = self.env['decimal.precision'].precision_get('Account')
        pos_session = self.env['pos.session'].browse(pos_order['pos_session_id'])
        if pos_session.state == 'closing_control' or pos_session.state == 'closed':
            pos_order['pos_session_id'] = self._get_valid_session(pos_order).id
        order = self.create(self._order_fields(pos_order))
        journal_ids = set()
        for payments in pos_order['statement_ids']:
            if not float_is_zero(payments[2]['amount'], precision_digits=prec_acc):
                order.add_payment(self._payment_fields(payments[2]))
            journal_ids.add(payments[2]['journal_id'])

        if pos_session.sequence_number <= pos_order['sequence_number']:
            pos_session.write({'sequence_number': pos_order['sequence_number'] + 1})
            pos_session.refresh()

        if not float_is_zero(pos_order['amount_return'], prec_acc):
            cash_journal_id = pos_session.cash_journal_id.id
            if not cash_journal_id:
                # Select for change one of the cash journals used in this
                # payment
                cash_journal = self.env['account.journal'].search([
                    ('type', '=', 'cash'),
                    ('id', 'in', list(journal_ids)),
                ], limit=1)
                if not cash_journal:
                    # If none, select for change one of the cash journals of the POS
                    # This is used for example when a customer pays by credit card
                    # an amount higher than total amount of the order and gets cash back
                    cash_journal = [statement.journal_id for statement in pos_session.statement_ids if statement.journal_id.type == 'cash']
                    if not cash_journal:
                        raise UserError(_("No cash statement found for this session. Unable to record returned cash."))
                cash_journal_id = cash_journal[0].id
            order.add_payment({
                'amount': -pos_order['amount_return'],
                'payment_date': fields.Datetime.now(),
                'payment_name': _('return'),
                'journal': cash_journal_id,
            })
        return order

    def _prepare_analytic_account(self, line):
        '''This method is designed to be inherited in a custom module'''
        return False

    def _create_account_move(self, dt, ref, journal_id, company_id):
        date_tz_user = fields.Datetime.context_timestamp(self, fields.Datetime.from_string(dt))
        date_tz_user = fields.Date.to_string(date_tz_user)
        return self.env['account.move'].sudo().create({'ref': ref, 'journal_id': journal_id, 'date': date_tz_user})

    def _prepare_invoice(self):
        """
        Prepare the dict of values to create the new invoice for a pos order.
        """
        return {
            'name': self.name,
            'origin': self.name,
            'account_id': self.partner_id.property_account_receivable_id.id,
            'journal_id': self.session_id.config_id.invoice_journal_id.id,
            'type': 'out_invoice',
            'reference': self.name,
            'partner_id': self.partner_id.id,
            'comment': self.note or '',
            # considering partner's sale pricelist's currency
            'currency_id': self.pricelist_id.currency_id.id,
            'user_id': self.env.uid,
        }

    def _action_create_invoice_line(self, line=False, invoice_id=False):
        InvoiceLine = self.env['account.invoice.line']
        inv_name = line.product_id.name_get()[0][1]
        inv_line = {
            'invoice_id': invoice_id,
            'product_id': line.product_id.id,
            'quantity': line.qty,
            'account_analytic_id': self._prepare_analytic_account(line),
            'name': inv_name,
        }
        # Oldlin trick
        invoice_line = InvoiceLine.sudo().new(inv_line)
        invoice_line._onchange_product_id()
        invoice_line.invoice_line_tax_ids = invoice_line.invoice_line_tax_ids.filtered(lambda t: t.company_id.id == line.order_id.company_id.id).ids
        fiscal_position_id = line.order_id.fiscal_position_id
        if fiscal_position_id:
            invoice_line.invoice_line_tax_ids = fiscal_position_id.map_tax(invoice_line.invoice_line_tax_ids, line.product_id, line.order_id.partner_id)
        invoice_line.invoice_line_tax_ids = invoice_line.invoice_line_tax_ids.ids
        # We convert a new id object back to a dictionary to write to
        # bridge between old and new api
        inv_line = invoice_line._convert_to_write({name: invoice_line[name] for name in invoice_line._cache})
        inv_line.update(price_unit=line.price_unit, discount=line.discount)
        return InvoiceLine.sudo().create(inv_line)

    def _create_account_move_line(self, session=None, move=None):
        # Tricky, via the workflow, we only have one id in the ids variable
        """Create a account move line of order grouped by products or not."""
        IrProperty = self.env['ir.property']
        ResPartner = self.env['res.partner']

        if session and not all(session.id == order.session_id.id for order in self):
            raise UserError(_('Selected orders do not have the same session!'))

        grouped_data = {}
        have_to_group_by = session and session.config_id.group_by or False
        rounding_method = session and session.config_id.company_id.tax_calculation_rounding_method

        for order in self.filtered(lambda o: not o.account_move or order.state == 'paid'):
            current_company = order.sale_journal.company_id
            account_def = IrProperty.get(
                'property_account_receivable_id', 'res.partner')
            order_account = order.partner_id.property_account_receivable_id.id or account_def and account_def.id
            partner_id = ResPartner._find_accounting_partner(order.partner_id).id or False
            if move is None:
                # Create an entry for the sale
                journal_id = self.env['ir.config_parameter'].sudo().get_param(
                    'pos.closing.journal_id_%s' % current_company.id, default=order.sale_journal.id)
                move = self._create_account_move(
                    order.session_id.start_at, order.name, int(journal_id), order.company_id.id)

            def insert_data(data_type, values):
                # if have_to_group_by:
                values.update({
                    'partner_id': partner_id,
                    'move_id': move.id,
                })

                if data_type == 'product':
                    key = ('product', values['partner_id'], (values['product_id'], tuple(values['tax_ids'][0][2]), values['name']), values['analytic_account_id'], values['debit'] > 0)
                elif data_type == 'tax':
                    key = ('tax', values['partner_id'], values['tax_line_id'], values['debit'] > 0)
                elif data_type == 'counter_part':
                    key = ('counter_part', values['partner_id'], values['account_id'], values['debit'] > 0)
                else:
                    return

                grouped_data.setdefault(key, [])

                if have_to_group_by:
                    if not grouped_data[key]:
                        grouped_data[key].append(values)
                    else:
                        current_value = grouped_data[key][0]
                        current_value['quantity'] = current_value.get('quantity', 0.0) + values.get('quantity', 0.0)
                        current_value['credit'] = current_value.get('credit', 0.0) + values.get('credit', 0.0)
                        current_value['debit'] = current_value.get('debit', 0.0) + values.get('debit', 0.0)
                else:
                    grouped_data[key].append(values)

            # because of the weird way the pos order is written, we need to make sure there is at least one line,
            # because just after the 'for' loop there are references to 'line' and 'income_account' variables (that
            # are set inside the for loop)
            # TOFIX: a deep refactoring of this method (and class!) is needed
            # in order to get rid of this stupid hack
            assert order.lines, _('The POS order must have lines when calling this method')
            # Create an move for each order line
            cur = order.pricelist_id.currency_id
            for line in order.lines:
                amount = line.price_subtotal

                # Search for the income account
                if line.product_id.property_account_income_id.id:
                    income_account = line.product_id.property_account_income_id.id
                elif line.product_id.categ_id.property_account_income_categ_id.id:
                    income_account = line.product_id.categ_id.property_account_income_categ_id.id
                else:
                    raise UserError(_('Please define income '
                                      'account for this product: "%s" (id:%d).')
                                    % (line.product_id.name, line.product_id.id))

                name = line.product_id.name
                if line.notice:
                    # add discount reason in move
                    name = name + ' (' + line.notice + ')'

                # Create a move for the line for the order line
                insert_data('product', {
                    'name': name,
                    'quantity': line.qty,
                    'product_id': line.product_id.id,
                    'account_id': income_account,
                    'analytic_account_id': self._prepare_analytic_account(line),
                    'credit': ((amount > 0) and amount) or 0.0,
                    'debit': ((amount < 0) and -amount) or 0.0,
                    'tax_ids': [(6, 0, line.tax_ids_after_fiscal_position.ids)],
                    'partner_id': partner_id
                })

                # Create the tax lines
                taxes = line.tax_ids_after_fiscal_position.filtered(lambda t: t.company_id.id == current_company.id)
                if not taxes:
                    continue
                for tax in taxes.compute_all(line.price_unit * (100.0 - line.discount) / 100.0, cur, line.qty)['taxes']:
                    insert_data('tax', {
                        'name': _('Tax') + ' ' + tax['name'],
                        'product_id': line.product_id.id,
                        'quantity': line.qty,
                        'account_id': tax['account_id'] or income_account,
                        'credit': ((tax['amount'] > 0) and tax['amount']) or 0.0,
                        'debit': ((tax['amount'] < 0) and -tax['amount']) or 0.0,
                        'tax_line_id': tax['id'],
                        'partner_id': partner_id
                    })

            # round tax lines per order
            if rounding_method == 'round_globally':
                for group_key, group_value in grouped_data.iteritems():
                    if group_key[0] == 'tax':
                        for line in group_value:
                            line['credit'] = cur.round(line['credit'])
                            line['debit'] = cur.round(line['debit'])

            # counterpart
            insert_data('counter_part', {
                'name': _("Trade Receivables"),  # order.name,
                'account_id': order_account,
                'credit': ((order.amount_total < 0) and -order.amount_total) or 0.0,
                'debit': ((order.amount_total > 0) and order.amount_total) or 0.0,
                'partner_id': partner_id
            })

            order.write({'state': 'done', 'account_move': move.id})

        all_lines = []
        for group_key, group_data in grouped_data.iteritems():
            for value in group_data:
                all_lines.append((0, 0, value),)
        if move:  # In case no order was changed
            move.sudo().write({'line_ids': all_lines})
            move.sudo().post()
        return True

    def _reconcile_payments(self):
        for order in self:
            aml = order.statement_ids.mapped('journal_entry_ids') | order.account_move.line_ids
            aml = aml.filtered(lambda r: not r.reconciled and r.account_id.internal_type == 'receivable' and r.partner_id == order.partner_id)
            try:
                aml.reconcile()
            except:
                # There might be unexpected situations where the automatic reconciliation won't
                # work. We don't want the user to be blocked because of this, since the automatic
                # reconciliation is introduced for convenience, not for mandatory accounting
                # reasons.
                continue

    def _default_session(self):
        return self.env['pos.session'].search([('state', '=', 'opened'), ('user_id', '=', self.env.uid)], limit=1)

    def _default_pricelist(self):
        return self._default_session().config_id.pricelist_id

    name = fields.Char(string='Order Ref', required=True, readonly=True, copy=False, default='/')
    company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True, default=lambda self: self.env.user.company_id)
    date_order = fields.Datetime(string='Order Date', readonly=True, index=True, default=fields.Datetime.now)
    user_id = fields.Many2one(
        comodel_name='res.users', string='Salesman',
        help="Person who uses the cash register. It can be a reliever, a student or an interim employee.",
        default=lambda self: self.env.uid,
        states={'done': [('readonly', True)], 'invoiced': [('readonly', True)]},
    )
    amount_tax = fields.Float(compute='_compute_amount_all', string='Taxes', digits=0)
    amount_total = fields.Float(compute='_compute_amount_all', string='Total', digits=0)
    amount_paid = fields.Float(compute='_compute_amount_all', string='Paid', states={'draft': [('readonly', False)]}, readonly=True, digits=0)
    amount_return = fields.Float(compute='_compute_amount_all', string='Returned', digits=0)
    lines = fields.One2many('pos.order.line', 'order_id', string='Order Lines', states={'draft': [('readonly', False)]}, readonly=True, copy=True)
    statement_ids = fields.One2many('account.bank.statement.line', 'pos_statement_id', string='Payments', states={'draft': [('readonly', False)]}, readonly=True)
    pricelist_id = fields.Many2one('product.pricelist', string='Pricelist', required=True, states={
                                   'draft': [('readonly', False)]}, readonly=True, default=_default_pricelist)
    partner_id = fields.Many2one('res.partner', string='Customer', change_default=True, index=True, states={'draft': [('readonly', False)], 'paid': [('readonly', False)]})
    sequence_number = fields.Integer(string='Sequence Number', help='A session-unique sequence number for the order', default=1)

    session_id = fields.Many2one(
        'pos.session', string='Session', required=True, index=True,
        domain="[('state', '=', 'opened')]", states={'draft': [('readonly', False)]},
        readonly=True, default=_default_session)
    config_id = fields.Many2one('pos.config', related='session_id.config_id', string="Point of Sale")
    state = fields.Selection(
        [('draft', 'New'), ('cancel', 'Cancelled'), ('paid', 'Paid'), ('done', 'Posted'), ('invoiced', 'Invoiced')],
        'Status', readonly=True, copy=False, default='draft')

    invoice_id = fields.Many2one('account.invoice', string='Invoice', copy=False)
    account_move = fields.Many2one('account.move', string='Journal Entry', readonly=True, copy=False)
    picking_id = fields.Many2one('stock.picking', string='Picking', readonly=True, copy=False)
    picking_type_id = fields.Many2one('stock.picking.type', related='session_id.config_id.picking_type_id', string="Operation Type")
    location_id = fields.Many2one(
        comodel_name='stock.location',
        related='session_id.config_id.stock_location_id',
        string="Location", store=True,
        readonly=True,
    )
    note = fields.Text(string='Internal Notes')
    nb_print = fields.Integer(string='Number of Print', readonly=True, copy=False, default=0)
    pos_reference = fields.Char(string='Receipt Ref', readonly=True, copy=False)
    sale_journal = fields.Many2one('account.journal', related='session_id.config_id.journal_id', string='Sales Journal', store=True, readonly=True)
    fiscal_position_id = fields.Many2one(
        comodel_name='account.fiscal.position', string='Fiscal Position',
        default=lambda self: self._default_session().config_id.default_fiscal_position_id,
        readonly=True,
        states={'draft': [('readonly', False)]},
    )

    @api.depends('statement_ids', 'lines.price_subtotal_incl', 'lines.discount')
    def _compute_amount_all(self):
        for order in self:
            order.amount_paid = order.amount_return = order.amount_tax = 0.0
            currency = order.pricelist_id.currency_id
            order.amount_paid = sum(payment.amount for payment in order.statement_ids)
            order.amount_return = sum(payment.amount < 0 and payment.amount or 0 for payment in order.statement_ids)
            order.amount_tax = currency.round(sum(self._amount_line_tax(line, order.fiscal_position_id) for line in order.lines))
            amount_untaxed = currency.round(sum(line.price_subtotal for line in order.lines))
            order.amount_total = order.amount_tax + amount_untaxed

    @api.onchange('partner_id')
    def _onchange_partner_id(self):
        if self.partner_id:
            self.pricelist = self.partner_id.property_product_pricelist.id

    @api.multi
    def write(self, vals):
        res = super(PosOrder, self).write(vals)
        Partner = self.env['res.partner']
        # If you change the partner of the PoS order, change also the partner of the associated bank statement lines
        if 'partner_id' in vals:
            for order in self:
                partner_id = False
                if order.invoice_id:
                    raise UserError(_("You cannot change the partner of a POS order for which an invoice has already been issued."))
                if vals['partner_id']:
                    partner = Partner.browse(vals['partner_id'])
                    partner_id = Partner._find_accounting_partner(partner).id
                order.statement_ids.write({'partner_id': partner_id})
        return res

    @api.multi
    def unlink(self):
        for pos_order in self.filtered(lambda pos_order: pos_order.state not in ['draft', 'cancel']):
            raise UserError(_('In order to delete a sale, it must be new or cancelled.'))
        return super(PosOrder, self).unlink()

    @api.model
    def create(self, values):
        if values.get('session_id'):
            # set name based on the sequence specified on the config
            session = self.env['pos.session'].browse(values['session_id'])
            values['name'] = session.config_id.sequence_id._next()
            values.setdefault('pricelist_id', session.config_id.pricelist_id.id)
        else:
            # fallback on any pos.order sequence
            values['name'] = self.env['ir.sequence'].next_by_code('pos.order')
        return super(PosOrder, self).create(values)

    @api.multi
    def action_view_invoice(self):
        return {
            'name': _('Customer Invoice'),
            'view_mode': 'form',
            'view_id': self.env.ref('account.invoice_form').id,
            'res_model': 'account.invoice',
            'context': "{'type':'out_invoice'}",
            'type': 'ir.actions.act_window',
            'res_id': self.invoice_id.id,
        }

    @api.multi
    def action_pos_order_paid(self):
        if not self.test_paid():
            raise UserError(_("Order is not paid."))
        self.write({'state': 'paid'})
        return self.create_picking()

    @api.multi
    def action_pos_order_invoice(self):
        Invoice = self.env['account.invoice']

        for order in self:
            # Force company for all SUPERUSER_ID action
            local_context = dict(self.env.context, force_company=order.company_id.id, company_id=order.company_id.id)
            if order.invoice_id:
                Invoice += order.invoice_id
                continue

            if not order.partner_id:
                raise UserError(_('Please provide a partner for the sale.'))

            invoice = Invoice.new(order._prepare_invoice())
            invoice._onchange_partner_id()
            invoice.fiscal_position_id = order.fiscal_position_id

            inv = invoice._convert_to_write({name: invoice[name] for name in invoice._cache})
            new_invoice = Invoice.with_context(local_context).sudo().create(inv)
            message = _("This invoice has been created from the point of sale session: <a href=# data-oe-model=pos.order data-oe-id=%d>%s</a>") % (order.id, order.name)
            new_invoice.message_post(body=message)
            order.write({'invoice_id': new_invoice.id, 'state': 'invoiced'})
            Invoice += new_invoice

            for line in order.lines:
                self.with_context(local_context)._action_create_invoice_line(line, new_invoice.id)

            new_invoice.with_context(local_context).sudo().compute_taxes()
            order.sudo().write({'state': 'invoiced'})

        if not Invoice:
            return {}

        return {
            'name': _('Customer Invoice'),
            'view_type': 'form',
            'view_mode': 'form',
            'view_id': self.env.ref('account.invoice_form').id,
            'res_model': 'account.invoice',
            'context': "{'type':'out_invoice'}",
            'type': 'ir.actions.act_window',
            'nodestroy': True,
            'target': 'current',
            'res_id': Invoice and Invoice.ids[0] or False,
        }

    # this method is unused, and so is the state 'cancel'
    @api.multi
    def action_pos_order_cancel(self):
        return self.write({'state': 'cancel'})

    @api.multi
    def action_pos_order_done(self):
        return self._create_account_move_line()

    @api.model
    def create_from_ui(self, orders):
        # Keep only new orders
        submitted_references = [o['data']['name'] for o in orders]
        pos_order = self.search([('pos_reference', 'in', submitted_references)])
        existing_orders = pos_order.read(['pos_reference'])
        existing_references = set([o['pos_reference'] for o in existing_orders])
        orders_to_save = [o for o in orders if o['data']['name'] not in existing_references]
        order_ids = []

        for tmp_order in orders_to_save:
            to_invoice = tmp_order['to_invoice']
            order = tmp_order['data']
            if to_invoice:
                self._match_payment_to_invoice(order)
            pos_order = self._process_order(order)
            order_ids.append(pos_order.id)

            try:
                pos_order.action_pos_order_paid()
            except psycopg2.OperationalError:
                # do not hide transactional errors, the order(s) won't be saved!
                raise
            except Exception as e:
                _logger.error('Could not fully process the POS Order: %s', tools.ustr(e))

            if to_invoice:
                pos_order.action_pos_order_invoice()
                pos_order.invoice_id.sudo().action_invoice_open()
                pos_order.account_move = pos_order.invoice_id.move_id
        return order_ids

    def test_paid(self):
        """A Point of Sale is paid when the sum
        @return: True
        """
        for order in self:
            if order.lines and not order.amount_total:
                continue
            if (not order.lines) or (not order.statement_ids) or (abs(order.amount_total - order.amount_paid) > 0.00001):
                return False
        return True

    def create_picking(self):
        """Create a picking for each order and validate it."""
        Picking = self.env['stock.picking']
        Move = self.env['stock.move']
        StockWarehouse = self.env['stock.warehouse']
        for order in self:
            if not order.lines.filtered(lambda l: l.product_id.type in ['product', 'consu']):
                continue
            address = order.partner_id.address_get(['delivery']) or {}
            picking_type = order.picking_type_id
            return_pick_type = order.picking_type_id.return_picking_type_id or order.picking_type_id
            order_picking = Picking
            return_picking = Picking
            moves = Move
            location_id = order.location_id.id
            if order.partner_id:
                destination_id = order.partner_id.property_stock_customer.id
            else:
                if (not picking_type) or (not picking_type.default_location_dest_id):
                    customerloc, supplierloc = StockWarehouse._get_partner_locations()
                    destination_id = customerloc.id
                else:
                    destination_id = picking_type.default_location_dest_id.id

            if picking_type:
                message = _("This transfer has been created from the point of sale session: <a href=# data-oe-model=pos.order data-oe-id=%d>%s</a>") % (order.id, order.name)
                picking_vals = {
                    'origin': order.name,
                    'partner_id': address.get('delivery', False),
                    'date_done': order.date_order,
                    'picking_type_id': picking_type.id,
                    'company_id': order.company_id.id,
                    'move_type': 'direct',
                    'note': order.note or "",
                    'location_id': location_id,
                    'location_dest_id': destination_id,
                }
                pos_qty = any([x.qty > 0 for x in order.lines])
                if pos_qty:
                    order_picking = Picking.create(picking_vals.copy())
                    order_picking.message_post(body=message)
                neg_qty = any([x.qty < 0 for x in order.lines])
                if neg_qty:
                    return_vals = picking_vals.copy()
                    return_vals.update({
                        'location_id': destination_id,
                        'location_dest_id': return_pick_type != picking_type and return_pick_type.default_location_dest_id.id or location_id,
                        'picking_type_id': return_pick_type.id
                    })
                    return_picking = Picking.create(return_vals)
                    return_picking.message_post(body=message)

            for line in order.lines.filtered(lambda l: l.product_id.type in ['product', 'consu'] and l.qty != 0):
                moves |= Move.create({
                    'name': line.name,
                    'product_uom': line.product_id.uom_id.id,
                    'picking_id': order_picking.id if line.qty >= 0 else return_picking.id,
                    'picking_type_id': picking_type.id if line.qty >= 0 else return_pick_type.id,
                    'product_id': line.product_id.id,
                    'product_uom_qty': abs(line.qty),
                    'state': 'draft',
                    'location_id': location_id if line.qty >= 0 else destination_id,
                    'location_dest_id': destination_id if line.qty >= 0 else return_pick_type != picking_type and return_pick_type.default_location_dest_id.id or location_id,
                })

            # prefer associating the regular order picking, not the return
            order.write({'picking_id': order_picking.id or return_picking.id})

            if return_picking:
                order._force_picking_done(return_picking)
            if order_picking:
                order._force_picking_done(order_picking)

            # when the pos.config has no picking_type_id set only the moves will be created
            if moves and not return_picking and not order_picking:
                moves.action_assign()
                moves.filtered(lambda m: m.state in ['confirmed', 'waiting']).force_assign()
                moves.filtered(lambda m: m.product_id.tracking == 'none').action_done()

        return True

    def _force_picking_done(self, picking):
        """Force picking in order to be set as done."""
        self.ensure_one()
        picking.action_assign()
        picking.force_assign()
        wrong_lots = self.set_pack_operation_lot(picking)
        if not wrong_lots:
            picking.action_done()

    def set_pack_operation_lot(self, picking=None):
        """Set Serial/Lot number in pack operations to mark the pack operation done."""

        StockProductionLot = self.env['stock.production.lot']
        PosPackOperationLot = self.env['pos.pack.operation.lot']
        has_wrong_lots = False
        for order in self:
            for pack_operation in (picking or self.picking_id).pack_operation_ids:
                picking_type = (picking or self.picking_id).picking_type_id
                lots_necessary = True
                if picking_type:
                    lots_necessary = picking_type and picking_type.use_existing_lots
                qty = 0
                qty_done = 0
                pack_lots = []
                pos_pack_lots = PosPackOperationLot.search([('order_id', '=',  order.id), ('product_id', '=', pack_operation.product_id.id)])
                pack_lot_names = [pos_pack.lot_name for pos_pack in pos_pack_lots]

                if pack_lot_names and lots_necessary:
                    for lot_name in list(set(pack_lot_names)):
                        stock_production_lot = StockProductionLot.search([('name', '=', lot_name), ('product_id', '=', pack_operation.product_id.id)])
                        if stock_production_lot:
                            if stock_production_lot.product_id.tracking == 'lot':
                                # if a lot nr is set through the frontend it will refer to the full quantity
                                qty = pack_operation.product_qty
                            else: # serial numbers
                                qty = 1.0
                            qty_done += qty
                            pack_lots.append({'lot_id': stock_production_lot.id, 'qty': qty})
                        else:
                            has_wrong_lots = True
                elif pack_operation.product_id.tracking == 'none' or not lots_necessary:
                    qty_done = pack_operation.product_qty
                else:
                    has_wrong_lots = True
                pack_operation.write({'pack_lot_ids': map(lambda x: (0, 0, x), pack_lots), 'qty_done': qty_done})
        return has_wrong_lots

    def add_payment(self, data):
        """Create a new payment for the order"""
        args = {
            'amount': data['amount'],
            'date': data.get('payment_date', fields.Date.today()),
            'name': self.name + ': ' + (data.get('payment_name', '') or ''),
            'partner_id': self.env["res.partner"]._find_accounting_partner(self.partner_id).id or False,
        }

        journal_id = data.get('journal', False)
        statement_id = data.get('statement_id', False)
        assert journal_id or statement_id, "No statement_id or journal_id passed to the method!"

        journal = self.env['account.journal'].browse(journal_id)
        # use the company of the journal and not of the current user
        company_cxt = dict(self.env.context, force_company=journal.company_id.id)
        account_def = self.env['ir.property'].with_context(company_cxt).get('property_account_receivable_id', 'res.partner')
        args['account_id'] = (self.partner_id.property_account_receivable_id.id) or (account_def and account_def.id) or False

        if not args['account_id']:
            if not args['partner_id']:
                msg = _('There is no receivable account defined to make payment.')
            else:
                msg = _('There is no receivable account defined to make payment for the partner: "%s" (id:%d).') % (
                    self.partner_id.name, self.partner_id.id,)
            raise UserError(msg)

        context = dict(self.env.context)
        context.pop('pos_session_id', False)
        for statement in self.session_id.statement_ids:
            if statement.id == statement_id:
                journal_id = statement.journal_id.id
                break
            elif statement.journal_id.id == journal_id:
                statement_id = statement.id
                break
        if not statement_id:
            raise UserError(_('You have to open at least one cashbox.'))

        args.update({
            'statement_id': statement_id,
            'pos_statement_id': self.id,
            'journal_id': journal_id,
            'ref': self.session_id.name,
        })
        self.env['account.bank.statement.line'].with_context(context).create(args)
        return statement_id

    @api.multi
    def refund(self):
        """Create a copy of order  for refund order"""
        PosOrder = self.env['pos.order']
        current_session = self.env['pos.session'].search([('state', '!=', 'closed'), ('user_id', '=', self.env.uid)], limit=1)
        if not current_session:
            raise UserError(_('To return product(s), you need to open a session that will be used to register the refund.'))
        for order in self:
            clone = order.copy({
                # ot used, name forced by create
                'name': order.name + _(' REFUND'),
                'session_id': current_session.id,
                'date_order': fields.Datetime.now(),
                'pos_reference': order.pos_reference,
                'lines': False,
            })
            for line in order.lines:
                clone_line = line.copy({
                    # required=True, copy=False
                    'name': line.name + _(' REFUND'),
                    'order_id': clone.id,
                    'qty': -line.qty,
                })
            PosOrder += clone

        return {
            'name': _('Return Products'),
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'pos.order',
            'res_id': PosOrder.ids[0],
            'view_id': False,
            'context': self.env.context,
            'type': 'ir.actions.act_window',
            'target': 'current',
        }
Esempio n. 8
0
class HrContract(models.Model):
    _inherit = 'hr.contract'

    transport_mode = fields.Selection(
        [
            ('company_car', 'Company car'),
            ('public_transport', 'Public Transport'),
            ('others', 'Other'),
        ],
        string="Transport",
        default='company_car',
        help="Transport mode the employee uses to go to work.")
    car_atn = fields.Monetary(string='ATN Company Car')
    public_transport_employee_amount = fields.Monetary(
        'Paid by the employee (Monthly)')
    thirteen_month = fields.Monetary(
        compute='_compute_holidays_advantages',
        string='13th Month',
        help="Yearly gross amount the employee receives as 13th month bonus.")
    double_holidays = fields.Monetary(
        compute='_compute_holidays_advantages',
        string='Holiday Bonus',
        help="Yearly gross amount the employee receives as holidays bonus.")
    warrant_value_employee = fields.Monetary(
        compute='_compute_warrants_cost',
        string="Warrant value for the employee")

    # Employer costs fields
    final_yearly_costs = fields.Monetary(
        compute='_compute_final_yearly_costs',
        readonly=False,
        string='Total Employee Cost',
        groups="hr.group_hr_manager",
        help="Total yearly cost of the employee for the employer.")
    monthly_yearly_costs = fields.Monetary(
        compute='_compute_monthly_yearly_costs',
        string='Monthly Equivalent Cost',
        readonly=True,
        help="Total monthly cost of the employee for the employer.")
    ucm_insurance = fields.Monetary(compute='_compute_ucm_insurance',
                                    string="Social Secretary Costs")
    social_security_contributions = fields.Monetary(
        compute='_compute_social_security_contributions',
        string="Social Security Contributions")
    yearly_cost_before_charges = fields.Monetary(
        compute='_compute_yearly_cost_before_charges',
        string="Yearly Costs Before Charges")
    meal_voucher_paid_by_employer = fields.Monetary(
        compute='_compute_meal_voucher_paid_by_employer',
        string="Meal Voucher Paid by Employer")
    company_car_total_depreciated_cost = fields.Monetary()
    public_transport_reimbursed_amount = fields.Monetary(
        string='Reimbursed amount',
        compute='_compute_public_transport_reimbursed_amount',
        readonly=False,
        store=True)
    others_reimbursed_amount = fields.Monetary(string='Reimbursed amount')
    transport_employer_cost = fields.Monetary(
        compute='_compute_transport_employer_cost',
        string="Employer cost from employee transports")
    warrants_cost = fields.Monetary(compute='_compute_warrants_cost')

    # Advantages
    commission_on_target = fields.Monetary(
        string="Commission on Target",
        default=lambda self: self.get_attribute('commission_on_target',
                                                'default_value'),
        help=
        "Monthly gross amount that the employee receives if the target is reached."
    )
    fuel_card = fields.Monetary(
        string="Fuel Card",
        default=lambda self: self.get_attribute('fuel_card', 'default_value'),
        help="Monthly amount the employee receives on his fuel card.")
    internet = fields.Monetary(
        string="Internet",
        default=lambda self: self.get_attribute('internet', 'default_value'),
        help=
        "The employee's internet subcription will be paid up to this amount.")
    representation_fees = fields.Monetary(
        string="Representation Fees",
        default=lambda self: self.get_attribute('representation_fees',
                                                'default_value'),
        help=
        "Monthly net amount the employee receives to cover his representation fees."
    )
    mobile = fields.Monetary(
        string="Mobile",
        default=lambda self: self.get_attribute('mobile', 'default_value'),
        help=
        "The employee's mobile subscription will be paid up to this amount.")
    mobile_plus = fields.Monetary(
        string="International Communication",
        default=lambda self: self.get_attribute('mobile_plus', 'default_value'
                                                ),
        help=
        "The employee's mobile subscription for international communication will be paid up to this amount."
    )
    meal_voucher_amount = fields.Monetary(
        string="Meal Vouchers",
        default=lambda self: self.get_attribute('meal_voucher_amount',
                                                'default_value'),
        help=
        "Amount the employee receives in the form of meal vouchers per worked day."
    )
    holidays = fields.Float(
        string='Legal Leaves',
        default=lambda self: self.get_attribute('holidays', 'default_value'),
        help="Number of days of paid leaves the employee gets per year.")
    holidays_editable = fields.Boolean(string="Editable Leaves", default=True)
    holidays_compensation = fields.Monetary(
        compute='_compute_holidays_compensation',
        string="Holidays Compensation")
    wage_with_holidays = fields.Monetary(
        compute='_compute_wage_with_holidays',
        inverse='_inverse_wage_with_holidays',
        string="Wage update with holidays retenues")
    additional_net_amount = fields.Monetary(
        string="Net Supplements",
        help="Monthly net amount the employee receives.")
    retained_net_amount = fields.Monetary(
        sting="Net Retained",
        help="Monthly net amount that is retained on the employee's salary.")
    eco_checks = fields.Monetary(
        "Eco Vouchers",
        default=lambda self: self.get_attribute('eco_checks', 'default_value'),
        help="Yearly amount the employee receives in the form of eco vouchers."
    )

    @api.depends('holidays', 'wage', 'final_yearly_costs')
    def _compute_wage_with_holidays(self):
        for contract in self:
            if contract.holidays > 20.0:
                yearly_cost = contract.final_yearly_costs * (
                    1.0 - (contract.holidays - 20.0) / 231.0)
                contract.wage_with_holidays = contract._get_gross_from_employer_costs(
                    yearly_cost)
            else:
                contract.wage_with_holidays = contract.wage

    def _inverse_wage_with_holidays(self):
        for contract in self:
            if contract.holidays > 20.0:
                remaining_for_gross = contract.wage_with_holidays * (
                    13.0 + 13.0 * 0.3507 + 0.92)
                yearly_cost = remaining_for_gross \
                    + 12.0 * contract.representation_fees \
                    + 12.0 * contract.fuel_card \
                    + 12.0 * contract.internet \
                    + 12.0 * (contract.mobile + contract.mobile_plus) \
                    + 12.0 * contract.transport_employer_cost \
                    + contract.warrants_cost \
                    + 220.0 * contract.meal_voucher_paid_by_employer
                contract.final_yearly_costs = yearly_cost / (
                    1.0 - (contract.holidays - 20.0) / 231.0)
                contract.wage = contract._get_gross_from_employer_costs(
                    contract.final_yearly_costs)
            else:
                contract.wage = contract.wage_with_holidays

    @api.depends('transport_mode', 'company_car_total_depreciated_cost',
                 'public_transport_reimbursed_amount',
                 'others_reimbursed_amount')
    def _compute_transport_employer_cost(self):
        for contract in self:
            if contract.transport_mode == 'company_car':
                contract.transport_employer_cost = contract.company_car_total_depreciated_cost
            elif contract.transport_mode == 'public_transport':
                contract.transport_employer_cost = contract.public_transport_reimbursed_amount
            elif contract.transport_mode == 'others':
                contract.transport_employer_cost = contract.others_reimbursed_amount

    @api.depends('commission_on_target')
    def _compute_warrants_cost(self):
        for contract in self:
            contract.warrants_cost = contract.commission_on_target * 1.326 * 1.05 * 12.0
            contract.warrant_value_employee = contract.commission_on_target * 1.326 * (
                1.00 - 0.535) * 12.0

    @api.depends('wage', 'fuel_card', 'representation_fees',
                 'transport_employer_cost', 'internet', 'mobile',
                 'mobile_plus')
    def _compute_yearly_cost_before_charges(self):
        for contract in self:
            contract.yearly_cost_before_charges = 12.0 * (
                contract.wage * (1.0 + 1.0 / 12.0) + contract.fuel_card +
                contract.representation_fees + contract.internet +
                contract.mobile + contract.mobile_plus +
                contract.transport_employer_cost)

    @api.depends('yearly_cost_before_charges', 'social_security_contributions',
                 'wage', 'social_security_contributions', 'double_holidays',
                 'warrants_cost', 'meal_voucher_paid_by_employer')
    def _compute_final_yearly_costs(self):
        for contract in self:
            contract.final_yearly_costs = (
                contract.yearly_cost_before_charges +
                contract.social_security_contributions +
                contract.double_holidays + contract.warrants_cost +
                (220.0 * contract.meal_voucher_paid_by_employer))

    @api.depends('holidays', 'final_yearly_costs')
    def _compute_holidays_compensation(self):
        for contract in self:
            if contract.holidays < 20:
                decrease_amount = contract.final_yearly_costs * (
                    20.0 - contract.holidays) / 231.0
                contract.holidays_compensation = decrease_amount
            else:
                contract.holidays_compensation = 0.0

    @api.onchange('final_yearly_costs')
    def _onchange_final_yearly_costs(self):
        self.wage = self._get_gross_from_employer_costs(
            self.final_yearly_costs)

    @api.depends('meal_voucher_amount')
    def _compute_meal_voucher_paid_by_employer(self):
        for contract in self:
            contract.meal_voucher_paid_by_employer = contract.meal_voucher_amount * (
                1 - 0.1463)

    @api.depends('wage')
    def _compute_social_security_contributions(self):
        for contract in self:
            total_wage = contract.wage * 13.0
            contract.social_security_contributions = (total_wage) * 0.3507

    @api.depends('wage')
    def _compute_ucm_insurance(self):
        for contract in self:
            contract.ucm_insurance = (contract.wage * 12.0) * 0.05

    @api.depends('public_transport_employee_amount')
    def _compute_public_transport_reimbursed_amount(self):
        for contract in self:
            contract.public_transport_reimbursed_amount = contract._get_public_transport_reimbursed_amount(
                contract.public_transport_employee_amount)

    def _get_public_transport_reimbursed_amount(self, amount):
        return amount * 0.68

    @api.depends('final_yearly_costs')
    def _compute_monthly_yearly_costs(self):
        for contract in self:
            contract.monthly_yearly_costs = contract.final_yearly_costs / 12.0

    @api.depends('wage')
    def _compute_holidays_advantages(self):
        for contract in self:
            contract.double_holidays = contract.wage * 0.92
            contract.thirteen_month = contract.wage

    @api.onchange('transport_mode')
    def _onchange_transport_mode(self):
        if self.transport_mode != 'company_car':
            self.fuel_card = 0
            self.company_car_total_depreciated_cost = 0
        if self.transport_mode != 'others':
            self.others_reimbursed_amount = 0
        if self.transport_mode != 'public_transports':
            self.public_transport_reimbursed_amount = 0

    @api.onchange('mobile', 'mobile_plus')
    def _onchange_mobile(self):
        if self.mobile_plus and not self.mobile:
            raise ValidationError(
                _('You should have a mobile subscription to select an international communication amount!'
                  ))

    def _get_internet_amount(self, has_internet):
        if has_internet:
            return self.get_attribute('internet', 'default_value')
        else:
            return 0.0

    def _get_mobile_amount(self, has_mobile, international_communication):
        if has_mobile and international_communication:
            return self.get_attribute('mobile',
                                      'default_value') + self.get_attribute(
                                          'mobile_plus', 'default_value')
        elif has_mobile:
            return self.get_attribute('mobile', 'default_value')
        else:
            return 0.0

    def _get_gross_from_employer_costs(self, yearly_cost):
        contract = self
        remaining_for_gross = yearly_cost \
            - 12.0 * contract.representation_fees \
            - 12.0 * contract.fuel_card \
            - 12.0 * contract.internet \
            - 12.0 * (contract.mobile + contract.mobile_plus) \
            - 12.0 * contract.transport_employer_cost \
            - contract.warrants_cost \
            - 220.0 * contract.meal_voucher_paid_by_employer
        gross = remaining_for_gross / (13.0 + 13.0 * 0.3507 + 0.92)
        return gross
Esempio n. 9
0
class WorkflowActionRule(models.Model):
    _name = "documents.workflow.rule"
    _description = "A set of condition and actions which will be available to all attachments matching the conditions"

    domain_folder_id = fields.Many2one('documents.folder', string="Folder", required=True, ondelete='cascade')
    name = fields.Char(required=True, string="Rule name", translate=True)
    note = fields.Char(string="Tooltip")

    # Conditions
    condition_type = fields.Selection([
        ('criteria', "Criteria"),
        ('domain', "Domain"),
    ], default='criteria', string="Condition type")

    # Domain
    domain = fields.Char()

    # Criteria
    criteria_partner_id = fields.Many2one('res.partner', string="Contact")
    criteria_owner_id = fields.Many2one('res.users', string="Owner")
    criteria_tag_ids = fields.One2many('documents.workflow.tag.criteria', 'workflow_rule_id', string="Tags")

    # Actions
    partner_id = fields.Many2one('res.partner', string="Set Contact")
    user_id = fields.Many2one('res.users', string="Set Owner")
    tag_action_ids = fields.One2many('documents.workflow.action', 'workflow_rule_id', string='Set Tags')
    folder_id = fields.Many2one('documents.folder', string="Move to Folder")
    has_business_option = fields.Boolean(compute='_get_business')
    create_model = fields.Selection([], string="Create")

    # Activity
    remove_activities = fields.Boolean(string='Mark all Activities as done')
    activity_option = fields.Boolean(string='Create a new activity')
    activity_type_id = fields.Many2one('mail.activity.type', string="Activity type")
    activity_summary = fields.Char('Summary')
    activity_date_deadline_range = fields.Integer(string='Due Date In')
    activity_date_deadline_range_type = fields.Selection([
        ('days', 'Days'),
        ('weeks', 'Weeks'),
        ('months', 'Months'),
    ], string='Due type', default='days')
    activity_note = fields.Html(string="Activity Note")
    activity_user_id = fields.Many2one('res.users', string='Responsible')

    @api.multi
    def _get_business(self):
        """
        Checks if the workflow rule has available create models to display the option.
        """
        for record in self:
            record.has_business_option = len(self._fields['create_model'].selection)

    def create_record(self, attachments=None):
        """
        implemented by each link module to define specific fields for the new business model (create_values)

        :param attachments: the list of the attachments of the selection
        :return: the action dictionary that will be called after the workflow action is done or True.
        """

        return True

    def apply_actions(self, attachment_ids):
        """
        called by the front-end Document Inspector to apply the actions to the selection of ID's.

        :param context:  attachment_ids[]: the list of attachments to apply the action.
        :return: if the action was to create a new business object, returns an action to open the view of the
                newly created object, else returns True.
        """
        attachments = self.env['ir.attachment'].browse(attachment_ids)

        for attachment in attachments:
            # partner/owner/share_link/folder changes
            attachment_dict = {}
            if self.user_id:
                attachment_dict['owner_id'] = self.user_id.id
            if self.partner_id:
                attachment_dict['partner_id'] = self.partner_id.id
            if self.folder_id:
                attachment_dict['folder_id'] = self.folder_id.id

            attachment.write(attachment_dict)

            if self.remove_activities:
                attachment.activity_ids.action_feedback(
                    feedback="completed by rule: %s. %s" % (self.name, self.note or '')
                )

            if self.activity_option and self.activity_type_id:
                attachment.documents_set_activity(settings_model=self)

            # tag and facet actions
            for tag_action in self.tag_action_ids:
                tag_action.execute_action(attachment)

        if self.create_model:
            return self.create_record(attachments=attachments)

        return True
Esempio n. 10
0
class LibraryBook(models.Model):
    _name = 'library.book'  # this is the unique id for the model
    _description = 'Library Book'  # this is a human readable name
    _inherit = ['base.archive']
    _order = 'date_release desc, name'
    _rec_name = 'short_name'  # Alternative field to use as name, used by osv’s name_get() (default: 'name')
    _sql_constraints = [('name_uniq', 'UNIQUE (name)',
                         'Book title must be unique.')]
    age_days = fields.Float(
        string='Days Since Release',
        compute='_compute_age',
        inverse='_inverse_age',
        search='_search_age',
        store=False,
        compute_sudo=False,
    )

    author_ids = fields.Many2many('res.partner', string='Authors')

    cost_price = fields.Float('Book Cost', dp.get_precision('Book Price'))

    cover = fields.Binary('Book Cover')

    currency_id = fields.Many2one('res.currency', string='Currency')

    date_release = fields.Date('Release Date')

    date_updated = fields.Datetime('Last Updated')

    description = fields.Html(
        string='Description',  # optional:
        sanitize=True,
        strip_style=False,
        translate=False,
    )

    isbn = fields.Char('ISBN')

    manager_remarks = fields.Text('Manager Remarks')

    name = fields.Char('Title', required=True)

    notes = fields.Text('Internal Notes')

    out_of_print = fields.Boolean('Out of Print?')

    pages = fields.Integer(
        string='Number of Pages',
        default=0,
        help='Total book pages count',
        groups='base.group_user',
        states={'cancel': [('readonly', True)]},
        copy=True,
        index=False,
        readonly=False,
        required=False,
        company_dependent=False,
    )

    publisher_id = fields.Many2one(
        'res.partner',
        string='Publisher',
        # optional
        ondelete='set null',
        context={},
        domain=[],
    )

    publisher_city = fields.Char('Publisher City', related='publisher_id.city')

    reader_rating = fields.Float(
        'Reader Average Rating',
        (14, 4),  # Optional precision (total, decimals),
    )

    ref_doc_id = fields.Reference(selection='_referencable_models',
                                  string='Reference Document')

    retail_price = fields.Monetary(
        'Retail Price',
        currency_field='currency_id',  # Optional
    )

    short_name = fields.Char(
        string='Short Title',
        size=100,  # For Char only
        translate=False,  # also for Text fields
    )

    state = fields.Selection([('draft', 'Not Available'),
                              ('available', 'Available'), ('lost', 'Lost')],
                             'State')

    @api.multi
    def name_get(self):
        result = []
        for book in self:
            authors = book.author_ids.mapped('name')
            name = u'%s (%s)' % (book.title, u', '.join(authors))
            result.append((book.id, name))
        return result

    @api.constrains('date_release')
    def _check_release_date(self):
        for r in self:
            if r.date_release > fields.Date.today():
                raise models.ValidationError(
                    'Release date must be in the past')

    @api.depends
    def _compute_age(self):
        today = fDate.from_string(fDate.today())
        for book in self.filtered('date_release'):
            delta = (fDate.from_string(book.date_release - today))
            book.age_days = delta.days

    def _inverse_age(self):
        today = fDate.from_string(fDate.today())
        for book in self.filtered('date_release'):
            d = td(days=book.age_days) - today
            book.date_release = fDate.to_string(d)

    def _search_age(self, operator, value):
        today = fDate.from_string(fDate.today())
        value_days = td(days=value)
        value_date = fDate.to_string(today - value_days)
        return [('date_release', operator, value_date)]

    @api.model
    def _referencable_models(self):
        models = self.env['res.request.link'].search([])
        return [(x.object, x.name) for x in models]

    @api.model
    def is_allowed_transition(self, old_state, new_state):
        allowed = [('draft', 'available'), ('available', 'borrowed'),
                   ('borrowed', 'available'), ('available', 'lost'),
                   ('lost', 'available')]
        return (old_state, new_state) in allowed

    @api.multi
    def change_state(self, new_state):
        for book in self:
            if book.is_allowed_transition(book.state, new_state):
                book.state = new_state
            else:
                continue

    @api.model
    @api.returns('self', lambda rec: rec.id)
    def create(self, values):
        if not self.user_has_groups('library.group_library_manager'):
            if 'manager_remarks' in values:
                raise exceptions.UserError('You are not allowed to modify',
                                           'manager_remarks')
        return super(LibraryBook, self).create(values)

    @api.multi
    def write(self, values):
        if not self.user_has_groups('library.group_library_manager'):
            if 'manager_remarks' in values:
                raise exceptions.UserError('You are not allowed to modify',
                                           'manager_remarks')
        return super(LibraryBook, self).write(values)

    @api.model
    def fields_get(self, allfields=None, write_access=True, attributes=None):
        fields = super(LibraryBook, self).fields_get(allfields=allfields,
                                                     write_access=write_access,
                                                     attributes=attributes)
        if not self.user_has_groups('library.group_library_manager'):
            if 'manager_remarks' in fields:
                fields['manager_remarks']['readonly'] = True

    @api.model
    def _name_search(self,
                     name='',
                     args=None,
                     operator='ilike',
                     limit=100,
                     name_get_uid=None):
        args = [] if args is None else args.copy()
        if not (name == '' and operator == 'ilike'):
            args += [
                '|', '|', ('name', operator, name), ('isbn', operator, name),
                ('author_ids.name', operator, name)
            ]
        return super(LibraryBook, self)._name_search(name='',
                                                     args=args,
                                                     operator='ilike',
                                                     limit=limit,
                                                     name_get_uid=name_get_uid)
Esempio n. 11
0
class HrEmployee(models.Model):
    _inherit = 'hr.employee'

    spouse_fiscal_status = fields.Selection(
        [('without income', 'Without Income'), ('with income', 'With Income')],
        string='Tax status for spouse',
        groups="hr.group_hr_user")
    disabled = fields.Boolean(
        string="Disabled",
        help="If the employee is declared disabled by law",
        groups="hr.group_hr_user")
    disabled_spouse_bool = fields.Boolean(
        string='Disabled Spouse',
        help='if recipient spouse is declared disabled by law',
        groups="hr.group_hr_user")
    disabled_children_bool = fields.Boolean(
        string='Disabled Children',
        help='if recipient children is/are declared disabled by law',
        groups="hr.group_hr_user")
    resident_bool = fields.Boolean(
        string='Nonresident',
        help='if recipient lives in a foreign country',
        groups="hr.group_hr_user")
    disabled_children_number = fields.Integer('Number of disabled children',
                                              groups="hr.group_hr_user")
    dependent_children = fields.Integer(
        compute='_compute_dependent_children',
        string='Considered number of dependent children',
        groups="hr.group_hr_user")
    other_dependent_people = fields.Boolean(
        string="Other Dependent People",
        help="If other people are dependent on the employee",
        groups="hr.group_hr_user")
    other_senior_dependent = fields.Integer(
        '# seniors (>=65)',
        help=
        "Number of seniors dependent on the employee, including the disabled ones",
        groups="hr.group_hr_user")
    other_disabled_senior_dependent = fields.Integer(
        '# disabled seniors (>=65)', groups="hr.group_hr_user")
    other_juniors_dependent = fields.Integer(
        '# people (<65)',
        help=
        "Number of juniors dependent on the employee, including the disabled ones",
        groups="hr.group_hr_user")
    other_disabled_juniors_dependent = fields.Integer(
        '# disabled people (<65)', groups="hr.group_hr_user")
    dependent_seniors = fields.Integer(
        compute='_compute_dependent_people',
        string="Considered number of dependent seniors",
        groups="hr.group_hr_user")
    dependent_juniors = fields.Integer(
        compute='_compute_dependent_people',
        string="Considered number of dependent juniors",
        groups="hr.group_hr_user")
    spouse_net_revenue = fields.Float(
        string="Spouse Net Revenue",
        help=
        "Own professional income, other than pensions, annuities or similar income",
        groups="hr.group_hr_user")
    spouse_other_net_revenue = fields.Float(
        string="Spouse Other Net Revenue",
        help=
        'Own professional income which is exclusively composed of pensions, annuities or similar income',
        groups="hr.group_hr_user")

    @api.constrains('spouse_fiscal_status', 'spouse_net_revenue',
                    'spouse_other_net_revenue')
    def _check_spouse_revenue(self):
        for employee in self:
            if employee.spouse_fiscal_status == 'with income' and not employee.spouse_net_revenue and not employee.spouse_other_net_revenue:
                raise ValidationError(
                    _("The revenue for the spouse can't be equal to zero is the fiscal status is 'With Income'."
                      ))

    @api.onchange('spouse_fiscal_status')
    def _onchange_spouse_fiscal_status(self):
        self.spouse_net_revenue = 0.0
        self.spouse_other_net_revenue = 0.0

    @api.onchange('disabled_children_bool')
    def _onchange_disabled_children_bool(self):
        self.disabled_children_number = 0

    @api.onchange('other_dependent_people')
    def _onchange_other_dependent_people(self):
        self.other_senior_dependent = 0.0
        self.other_disabled_senior_dependent = 0.0
        self.other_juniors_dependent = 0.0
        self.other_disabled_juniors_dependent = 0.0

    @api.depends('disabled_children_bool', 'disabled_children_number',
                 'children')
    def _compute_dependent_children(self):
        for employee in self:
            if employee.disabled_children_bool:
                employee.dependent_children = employee.children + employee.disabled_children_number
            else:
                employee.dependent_children = employee.children

    @api.depends('other_dependent_people', 'other_senior_dependent',
                 'other_disabled_senior_dependent', 'other_juniors_dependent',
                 'other_disabled_juniors_dependent')
    def _compute_dependent_people(self):
        for employee in self:
            employee.dependent_seniors = employee.other_senior_dependent + employee.other_disabled_senior_dependent
            employee.dependent_juniors = employee.other_juniors_dependent + employee.other_disabled_juniors_dependent
Esempio n. 12
0
class PaymentAcquirer(models.Model):
    _inherit = 'payment.acquirer'

    provider = fields.Selection(
        selection_add=[('payulatam', 'PayU Latam')], ondelete={'payulatam': 'set default'})
    payulatam_merchant_id = fields.Char(
        string="PayU Latam Merchant ID",
        help="The ID solely used to identify the account with PayULatam",
        required_if_provider='payulatam')
    payulatam_account_id = fields.Char(
        string="PayU Latam Account ID",
        help="The ID solely used to identify the country-dependent shop with PayULatam",
        required_if_provider='payulatam')
    payulatam_api_key = fields.Char(
        string="PayU Latam API Key", required_if_provider='payulatam',
        groups='base.group_system')

    @api.model
    def _get_compatible_acquirers(self, *args, currency_id=None, **kwargs):
        """ Override of payment to unlist PayU Latam acquirers for unsupported currencies. """
        acquirers = super()._get_compatible_acquirers(*args, currency_id=currency_id, **kwargs)

        currency = self.env['res.currency'].browse(currency_id).exists()
        if currency and currency.name not in SUPPORTED_CURRENCIES:
            acquirers = acquirers.filtered(lambda a: a.provider != 'payulatam')

        return acquirers

    def _payulatam_generate_sign(self, values, incoming=True):
        """ Generate the signature for incoming or outgoing communications.

        :param dict values: The values used to generate the signature
        :param bool incoming: Whether the signature must be generated for an incoming (PayU Latam to
                              Odoo) or outgoing (Odoo to PayU Latam) communication.
        :return: The signature
        :rtype: str
        """
        if incoming:
            data_string = '~'.join([
                self.payulatam_api_key,
                self.payulatam_merchant_id,
                values['referenceCode'],
                # http://developers.payulatam.com/en/web_checkout/integration.html
                # Section: 2. Response page > Signature validation
                # PayU Latam use the "Round half to even" rounding method
                # to generate their signature. This happens to be Python 3's
                # default rounding method.
                float_repr(float(values.get('TX_VALUE')), 1),
                values['currency'],
                values.get('transactionState'),
            ])
        else:
            data_string = '~'.join([
                self.payulatam_api_key,
                self.payulatam_merchant_id,
                values['referenceCode'],
                float_repr(float(values['amount']), 1),
                values['currency'],
            ])
        return md5(data_string.encode('utf-8')).hexdigest()

    def _get_default_payment_method_id(self):
        self.ensure_one()
        if self.provider != 'payulatam':
            return super()._get_default_payment_method_id()
        return self.env.ref('payment_payulatam.payment_method_payulatam').id

    def _neutralize(self):
        super()._neutralize()
        self._neutralize_fields('payulatam', [
            'payulatam_merchant_id',
            'payulatam_account_id',
            'payulatam_api_key',
        ])
Esempio n. 13
0
class Slide(models.Model):
    _name = 'slide.slide'
    _inherit = [
        'mail.thread', 'image.mixin', 'website.seo.metadata',
        'website.published.mixin'
    ]
    _description = 'Slides'
    _mail_post_access = 'read'
    _order_by_strategy = {
        'sequence': 'sequence asc',
        'most_viewed': 'total_views desc',
        'most_voted': 'likes desc',
        'latest': 'date_published desc',
    }
    _order = 'sequence asc, is_category asc'

    # description
    name = fields.Char('Title', required=True, translate=True)
    active = fields.Boolean(default=True, tracking=100)
    sequence = fields.Integer('Sequence', default=0)
    user_id = fields.Many2one('res.users',
                              string='Uploaded by',
                              default=lambda self: self.env.uid)
    description = fields.Text('Description', translate=True)
    channel_id = fields.Many2one('slide.channel',
                                 string="Course",
                                 required=True)
    tag_ids = fields.Many2many('slide.tag',
                               'rel_slide_tag',
                               'slide_id',
                               'tag_id',
                               string='Tags')
    is_preview = fields.Boolean(
        'Allow Preview',
        default=False,
        help=
        "The course is accessible by anyone : the users don't need to join the channel to access the content of the course."
    )
    is_new_slide = fields.Boolean('Is New Slide',
                                  compute='_compute_is_new_slide')
    completion_time = fields.Float(
        'Duration',
        digits=(10, 4),
        help="The estimated completion time for this slide")
    # Categories
    is_category = fields.Boolean('Is a category', default=False)
    category_id = fields.Many2one('slide.slide',
                                  string="Section",
                                  compute="_compute_category_id",
                                  store=True)
    slide_ids = fields.One2many('slide.slide', "category_id", string="Slides")
    # subscribers
    partner_ids = fields.Many2many(
        'res.partner',
        'slide_slide_partner',
        'slide_id',
        'partner_id',
        string='Subscribers',
        groups='website_slides.group_website_slides_officer',
        copy=False)
    slide_partner_ids = fields.One2many(
        'slide.slide.partner',
        'slide_id',
        string='Subscribers information',
        groups='website_slides.group_website_slides_officer',
        copy=False)
    user_membership_id = fields.Many2one(
        'slide.slide.partner',
        string="Subscriber information",
        compute='_compute_user_membership_id',
        compute_sudo=False,
        help="Subscriber information for the current logged in user")
    # Quiz related fields
    question_ids = fields.One2many("slide.question",
                                   "slide_id",
                                   string="Questions")
    questions_count = fields.Integer(string="Numbers of Questions",
                                     compute='_compute_questions_count')
    quiz_first_attempt_reward = fields.Integer("Reward: first attempt",
                                               default=10)
    quiz_second_attempt_reward = fields.Integer("Reward: second attempt",
                                                default=7)
    quiz_third_attempt_reward = fields.Integer(
        "Reward: third attempt",
        default=5,
    )
    quiz_fourth_attempt_reward = fields.Integer(
        "Reward: every attempt after the third try", default=2)
    # content
    slide_type = fields.Selection(
        [('infographic', 'Infographic'), ('webpage', 'Web Page'),
         ('presentation', 'Presentation'), ('document', 'Document'),
         ('video', 'Video'), ('quiz', "Quiz")],
        string='Type',
        required=True,
        default='document',
        help=
        "The document type will be set automatically based on the document URL and properties (e.g. height and width for presentation and document)."
    )
    datas = fields.Binary('Content', attachment=True)
    url = fields.Char('Document URL', help="Youtube or Google Document URL")
    document_id = fields.Char('Document ID',
                              help="Youtube or Google Document ID")
    link_ids = fields.One2many('slide.slide.link',
                               'slide_id',
                               string="External URL for this slide")
    slide_resource_ids = fields.One2many(
        'slide.slide.resource',
        'slide_id',
        string="Additional Resource for this slide")
    slide_resource_downloadable = fields.Boolean(
        'Allow Download',
        default=False,
        help="Allow the user to download the content of the slide.")
    mime_type = fields.Char('Mime-type')
    html_content = fields.Html(
        "HTML Content",
        help="Custom HTML content for slides of type 'Web Page'.",
        translate=True,
        sanitize_form=False)
    # website
    website_id = fields.Many2one(related='channel_id.website_id',
                                 readonly=True)
    date_published = fields.Datetime('Publish Date', readonly=True, tracking=1)
    likes = fields.Integer('Likes',
                           compute='_compute_user_info',
                           store=True,
                           compute_sudo=False)
    dislikes = fields.Integer('Dislikes',
                              compute='_compute_user_info',
                              store=True,
                              compute_sudo=False)
    user_vote = fields.Integer('User vote',
                               compute='_compute_user_info',
                               compute_sudo=False)
    embed_code = fields.Text('Embed Code',
                             readonly=True,
                             compute='_compute_embed_code')
    # views
    embedcount_ids = fields.One2many('slide.embed',
                                     'slide_id',
                                     string="Embed Count")
    slide_views = fields.Integer('# of Website Views',
                                 store=True,
                                 compute="_compute_slide_views")
    public_views = fields.Integer('# of Public Views', copy=False)
    total_views = fields.Integer("Views",
                                 default="0",
                                 compute='_compute_total',
                                 store=True)
    # comments
    comments_count = fields.Integer('Number of comments',
                                    compute="_compute_comments_count")
    # channel
    channel_type = fields.Selection(related="channel_id.channel_type",
                                    string="Channel type")
    channel_allow_comment = fields.Boolean(related="channel_id.allow_comment",
                                           string="Allows comment")
    # Statistics in case the slide is a category
    nbr_presentation = fields.Integer("Number of Presentations",
                                      compute='_compute_slides_statistics',
                                      store=True)
    nbr_document = fields.Integer("Number of Documents",
                                  compute='_compute_slides_statistics',
                                  store=True)
    nbr_video = fields.Integer("Number of Videos",
                               compute='_compute_slides_statistics',
                               store=True)
    nbr_infographic = fields.Integer("Number of Infographics",
                                     compute='_compute_slides_statistics',
                                     store=True)
    nbr_webpage = fields.Integer("Number of Webpages",
                                 compute='_compute_slides_statistics',
                                 store=True)
    nbr_quiz = fields.Integer("Number of Quizs",
                              compute="_compute_slides_statistics",
                              store=True)
    total_slides = fields.Integer(compute='_compute_slides_statistics',
                                  store=True)

    _sql_constraints = [(
        'exclusion_html_content_and_url',
        "CHECK(html_content IS NULL OR url IS NULL)",
        "A slide is either filled with a document url or HTML content. Not both."
    )]

    @api.depends('date_published', 'is_published')
    def _compute_is_new_slide(self):
        for slide in self:
            slide.is_new_slide = slide.date_published > fields.Datetime.now(
            ) - relativedelta(days=7) if slide.is_published else False

    @api.depends('channel_id.slide_ids.is_category',
                 'channel_id.slide_ids.sequence')
    def _compute_category_id(self):
        """ Will take all the slides of the channel for which the index is higher
        than the index of this category and lower than the index of the next category.

        Lists are manually sorted because when adding a new browse record order
        will not be correct as the added slide would actually end up at the
        first place no matter its sequence."""
        self.category_id = False  # initialize whatever the state

        channel_slides = {}
        for slide in self:
            if slide.channel_id.id not in channel_slides:
                channel_slides[
                    slide.channel_id.id] = slide.channel_id.slide_ids

        for cid, slides in channel_slides.items():
            current_category = self.env['slide.slide']
            slide_list = list(slides)
            slide_list.sort(key=lambda s: (s.sequence, not s.is_category))
            for slide in slide_list:
                if slide.is_category:
                    current_category = slide
                elif slide.category_id != current_category:
                    slide.category_id = current_category.id

    @api.depends('question_ids')
    def _compute_questions_count(self):
        for slide in self:
            slide.questions_count = len(slide.question_ids)

    @api.depends('website_message_ids.res_id', 'website_message_ids.model',
                 'website_message_ids.message_type')
    def _compute_comments_count(self):
        for slide in self:
            slide.comments_count = len(slide.website_message_ids)

    @api.depends('slide_views', 'public_views')
    def _compute_total(self):
        for record in self:
            record.total_views = record.slide_views + record.public_views

    @api.depends('slide_partner_ids.vote')
    @api.depends_context('uid')
    def _compute_user_info(self):
        slide_data = dict.fromkeys(
            self.ids, dict({
                'likes': 0,
                'dislikes': 0,
                'user_vote': False
            }))
        slide_partners = self.env['slide.slide.partner'].sudo().search([
            ('slide_id', 'in', self.ids)
        ])
        for slide_partner in slide_partners:
            if slide_partner.vote == 1:
                slide_data[slide_partner.slide_id.id]['likes'] += 1
                if slide_partner.partner_id == self.env.user.partner_id:
                    slide_data[slide_partner.slide_id.id]['user_vote'] = 1
            elif slide_partner.vote == -1:
                slide_data[slide_partner.slide_id.id]['dislikes'] += 1
                if slide_partner.partner_id == self.env.user.partner_id:
                    slide_data[slide_partner.slide_id.id]['user_vote'] = -1
        for slide in self:
            slide.update(slide_data[slide.id])

    @api.depends('slide_partner_ids.slide_id')
    def _compute_slide_views(self):
        # TODO awa: tried compute_sudo, for some reason it doesn't work in here...
        read_group_res = self.env['slide.slide.partner'].sudo().read_group(
            [('slide_id', 'in', self.ids)], ['slide_id'], groupby=['slide_id'])
        mapped_data = dict((res['slide_id'][0], res['slide_id_count'])
                           for res in read_group_res)
        for slide in self:
            slide.slide_views = mapped_data.get(slide.id, 0)

    @api.depends('slide_ids.sequence', 'slide_ids.slide_type',
                 'slide_ids.is_published', 'slide_ids.is_category')
    def _compute_slides_statistics(self):
        # Do not use dict.fromkeys(self.ids, dict()) otherwise it will use the same dictionnary for all keys.
        # Therefore, when updating the dict of one key, it updates the dict of all keys.
        keys = [
            'nbr_%s' % slide_type for slide_type in
            self.env['slide.slide']._fields['slide_type'].get_values(self.env)
        ]
        default_vals = dict((key, 0) for key in keys + ['total_slides'])

        res = self.env['slide.slide'].read_group(
            [('is_published', '=', True), ('category_id', 'in', self.ids),
             ('is_category', '=', False)], ['category_id', 'slide_type'],
            ['category_id', 'slide_type'],
            lazy=False)

        type_stats = self._compute_slides_statistics_type(res)

        for record in self:
            record.update(type_stats.get(record._origin.id, default_vals))

    def _compute_slides_statistics_type(self, read_group_res):
        """ Compute statistics based on all existing slide types """
        slide_types = self.env['slide.slide']._fields['slide_type'].get_values(
            self.env)
        keys = ['nbr_%s' % slide_type for slide_type in slide_types]
        result = dict((cid, dict((key, 0) for key in keys + ['total_slides']))
                      for cid in self.ids)
        for res_group in read_group_res:
            cid = res_group['category_id'][0]
            slide_type = res_group.get('slide_type')
            if slide_type:
                slide_type_count = res_group.get('__count', 0)
                result[cid]['nbr_%s' % slide_type] = slide_type_count
                result[cid]['total_slides'] += slide_type_count
        return result

    @api.depends('slide_partner_ids.partner_id')
    @api.depends('uid')
    def _compute_user_membership_id(self):
        slide_partners = self.env['slide.slide.partner'].sudo().search([
            ('slide_id', 'in', self.ids),
            ('partner_id', '=', self.env.user.partner_id.id),
        ])

        for record in self:
            record.user_membership_id = next(
                (slide_partner for slide_partner in slide_partners
                 if slide_partner.slide_id == record),
                self.env['slide.slide.partner'])

    @api.depends('document_id', 'slide_type', 'mime_type')
    def _compute_embed_code(self):
        base_url = request and request.httprequest.url_root or self.env[
            'ir.config_parameter'].sudo().get_param('web.base.url')
        if base_url[-1] == '/':
            base_url = base_url[:-1]
        for record in self:
            if record.datas and (not record.document_id or record.slide_type
                                 in ['document', 'presentation']):
                slide_url = base_url + url_for(
                    '/slides/embed/%s?page=1' % record.id)
                record.embed_code = '<iframe src="%s" class="o_wslides_iframe_viewer" allowFullScreen="true" height="%s" width="%s" frameborder="0"></iframe>' % (
                    slide_url, 315, 420)
            elif record.slide_type == 'video' and record.document_id:
                if not record.mime_type:
                    # embed youtube video
                    record.embed_code = '<iframe src="//www.youtube.com/embed/%s?theme=light" allowFullScreen="true" frameborder="0"></iframe>' % (
                        record.document_id)
                else:
                    # embed google doc video
                    record.embed_code = '<iframe src="//drive.google.com/file/d/%s/preview" allowFullScreen="true" frameborder="0"></iframe>' % (
                        record.document_id)
            else:
                record.embed_code = False

    @api.onchange('url')
    def _on_change_url(self):
        self.ensure_one()
        if self.url:
            res = self._parse_document_url(self.url)
            if res.get('error'):
                raise Warning(res.get('error'))
            values = res['values']
            if not values.get('document_id'):
                raise Warning(
                    _('Please enter valid Youtube or Google Doc URL'))
            for key, value in values.items():
                self[key] = value

    @api.onchange('datas')
    def _on_change_datas(self):
        """ For PDFs, we assume that it takes 5 minutes to read a page.
            If the selected file is not a PDF, it is an image (You can
            only upload PDF or Image file) then the slide_type is changed
            into infographic and the uploaded dataS is transfered to the
            image field. (It avoids the infinite loading in PDF viewer)"""
        if self.datas:
            data = base64.b64decode(self.datas)
            if data.startswith(b'%PDF-'):
                pdf = PyPDF2.PdfFileReader(io.BytesIO(data),
                                           overwriteWarnings=False)
                self.completion_time = (5 * len(pdf.pages)) / 60
            else:
                self.slide_type = 'infographic'
                self.image_1920 = self.datas
                self.datas = None

    @api.depends('name', 'channel_id.website_id.domain')
    def _compute_website_url(self):
        # TDE FIXME: clena this link.tracker strange stuff
        super(Slide, self)._compute_website_url()
        for slide in self:
            if slide.id:  # avoid to perform a slug on a not yet saved record in case of an onchange.
                base_url = slide.channel_id.get_base_url()
                # link_tracker is not in dependencies, so use it to shorten url only if installed.
                if self.env.registry.get('link.tracker'):
                    url = self.env['link.tracker'].sudo().create({
                        'url':
                        '%s/slides/slide/%s' % (base_url, slug(slide)),
                        'title':
                        slide.name,
                    }).short_url
                else:
                    url = '%s/slides/slide/%s' % (base_url, slug(slide))
                slide.website_url = url

    @api.depends('channel_id.can_publish')
    def _compute_can_publish(self):
        for record in self:
            record.can_publish = record.channel_id.can_publish

    @api.model
    def _get_can_publish_error_message(self):
        return _(
            "Publishing is restricted to the responsible of training courses or members of the publisher group for documentation courses"
        )

    # ---------------------------------------------------------
    # ORM Overrides
    # ---------------------------------------------------------

    @api.model
    def create(self, values):
        # Do not publish slide if user has not publisher rights
        channel = self.env['slide.channel'].browse(values['channel_id'])
        if not channel.can_publish:
            # 'website_published' is handled by mixin
            values['date_published'] = False

        if values.get('slide_type'
                      ) == 'infographic' and not values.get('image_1920'):
            values['image_1920'] = values['datas']
        if values.get('is_category'):
            values['is_preview'] = True
            values['is_published'] = True
        if values.get('is_published') and not values.get('date_published'):
            values['date_published'] = datetime.datetime.now()
        if values.get('url') and not values.get('document_id'):
            doc_data = self._parse_document_url(values['url']).get(
                'values', dict())
            for key, value in doc_data.items():
                values.setdefault(key, value)

        slide = super(Slide, self).create(values)

        if slide.is_published and not slide.is_category:
            slide._post_publication()
        return slide

    def write(self, values):
        if values.get('url') and values['url'] != self.url:
            doc_data = self._parse_document_url(values['url']).get(
                'values', dict())
            for key, value in doc_data.items():
                values.setdefault(key, value)
        if values.get('is_category'):
            values['is_preview'] = True
            values['is_published'] = True

        res = super(Slide, self).write(values)
        if values.get('is_published'):
            self.date_published = datetime.datetime.now()
            self._post_publication()

        if 'is_published' in values or 'active' in values:
            # if the slide is published/unpublished, recompute the completion for the partners
            self.slide_partner_ids._set_completed_callback()

        return res

    @api.returns('self', lambda value: value.id)
    def copy(self, default=None):
        """Sets the sequence to zero so that it always lands at the beginning
        of the newly selected course as an uncategorized slide"""
        rec = super(Slide, self).copy(default)
        rec.sequence = 0
        return rec

    def unlink(self):
        if self.question_ids and self.channel_id.channel_partner_ids:
            raise UserError(
                _("People already took this quiz. To keep course progression it should not be deleted."
                  ))
        super(Slide, self).unlink()

    # ---------------------------------------------------------
    # Mail/Rating
    # ---------------------------------------------------------

    @api.returns('mail.message', lambda value: value.id)
    def message_post(self, *, message_type='notification', **kwargs):
        self.ensure_one()
        if message_type == 'comment' and not self.channel_id.can_comment:  # user comments have a restriction on karma
            raise AccessError(_('Not enough karma to comment'))
        return super(Slide, self).message_post(message_type=message_type,
                                               **kwargs)

    def get_access_action(self, access_uid=None):
        """ Instead of the classic form view, redirect to website if it is published. """
        self.ensure_one()
        if self.website_published:
            return {
                'type': 'ir.actions.act_url',
                'url': '%s' % self.website_url,
                'target': 'self',
                'target_type': 'public',
                'res_id': self.id,
            }
        return super(Slide, self).get_access_action(access_uid)

    def _notify_get_groups(self):
        """ Add access button to everyone if the document is active. """
        groups = super(Slide, self)._notify_get_groups()

        if self.website_published:
            for group_name, group_method, group_data in groups:
                group_data['has_button_access'] = True

        return groups

    # ---------------------------------------------------------
    # Business Methods
    # ---------------------------------------------------------

    def _post_publication(self):
        base_url = self.env['ir.config_parameter'].sudo().get_param(
            'web.base.url')
        for slide in self.filtered(lambda slide: slide.website_published and
                                   slide.channel_id.publish_template_id):
            publish_template = slide.channel_id.publish_template_id
            html_body = publish_template.with_context(
                base_url=base_url)._render_field('body_html',
                                                 slide.ids)[slide.id]
            subject = publish_template._render_field('subject',
                                                     slide.ids)[slide.id]
            slide.channel_id.with_context(
                mail_create_nosubscribe=True).message_post(
                    subject=subject,
                    body=html_body,
                    subtype_xmlid='website_slides.mt_channel_slide_published',
                    email_layout_xmlid='mail.mail_notification_light',
                )
        return True

    def _generate_signed_token(self, partner_id):
        """ Lazy generate the acces_token and return it signed by the given partner_id
            :rtype tuple (string, int)
            :return (signed_token, partner_id)
        """
        if not self.access_token:
            self.write({'access_token': self._default_access_token()})
        return self._sign_token(partner_id)

    def _send_share_email(self, email, fullscreen):
        # TDE FIXME: template to check
        mail_ids = []
        base_url = self.env['ir.config_parameter'].sudo().get_param(
            'web.base.url')
        for record in self:
            template = self.channel_id.share_template_id.with_context(
                user=self.env.user,
                email=email,
                base_url=base_url,
                fullscreen=fullscreen)
            email_values = {'email_to': email}
            if self.env.user.has_group('base.group_portal'):
                template = template.sudo()
                email_values[
                    'email_from'] = self.env.company.catchall_formatted or self.env.company.email_formatted

            mail_ids.append(
                template.send_mail(record.id,
                                   notif_layout='mail.mail_notification_light',
                                   email_values=email_values))
        return mail_ids

    def action_like(self):
        self.check_access_rights('read')
        self.check_access_rule('read')
        return self._action_vote(upvote=True)

    def action_dislike(self):
        self.check_access_rights('read')
        self.check_access_rule('read')
        return self._action_vote(upvote=False)

    def _action_vote(self, upvote=True):
        """ Private implementation of voting. It does not check for any real access
        rights; public methods should grant access before calling this method.

          :param upvote: if True, is a like; if False, is a dislike
        """
        self_sudo = self.sudo()
        SlidePartnerSudo = self.env['slide.slide.partner'].sudo()
        slide_partners = SlidePartnerSudo.search([
            ('slide_id', 'in', self.ids),
            ('partner_id', '=', self.env.user.partner_id.id)
        ])
        slide_id = slide_partners.mapped('slide_id')
        new_slides = self_sudo - slide_id
        channel = slide_id.channel_id
        karma_to_add = 0

        for slide_partner in slide_partners:
            if upvote:
                new_vote = 0 if slide_partner.vote == -1 else 1
                if slide_partner.vote != 1:
                    karma_to_add += channel.karma_gen_slide_vote
            else:
                new_vote = 0 if slide_partner.vote == 1 else -1
                if slide_partner.vote != -1:
                    karma_to_add -= channel.karma_gen_slide_vote
            slide_partner.vote = new_vote

        for new_slide in new_slides:
            new_vote = 1 if upvote else -1
            new_slide.write({
                'slide_partner_ids': [(0, 0, {
                    'vote':
                    new_vote,
                    'partner_id':
                    self.env.user.partner_id.id
                })]
            })
            karma_to_add += new_slide.channel_id.karma_gen_slide_vote * (
                1 if upvote else -1)

        if karma_to_add:
            self.env.user.add_karma(karma_to_add)

    def action_set_viewed(self, quiz_attempts_inc=False):
        if not all(slide.channel_id.is_member for slide in self):
            raise UserError(
                _('You cannot mark a slide as viewed if you are not among its members.'
                  ))

        return bool(
            self._action_set_viewed(self.env.user.partner_id,
                                    quiz_attempts_inc=quiz_attempts_inc))

    def _action_set_viewed(self, target_partner, quiz_attempts_inc=False):
        self_sudo = self.sudo()
        SlidePartnerSudo = self.env['slide.slide.partner'].sudo()
        existing_sudo = SlidePartnerSudo.search([('slide_id', 'in', self.ids),
                                                 ('partner_id', '=',
                                                  target_partner.id)])
        if quiz_attempts_inc:
            for exsting_slide in existing_sudo:
                exsting_slide.write({
                    'quiz_attempts_count':
                    exsting_slide.quiz_attempts_count + 1
                })

        new_slides = self_sudo - existing_sudo.mapped('slide_id')
        return SlidePartnerSudo.create([{
            'slide_id':
            new_slide.id,
            'channel_id':
            new_slide.channel_id.id,
            'partner_id':
            target_partner.id,
            'quiz_attempts_count':
            1 if quiz_attempts_inc else 0,
            'vote':
            0
        } for new_slide in new_slides])

    def action_set_completed(self):
        if not all(slide.channel_id.is_member for slide in self):
            raise UserError(
                _('You cannot mark a slide as completed if you are not among its members.'
                  ))

        return self._action_set_completed(self.env.user.partner_id)

    def _action_set_completed(self, target_partner):
        self_sudo = self.sudo()
        SlidePartnerSudo = self.env['slide.slide.partner'].sudo()
        existing_sudo = SlidePartnerSudo.search([('slide_id', 'in', self.ids),
                                                 ('partner_id', '=',
                                                  target_partner.id)])
        existing_sudo.write({'completed': True})

        new_slides = self_sudo - existing_sudo.mapped('slide_id')
        SlidePartnerSudo.create([{
            'slide_id': new_slide.id,
            'channel_id': new_slide.channel_id.id,
            'partner_id': target_partner.id,
            'vote': 0,
            'completed': True
        } for new_slide in new_slides])

        return True

    def _action_set_quiz_done(self):
        if not all(slide.channel_id.is_member for slide in self):
            raise UserError(
                _('You cannot mark a slide quiz as completed if you are not among its members.'
                  ))

        points = 0
        for slide in self:
            user_membership_sudo = slide.user_membership_id.sudo()
            if not user_membership_sudo or user_membership_sudo.completed or not user_membership_sudo.quiz_attempts_count:
                continue

            gains = [
                slide.quiz_first_attempt_reward,
                slide.quiz_second_attempt_reward,
                slide.quiz_third_attempt_reward,
                slide.quiz_fourth_attempt_reward
            ]
            points += gains[
                user_membership_sudo.quiz_attempts_count -
                1] if user_membership_sudo.quiz_attempts_count <= len(
                    gains) else gains[-1]

        return self.env.user.sudo().add_karma(points)

    def _compute_quiz_info(self, target_partner, quiz_done=False):
        result = dict.fromkeys(self.ids, False)
        slide_partners = self.env['slide.slide.partner'].sudo().search([
            ('slide_id', 'in', self.ids),
            ('partner_id', '=', target_partner.id)
        ])
        slide_partners_map = dict(
            (sp.slide_id.id, sp) for sp in slide_partners)
        for slide in self:
            if not slide.question_ids:
                gains = [0]
            else:
                gains = [
                    slide.quiz_first_attempt_reward,
                    slide.quiz_second_attempt_reward,
                    slide.quiz_third_attempt_reward,
                    slide.quiz_fourth_attempt_reward
                ]
            result[slide.id] = {
                'quiz_karma_max':
                gains[0],  # what could be gained if succeed at first try
                'quiz_karma_gain':
                gains[0],  # what would be gained at next test
                'quiz_karma_won': 0,  # what has been gained
                'quiz_attempts_count': 0,  # number of attempts
            }
            slide_partner = slide_partners_map.get(slide.id)
            if slide.question_ids and slide_partner:
                if slide_partner.quiz_attempts_count:
                    result[slide.id]['quiz_karma_gain'] = gains[
                        slide_partner.
                        quiz_attempts_count] if slide_partner.quiz_attempts_count < len(
                            gains) else gains[-1]
                    result[slide.id][
                        'quiz_attempts_count'] = slide_partner.quiz_attempts_count
                if quiz_done or slide_partner.completed:
                    result[slide.id]['quiz_karma_won'] = gains[
                        slide_partner.quiz_attempts_count -
                        1] if slide_partner.quiz_attempts_count < len(
                            gains) else gains[-1]
        return result

    # --------------------------------------------------
    # Parsing methods
    # --------------------------------------------------

    @api.model
    def _fetch_data(self, base_url, params, content_type=False):
        result = {'values': dict()}
        try:
            response = requests.get(base_url, timeout=3, params=params)
            response.raise_for_status()
            if content_type == 'json':
                result['values'] = response.json()
            elif content_type in ('image', 'pdf'):
                result['values'] = base64.b64encode(response.content)
            else:
                result['values'] = response.content
        except requests.exceptions.HTTPError as e:
            result['error'] = e.response.content
        except requests.exceptions.ConnectionError as e:
            result['error'] = str(e)
        return result

    def _find_document_data_from_url(self, url):
        url_obj = urls.url_parse(url)
        if url_obj.ascii_host == 'youtu.be':
            return ('youtube', url_obj.path[1:] if url_obj.path else False)
        elif url_obj.ascii_host in ('youtube.com', 'www.youtube.com',
                                    'm.youtube.com'):
            v_query_value = url_obj.decode_query().get('v')
            if v_query_value:
                return ('youtube', v_query_value)
            split_path = url_obj.path.split('/')
            if len(split_path) >= 3 and split_path[1] in ('v', 'embed'):
                return ('youtube', split_path[2])

        expr = re.compile(
            r'(^https:\/\/docs.google.com|^https:\/\/drive.google.com).*\/d\/([^\/]*)'
        )
        arg = expr.match(url)
        document_id = arg and arg.group(2) or False
        if document_id:
            return ('google', document_id)

        return (None, False)

    def _parse_document_url(self, url, only_preview_fields=False):
        document_source, document_id = self._find_document_data_from_url(url)
        if document_source and hasattr(self,
                                       '_parse_%s_document' % document_source):
            return getattr(self, '_parse_%s_document' % document_source)(
                document_id, only_preview_fields)
        return {'error': _('Unknown document')}

    def _parse_youtube_document(self, document_id, only_preview_fields):
        """ If we receive a duration (YT video), we use it to determine the slide duration.
        The received duration is under a special format (e.g: PT1M21S15, meaning 1h 21m 15s). """

        key = self.env['website'].get_current_website(
        ).website_slide_google_app_key
        fetch_res = self._fetch_data(
            'https://www.googleapis.com/youtube/v3/videos', {
                'id': document_id,
                'key': key,
                'part': 'snippet,contentDetails',
                'fields': 'items(id,snippet,contentDetails)'
            }, 'json')
        if fetch_res.get('error'):
            return {
                'error':
                self._extract_google_error_message(fetch_res.get('error'))
            }

        values = {'slide_type': 'video', 'document_id': document_id}
        items = fetch_res['values'].get('items')
        if not items:
            return {'error': _('Please enter valid Youtube or Google Doc URL')}
        youtube_values = items[0]

        youtube_duration = youtube_values.get('contentDetails',
                                              {}).get('duration')
        if youtube_duration:
            parsed_duration = re.search(
                r'^PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?$', youtube_duration)
            values['completion_time'] = (int(parsed_duration.group(1) or 0)) + \
                                        (int(parsed_duration.group(2) or 0) / 60) + \
                                        (int(parsed_duration.group(3) or 0) / 3600)

        if youtube_values.get('snippet'):
            snippet = youtube_values['snippet']
            if only_preview_fields:
                values.update({
                    'url_src': snippet['thumbnails']['high']['url'],
                    'title': snippet['title'],
                    'description': snippet['description']
                })

                return values

            values.update({
                'name':
                snippet['title'],
                'image_1920':
                self._fetch_data(snippet['thumbnails']['high']['url'], {},
                                 'image')['values'],
                'description':
                snippet['description'],
                'mime_type':
                False,
            })
        return {'values': values}

    def _extract_google_error_message(self, error):
        """
        See here for Google error format
        https://developers.google.com/drive/api/v3/handle-errors
        """
        try:
            error = json.loads(error)
            error = (error.get('error', {}).get('errors', [])
                     or [{}])[0].get('reason')
        except json.decoder.JSONDecodeError:
            error = str(error)

        if error == 'keyInvalid':
            return _(
                'Your Google API key is invalid, please update it into your settings.\nSettings > Website > Features > API Key'
            )

        return _(
            'Could not fetch data from url. Document or access right not available:\n%s'
        ) % error

    @api.model
    def _parse_google_document(self, document_id, only_preview_fields):
        def get_slide_type(vals):
            # TDE FIXME: WTF ??
            slide_type = 'presentation'
            if vals.get('image_1920'):
                image = Image.open(
                    io.BytesIO(base64.b64decode(vals['image_1920'])))
                width, height = image.size
                if height > width:
                    return 'document'
            return slide_type

        # Google drive doesn't use a simple API key to access the data, but requires an access
        # token. However, this token is generated in module google_drive, which is not in the
        # dependencies of website_slides. We still keep the 'key' parameter just in case, but that
        # is probably useless.
        params = {}
        params['projection'] = 'BASIC'
        if 'google.drive.config' in self.env:
            access_token = self.env['google.drive.config'].get_access_token()
            if access_token:
                params['access_token'] = access_token
        if not params.get('access_token'):
            params['key'] = self.env['website'].get_current_website(
            ).website_slide_google_app_key

        fetch_res = self._fetch_data(
            'https://www.googleapis.com/drive/v2/files/%s' % document_id,
            params, "json")
        if fetch_res.get('error'):
            return {
                'error':
                self._extract_google_error_message(fetch_res.get('error'))
            }

        google_values = fetch_res['values']
        if only_preview_fields:
            return {
                'url_src': google_values['thumbnailLink'],
                'title': google_values['title'],
            }

        values = {
            'name':
            google_values['title'],
            'image_1920':
            self._fetch_data(
                google_values['thumbnailLink'].replace('=s220', ''), {},
                'image')['values'],
            'mime_type':
            google_values['mimeType'],
            'document_id':
            document_id,
        }
        if google_values['mimeType'].startswith('video/'):
            values['slide_type'] = 'video'
        elif google_values['mimeType'].startswith('image/'):
            values['datas'] = values['image_1920']
            values['slide_type'] = 'infographic'
        elif google_values['mimeType'].startswith(
                'application/vnd.google-apps'):
            values['slide_type'] = get_slide_type(values)
            if 'exportLinks' in google_values:
                values['datas'] = self._fetch_data(
                    google_values['exportLinks']['application/pdf'], params,
                    'pdf')['values']
        elif google_values['mimeType'] == 'application/pdf':
            # TODO: Google Drive PDF document doesn't provide plain text transcript
            values['datas'] = self._fetch_data(google_values['webContentLink'],
                                               {}, 'pdf')['values']
            values['slide_type'] = get_slide_type(values)

        return {'values': values}

    def _default_website_meta(self):
        res = super(Slide, self)._default_website_meta()
        res['default_opengraph']['og:title'] = res['default_twitter'][
            'twitter:title'] = self.name
        res['default_opengraph']['og:description'] = res['default_twitter'][
            'twitter:description'] = self.description
        res['default_opengraph']['og:image'] = res['default_twitter'][
            'twitter:image'] = self.env['website'].image_url(
                self, 'image_1024')
        res['default_meta_description'] = self.description
        return res

    # ---------------------------------------------------------
    # Data / Misc
    # ---------------------------------------------------------

    def get_backend_menu_id(self):
        return self.env.ref('website_slides.website_slides_menu_root').id
Esempio n. 14
0
class HrEmployee(models.Model):
    _inherit = 'hr.employee'

    gender = fields.Selection(selection_add=[('transgender', 'Transgender')])
Esempio n. 15
0
class PurchaseRequisition(models.Model):
    _name = "purchase.requisition"
    _description = "Purchase Requisition"
    _inherit = ['mail.thread']
    _order = "id desc"

    def _get_picking_in(self):
        pick_in = self.env.ref('stock.picking_type_in')
        if not pick_in:
            company = self.env['res.company']._company_default_get(
                'purchase.requisition')
            pick_in = self.env['stock.picking.type'].search(
                [('warehouse_id.company_id', '=', company.id),
                 ('code', '=', 'incoming')],
                limit=1,
            )
        return pick_in

    def _get_type_id(self):
        return self.env['purchase.requisition.type'].search([], limit=1)

    name = fields.Char(string='Agreement Reference',
                       required=True,
                       copy=False,
                       default='New',
                       readonly=True)
    origin = fields.Char(string='Source Document')
    order_count = fields.Integer(compute='_compute_orders_number',
                                 string='Number of Orders')
    vendor_id = fields.Many2one('res.partner', string="Vendor")
    type_id = fields.Many2one('purchase.requisition.type',
                              string="Agreement Type",
                              required=True,
                              default=_get_type_id)
    ordering_date = fields.Date(string="Ordering Date",
                                track_visibility='onchange')
    date_end = fields.Datetime(string='Agreement Deadline',
                               track_visibility='onchange')
    schedule_date = fields.Date(
        string='Delivery Date',
        index=True,
        help=
        "The expected and scheduled delivery date where all the products are received",
        track_visibility='onchange')
    user_id = fields.Many2one('res.users',
                              string='Purchase Representative',
                              default=lambda self: self.env.user)
    description = fields.Text()
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 required=True,
                                 default=lambda self: self.env['res.company'].
                                 _company_default_get('purchase.requisition'))
    purchase_ids = fields.One2many('purchase.order',
                                   'requisition_id',
                                   string='Purchase Orders',
                                   states={'done': [('readonly', True)]})
    line_ids = fields.One2many('purchase.requisition.line',
                               'requisition_id',
                               string='Products to Purchase',
                               states={'done': [('readonly', True)]},
                               copy=True)
    warehouse_id = fields.Many2one('stock.warehouse', string='Warehouse')
    state = fields.Selection(PURCHASE_REQUISITION_STATES,
                             'Status',
                             track_visibility='onchange',
                             required=True,
                             copy=False,
                             default='draft')
    state_blanket_order = fields.Selection(PURCHASE_REQUISITION_STATES,
                                           compute='_set_state')
    account_analytic_id = fields.Many2one('account.analytic.account',
                                          'Analytic Account')
    picking_type_id = fields.Many2one('stock.picking.type',
                                      'Operation Type',
                                      required=True,
                                      default=_get_picking_in)
    is_quantity_copy = fields.Selection(related='type_id.quantity_copy',
                                        readonly=True)
    currency_id = fields.Many2one(
        'res.currency',
        'Currency',
        required=True,
        default=lambda self: self.env.user.company_id.currency_id.id)

    @api.depends('state')
    def _set_state(self):
        self.state_blanket_order = self.state

    @api.onchange('vendor_id')
    def _onchange_vendor(self):
        requisitions = self.env['purchase.requisition'].search([
            ('vendor_id', '=', self.vendor_id.id),
            ('state', '=', 'ongoing'),
            ('type_id.quantity_copy', '=', 'none'),
        ])
        if any(requisitions):
            title = _("Warning for %s") % self.vendor_id.name
            message = _(
                "There is already an open blanket order for this supplier. We suggest you to use to complete this open blanket order instead of creating a new one."
            )
            warning = {'title': title, 'message': message}
            return {'warning': warning}

    @api.multi
    @api.depends('purchase_ids')
    def _compute_orders_number(self):
        for requisition in self:
            requisition.order_count = len(requisition.purchase_ids)

    @api.multi
    def action_cancel(self):
        # try to set all associated quotations to cancel state
        for requisition in self:
            for requisition_line in requisition.line_ids:
                requisition_line.supplier_info_ids.unlink()
            requisition.purchase_ids.button_cancel()
            for po in requisition.purchase_ids:
                po.message_post(body=_(
                    'Cancelled by the agreement associated to this quotation.')
                                )
        self.write({'state': 'cancel'})

    @api.multi
    def action_in_progress(self):
        self.ensure_one()
        if not all(obj.line_ids for obj in self):
            raise UserError(
                _('You cannot confirm agreement because there is no product line.'
                  ))
        if self.type_id.quantity_copy == 'none' and self.vendor_id:
            for requisition_line in self.line_ids:
                if requisition_line.price_unit <= 0.0:
                    raise UserError(
                        _('You cannot confirm the blanket order without price.'
                          ))
                if requisition_line.product_qty <= 0.0:
                    raise UserError(
                        _('You cannot confirm the blanket order without quantity.'
                          ))
                requisition_line.create_supplier_info()
            self.write({'state': 'ongoing'})
        else:
            self.write({'state': 'in_progress'})
        # Set the sequence number regarding the requisition type
        if self.name == 'New':
            if self.is_quantity_copy != 'none':
                self.name = self.env['ir.sequence'].next_by_code(
                    'purchase.requisition.purchase.tender')
            else:
                self.name = self.env['ir.sequence'].next_by_code(
                    'purchase.requisition.blanket.order')

    @api.multi
    def action_open(self):
        self.write({'state': 'open'})

    def action_draft(self):
        self.ensure_one()
        self.name = 'New'
        self.write({'state': 'draft'})

    @api.multi
    def action_done(self):
        """
        Generate all purchase order based on selected lines, should only be called on one agreement at a time
        """
        if any(purchase_order.state in ['draft', 'sent', 'to approve']
               for purchase_order in self.mapped('purchase_ids')):
            raise UserError(
                _('You have to cancel or validate every RfQ before closing the purchase requisition.'
                  ))
        for requisition in self:
            for requisition_line in requisition.line_ids:
                requisition_line.supplier_info_ids.unlink()
        self.write({'state': 'done'})

    def _prepare_tender_values(self, product_id, product_qty, product_uom,
                               location_id, name, origin, values):
        return {
            'origin':
            origin,
            'date_end':
            values['date_planned'],
            'warehouse_id':
            values.get('warehouse_id') and values['warehouse_id'].id or False,
            'company_id':
            values['company_id'].id,
            'line_ids': [(0, 0, {
                'product_id':
                product_id.id,
                'product_uom_id':
                product_uom.id,
                'product_qty':
                product_qty,
                'move_dest_id':
                values.get('move_dest_ids') and values['move_dest_ids'][0].id
                or False,
            })],
        }

    def unlink(self):
        if any(requisition.state not in ('draft', 'cancel')
               for requisition in self):
            raise UserError(_('You can only delete draft requisitions.'))
        # Draft requisitions could have some requisition lines.
        self.mapped('line_ids').unlink()
        return super(PurchaseRequisition, self).unlink()
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'
Esempio n. 17
0
class Patient(models.Model):
    _name = "arc.patient"

    name = fields.Char(string="Name", required=True)
    patient_uid = fields.Char(string="Patient ID", readonly=True)
    image = fields.Binary(string="Image")
    small_image = fields.Binary(string="Small Image")
    person_id = fields.Many2one(comodel_name="arc.person", string="Person")
    identity_ids = fields.One2many(comodel_name="arc.identity", inverse_name="patient_id")

    # Contact Details
    email = fields.Char(string="e-Mail")
    mobile = fields.Char(string="Mobile")
    phone = fields.Char(string="Phone")

    # Alternate Contact
    alternate_contact_name = fields.Char(string="Name")
    alternate_contact_relation = fields.Char(string="Relation")
    alternate_mobile = fields.Char(string="Mobile")
    alternate_address = fields.Text(string="Address")

    # Address in Detail
    door_no = fields.Char(string="Door No")
    building_name = fields.Char(string="Building Name")
    street_1 = fields.Char(string="Street 1")
    street_2 = fields.Char(string="Street 2")
    locality = fields.Char(string="locality")
    landmark = fields.Char(string="landmark")
    city = fields.Char(string="City")
    state_id = fields.Many2one(comodel_name="res.country.state", string="State",
                               default=lambda self: self.env.user.company_id.state_id.id)
    country_id = fields.Many2one(comodel_name="res.country", string="Country")
    pin_code = fields.Char(string="Pincode")

    # Personnel Details
    age = fields.Integer(string="Age")
    blood_group = fields.Selection(selection=BLOOD_GROUP, string="Blood Group")
    marital_status = fields.Selection(selection=MARITAL_STATUS, string="Marital Status")
    gender = fields.Selection(selection=GENDER, string="Gender")
    caste = fields.Char(string="Caste")
    religion_id = fields.Many2one(comodel_name="arc.religion", string="Religion")
    physically_challenged = fields.Boolean(string="Physically Challenged")
    nationality_id = fields.Many2one(comodel_name="res.country")
    mother_tongue_id = fields.Many2one(comodel_name="arc.language", string="Mother Tongue")
    language_known_ids = fields.Many2many(comodel_name="arc.language", string="Language Known")
    family_member_ids = fields.One2many(comodel_name="arc.address", inverse_name="patient_id")

    # Medical Detail
    allergic_towards = fields.Text(string="Allergic Towards")

    # Attachment
    attachment_ids = fields.Many2many(comodel_name="ir.attachment", string="Attachment")

    def update_person_address(self):
        recs = {}

        recs["name"] = self.name
        recs["person_uid"] = self.patient_uid
        recs["image"] = self.image
        recs["small_image"] = self.small_image

        recs["email"] = self.email
        recs["mobile"] = self.mobile
        recs["phone"] = self.phone

        recs["door_no"] = self.door_no
        recs["building_name"] = self.building_name
        recs["street_1"] = self.street_1
        recs["street_2"] = self.street_2
        recs["locality"] = self.locality
        recs["landmark"] = self.landmark
        recs["city"] = self.city
        recs["state_id"] = self.state_id.id
        recs["country_id"] = self.country_id.id
        recs["pin_code"] = self.pin_code

        recs["is_patient"] = True

        self.person_id.write(recs)

    @api.multi
    def write(self, vals):
        rec = super(Patient, self).write(vals)
        self.update_person_address()
        return rec

    @api.model
    def create(self, vals):
        data = {"person_uid": self.env["ir.sequence"].next_by_code(self._name),
                "is_patient": True,
                "name": vals["name"]}

        data.update(vals)

        person_id = self.env["arc.person"].create(data)
        vals["person_id"] = person_id.id
        vals["patient_uid"] = data["person_uid"]
        return super(Patient, self).create(vals)
Esempio n. 18
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
                )
Esempio n. 19
0
class PurchaseOrderLine(models.Model):
    _inherit = 'purchase.order.line'

    qty_received_method = fields.Selection(selection_add=[('stock_moves', 'Stock Moves')])

    move_ids = fields.One2many('stock.move', 'purchase_line_id', string='Reservation', readonly=True, copy=False)
    orderpoint_id = fields.Many2one('stock.warehouse.orderpoint', 'Orderpoint', copy=False, index=True)
    move_dest_ids = fields.One2many('stock.move', 'created_purchase_line_id', 'Downstream Moves')
    product_description_variants = fields.Char('Custom Description')
    propagate_cancel = fields.Boolean('Propagate cancellation', default=True)
    forecasted_issue = fields.Boolean(compute='_compute_forecasted_issue')

    def _compute_qty_received_method(self):
        super(PurchaseOrderLine, self)._compute_qty_received_method()
        for line in self.filtered(lambda l: not l.display_type):
            if line.product_id.type in ['consu', 'product']:
                line.qty_received_method = 'stock_moves'

    def _get_po_line_moves(self):
        self.ensure_one()
        moves = self.move_ids.filtered(lambda m: m.product_id == self.product_id)
        if self._context.get('accrual_entry_date'):
            moves = moves.filtered(lambda r: fields.Date.to_date(r.date) <= self._context['accrual_entry_date'])
        return moves

    @api.depends('move_ids.state', 'move_ids.product_uom_qty', 'move_ids.product_uom')
    def _compute_qty_received(self):
        super(PurchaseOrderLine, self)._compute_qty_received()
        for line in self:
            if line.qty_received_method == 'stock_moves':
                total = 0.0
                # In case of a BOM in kit, the products delivered do not correspond to the products in
                # the PO. Therefore, we can skip them since they will be handled later on.
                for move in line._get_po_line_moves():
                    if move.state == 'done':
                        if move.location_dest_id.usage == "supplier":
                            if move.to_refund:
                                total -= move.product_uom._compute_quantity(move.product_uom_qty, line.product_uom, rounding_method='HALF-UP')
                        elif move.origin_returned_move_id and move.origin_returned_move_id._is_dropshipped() and not move._is_dropshipped_returned():
                            # Edge case: the dropship is returned to the stock, no to the supplier.
                            # In this case, the received quantity on the PO is set although we didn't
                            # receive the product physically in our stock. To avoid counting the
                            # quantity twice, we do nothing.
                            pass
                        elif (
                            move.location_dest_id.usage == "internal"
                            and move.to_refund
                            and move.location_dest_id
                            not in self.env["stock.location"].search(
                                [("id", "child_of", move.warehouse_id.view_location_id.id)]
                            )
                        ):
                            total -= move.product_uom._compute_quantity(move.product_uom_qty, line.product_uom, rounding_method='HALF-UP')
                        else:
                            total += move.product_uom._compute_quantity(move.product_uom_qty, line.product_uom, rounding_method='HALF-UP')
                line._track_qty_received(total)
                line.qty_received = total

    @api.depends('product_uom_qty', 'date_planned')
    def _compute_forecasted_issue(self):
        for line in self:
            warehouse = line.order_id.picking_type_id.warehouse_id
            line.forecasted_issue = False
            if line.product_id:
                virtual_available = line.product_id.with_context(warehouse=warehouse.id, to_date=line.date_planned).virtual_available
                if line.state == 'draft':
                    virtual_available += line.product_uom_qty
                if virtual_available < 0:
                    line.forecasted_issue = True

    @api.model_create_multi
    def create(self, vals_list):
        lines = super(PurchaseOrderLine, self).create(vals_list)
        lines.filtered(lambda l: l.order_id.state == 'purchase')._create_or_update_picking()
        return lines

    def write(self, values):
        if values.get('date_planned'):
            new_date = fields.Datetime.to_datetime(values['date_planned'])
            self.filtered(lambda l: not l.display_type)._update_move_date_deadline(new_date)
        lines = self.filtered(lambda l: l.order_id.state == 'purchase')
        previous_product_qty = {line.id: line.product_uom_qty for line in lines}
        result = super(PurchaseOrderLine, self).write(values)
        if 'product_qty' in values:
            lines.with_context(previous_product_qty=previous_product_qty)._create_or_update_picking()
        return result

    def action_product_forecast_report(self):
        self.ensure_one()
        action = self.product_id.action_product_forecast_report()
        action['context'] = {
            'active_id': self.product_id.id,
            'active_model': 'product.product',
            'move_to_match_ids': self.move_ids.filtered(lambda m: m.product_id == self.product_id).ids,
            'purchase_line_to_match_id': self.id,
        }
        warehouse = self.order_id.picking_type_id.warehouse_id
        if warehouse:
            action['context']['warehouse'] = warehouse.id
        return action

    def unlink(self):
        self.move_ids._action_cancel()

        ppg_cancel_lines = self.filtered(lambda line: line.propagate_cancel)
        ppg_cancel_lines.move_dest_ids._action_cancel()

        not_ppg_cancel_lines = self.filtered(lambda line: not line.propagate_cancel)
        not_ppg_cancel_lines.move_dest_ids.write({'procure_method': 'make_to_stock'})
        not_ppg_cancel_lines.move_dest_ids._recompute_state()

        return super().unlink()

    # --------------------------------------------------
    # Business methods
    # --------------------------------------------------

    def _update_move_date_deadline(self, new_date):
        """ Updates corresponding move picking line deadline dates that are not yet completed. """
        moves_to_update = self.move_ids.filtered(lambda m: m.state not in ('done', 'cancel'))
        if not moves_to_update:
            moves_to_update = self.move_dest_ids.filtered(lambda m: m.state not in ('done', 'cancel'))
        for move in moves_to_update:
            move.date_deadline = new_date + relativedelta(days=move.company_id.po_lead)

    def _create_or_update_picking(self):
        for line in self:
            if line.product_id and line.product_id.type in ('product', 'consu'):
                # Prevent decreasing below received quantity
                if float_compare(line.product_qty, line.qty_received, line.product_uom.rounding) < 0:
                    raise UserError(_('You cannot decrease the ordered quantity below the received quantity.\n'
                                      'Create a return first.'))

                if float_compare(line.product_qty, line.qty_invoiced, line.product_uom.rounding) == -1:
                    # If the quantity is now below the invoiced quantity, create an activity on the vendor bill
                    # inviting the user to create a refund.
                    line.invoice_lines[0].move_id.activity_schedule(
                        'mail.mail_activity_data_warning',
                        note=_('The quantities on your purchase order indicate less than billed. You should ask for a refund.'))

                # If the user increased quantity of existing line or created a new line
                pickings = line.order_id.picking_ids.filtered(lambda x: x.state not in ('done', 'cancel') and x.location_dest_id.usage in ('internal', 'transit', 'customer'))
                picking = pickings and pickings[0] or False
                if not picking:
                    res = line.order_id._prepare_picking()
                    picking = self.env['stock.picking'].create(res)

                moves = line._create_stock_moves(picking)
                moves._action_confirm()._action_assign()

    def _get_stock_move_price_unit(self):
        self.ensure_one()
        order = self.order_id
        price_unit = self.price_unit
        price_unit_prec = self.env['decimal.precision'].precision_get('Product Price')
        if self.taxes_id:
            qty = self.product_qty or 1
            price_unit = self.taxes_id.with_context(round=False).compute_all(
                price_unit, currency=self.order_id.currency_id, quantity=qty, product=self.product_id, partner=self.order_id.partner_id
            )['total_void']
            price_unit = float_round(price_unit / qty, precision_digits=price_unit_prec)
        if self.product_uom.id != self.product_id.uom_id.id:
            price_unit *= self.product_uom.factor / self.product_id.uom_id.factor
        if order.currency_id != order.company_id.currency_id:
            price_unit = order.currency_id._convert(
                price_unit, order.company_id.currency_id, self.company_id, self.date_order or fields.Date.today(), round=False)
        return price_unit

    def _prepare_stock_moves(self, picking):
        """ Prepare the stock moves data for one order line. This function returns a list of
        dictionary ready to be used in stock.move's create()
        """
        self.ensure_one()
        res = []
        if self.product_id.type not in ['product', 'consu']:
            return res

        price_unit = self._get_stock_move_price_unit()
        qty = self._get_qty_procurement()

        move_dests = self.move_dest_ids
        if not move_dests:
            move_dests = self.move_ids.move_dest_ids.filtered(lambda m: m.state != 'cancel' and not m.location_dest_id.usage == 'supplier')

        if not move_dests:
            qty_to_attach = 0
            qty_to_push = self.product_qty - qty
        else:
            move_dests_initial_demand = self.product_id.uom_id._compute_quantity(
                sum(move_dests.filtered(lambda m: m.state != 'cancel' and not m.location_dest_id.usage == 'supplier').mapped('product_qty')),
                self.product_uom, rounding_method='HALF-UP')
            qty_to_attach = move_dests_initial_demand - qty
            qty_to_push = self.product_qty - move_dests_initial_demand

        if float_compare(qty_to_attach, 0.0, precision_rounding=self.product_uom.rounding) > 0:
            product_uom_qty, product_uom = self.product_uom._adjust_uom_quantities(qty_to_attach, self.product_id.uom_id)
            res.append(self._prepare_stock_move_vals(picking, price_unit, product_uom_qty, product_uom))
        if not float_is_zero(qty_to_push, precision_rounding=self.product_uom.rounding):
            product_uom_qty, product_uom = self.product_uom._adjust_uom_quantities(qty_to_push, self.product_id.uom_id)
            extra_move_vals = self._prepare_stock_move_vals(picking, price_unit, product_uom_qty, product_uom)
            extra_move_vals['move_dest_ids'] = False  # don't attach
            res.append(extra_move_vals)
        return res

    def _get_qty_procurement(self):
        self.ensure_one()
        qty = 0.0
        outgoing_moves, incoming_moves = self._get_outgoing_incoming_moves()
        for move in outgoing_moves:
            qty -= move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom, rounding_method='HALF-UP')
        for move in incoming_moves:
            qty += move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom, rounding_method='HALF-UP')
        return qty

    def _check_orderpoint_picking_type(self):
        warehouse_loc = self.order_id.picking_type_id.warehouse_id.view_location_id
        dest_loc = self.move_dest_ids.location_id or self.orderpoint_id.location_id
        if warehouse_loc and dest_loc and not warehouse_loc.parent_path in dest_loc[0].parent_path:
            raise UserError(_('For the product %s, the warehouse of the operation type (%s) is inconsistent with the location (%s) of the reordering rule (%s). Change the operation type or cancel the request for quotation.',
                              self.product_id.display_name, self.order_id.picking_type_id.display_name, self.orderpoint_id.location_id.display_name, self.orderpoint_id.display_name))

    def _prepare_stock_move_vals(self, picking, price_unit, product_uom_qty, product_uom):
        self.ensure_one()
        self._check_orderpoint_picking_type()
        product = self.product_id.with_context(lang=self.order_id.dest_address_id.lang or self.env.user.lang)
        date_planned = self.date_planned or self.order_id.date_planned
        return {
            # truncate to 2000 to avoid triggering index limit error
            # TODO: remove index in master?
            'name': (self.name or '')[:2000],
            'product_id': self.product_id.id,
            'date': date_planned,
            'date_deadline': date_planned + relativedelta(days=self.order_id.company_id.po_lead),
            'location_id': self.order_id.partner_id.property_stock_supplier.id,
            'location_dest_id': (self.orderpoint_id and not (self.move_ids | self.move_dest_ids)) and self.orderpoint_id.location_id.id or self.order_id._get_destination_location(),
            'picking_id': picking.id,
            'partner_id': self.order_id.dest_address_id.id,
            'move_dest_ids': [(4, x) for x in self.move_dest_ids.ids],
            'state': 'draft',
            'purchase_line_id': self.id,
            'company_id': self.order_id.company_id.id,
            'price_unit': price_unit,
            'picking_type_id': self.order_id.picking_type_id.id,
            'group_id': self.order_id.group_id.id,
            'origin': self.order_id.name,
            'description_picking': product.description_pickingin or self.name,
            'propagate_cancel': self.propagate_cancel,
            'warehouse_id': self.order_id.picking_type_id.warehouse_id.id,
            'product_uom_qty': product_uom_qty,
            'product_uom': product_uom.id,
            'product_packaging_id': self.product_packaging_id.id,
        }

    @api.model
    def _prepare_purchase_order_line_from_procurement(self, product_id, product_qty, product_uom, company_id, values, po):
        line_description = ''
        if values.get('product_description_variants'):
            line_description = values['product_description_variants']
        supplier = values.get('supplier')
        res = self._prepare_purchase_order_line(product_id, product_qty, product_uom, company_id, supplier, po)
        # We need to keep the vendor name set in _prepare_purchase_order_line. To avoid redundancy
        # in the line name, we add the line_description only if different from the product name.
        # This way, we shoud not lose any valuable information.
        if line_description and product_id.name != line_description:
            res['name'] += '\n' + line_description
        res['move_dest_ids'] = [(4, x.id) for x in values.get('move_dest_ids', [])]
        res['orderpoint_id'] = values.get('orderpoint_id', False) and values.get('orderpoint_id').id
        res['propagate_cancel'] = values.get('propagate_cancel')
        res['product_description_variants'] = values.get('product_description_variants')
        return res

    def _create_stock_moves(self, picking):
        values = []
        for line in self.filtered(lambda l: not l.display_type):
            for val in line._prepare_stock_moves(picking):
                values.append(val)
            line.move_dest_ids.created_purchase_line_id = False

        return self.env['stock.move'].create(values)

    def _find_candidate(self, product_id, product_qty, product_uom, location_id, name, origin, company_id, values):
        """ Return the record in self where the procument with values passed as
        args can be merged. If it returns an empty record then a new line will
        be created.
        """
        description_picking = ''
        if values.get('product_description_variants'):
            description_picking = values['product_description_variants']
        lines = self.filtered(
            lambda l: l.propagate_cancel == values['propagate_cancel']
            and ((values['orderpoint_id'] and not values['move_dest_ids']) and l.orderpoint_id == values['orderpoint_id'] or True)
        )

        # In case 'product_description_variants' is in the values, we also filter on the PO line
        # name. This way, we can merge lines with the same description. To do so, we need the
        # product name in the context of the PO partner.
        if lines and values.get('product_description_variants'):
            partner = self.mapped('order_id.partner_id')[:1]
            product_lang = product_id.with_context(
                lang=partner.lang,
                partner_id=partner.id,
            )
            name = product_lang.display_name
            if product_lang.description_purchase:
                name += '\n' + product_lang.description_purchase
            lines = lines.filtered(lambda l: l.name == name + '\n' + description_picking)
            if lines:
                return lines[0]

        return lines and lines[0] or self.env['purchase.order.line']

    def _get_outgoing_incoming_moves(self):
        outgoing_moves = self.env['stock.move']
        incoming_moves = self.env['stock.move']

        for move in self.move_ids.filtered(lambda r: r.state != 'cancel' and not r.scrapped and self.product_id == r.product_id):
            if move.location_dest_id.usage == "supplier" and move.to_refund:
                outgoing_moves |= move
            elif move.location_dest_id.usage != "supplier":
                if not move.origin_returned_move_id or (move.origin_returned_move_id and move.to_refund):
                    incoming_moves |= move

        return outgoing_moves, incoming_moves

    def _update_date_planned(self, updated_date):
        move_to_update = self.move_ids.filtered(lambda m: m.state not in ['done', 'cancel'])
        if not self.move_ids or move_to_update:  # Only change the date if there is no move done or none
            super()._update_date_planned(updated_date)
        if move_to_update:
            self._update_move_date_deadline(updated_date)

    @api.model
    def _update_qty_received_method(self):
        """Update qty_received_method for old PO before install this module."""
        self.search([])._compute_qty_received_method()
Esempio n. 20
0
class HelpdeskStageCustom(models.Model):
    _name = "helpdesk.stage.custom"
    _order = 'priority desc'

    priority = fields.Integer(string='Priority', default=1, required=True)
    send_mail = fields.Boolean('Send mail')
    template_id = fields.Many2one('mail.template', string='Template')
    helpdesk_stage_id = fields.Many2one('helpdesk.stage.config',
                                        string='Stage')
    action_when = fields.Selection([('before', 'Before'), ('after', 'After')],
                                   string='Before/After',
                                   required=True,
                                   default='before')
    mail_action = fields.Selection([('remainder', 'FollowUp Mail'),
                                    ('warning', 'Reminder Mail')],
                                   string='Mail Action')
    sla_level_id = fields.Many2one('sla.level.configuration',
                                   string='SLA Levels')
    action_time = fields.Selection([
        ('1_m', '1 Minute'),
        ('5_m', '5 Minutes'),
        ('15_m', '15 Minutes'),
        ('30_m', '30 Minutes'),
        ('1_h', '1 Hour'),
        ('2_h', '2 Hours'),
        ('4_h', '4 Hours'),
        ('8_h', '8 Hours'),
        ('1_d', '1 Day'),
        ('2_d', '2 Days'),
        ('3_d', '3 Days'),
        ('4_d', '4 Days'),
        ('5_d', '5 Days'),
        ('7_d', '7 Days'),
        ('10_d', '10 Days'),
        ('20_d', '20 Days'),
        ('30_d', '30 Days'),
        ('180_d', '180 Days'),
        ('365_d', '365 Days'),
    ],
                                   string='Time',
                                   required=True)
    action_color = fields.Selection([('orange', 'Orange'), ('red', 'Red'),
                                     ('green', 'Green'), ('purple', 'Purple'),
                                     ('yellow', 'Yellow'), ('blue', 'Blue')],
                                    string='Colors',
                                    required=True)
    action_perform = fields.Selection(
        [('request_date', 'Creation Date'),
         ('last_stage_update', 'Last Stage Update'),
         ('assign_date', 'Assign Date'), ('dead_line_date', 'Deadline Date'),
         ('close_date', 'Closed'), ('message_last_post', 'Last Message Date')],
        string='Field',
        required=True)

    @api.one
    @api.constrains('action_time', 'action_perform')
    def _check_action_time(self):
        if self.action_perform in _MAP_FIELDS_DATE and self.action_time in [
                '1_m', '5_m', '15_m', '30_m', '1_h', '2_h', '4_h', '8_h'
        ]:
            raise Warning(_('Misconfiguration, check Field and Time.'))
Esempio n. 21
0
class PaymentAcquirer(models.Model):
    _inherit = 'payment.acquirer'

    provider = fields.Selection(selection_add=[('payulatam', 'PayU Latam')],
                                ondelete={'payulatam': 'set default'})
    payulatam_merchant_id = fields.Char(
        string="PayU Latam Merchant ID",
        help="The ID solely used to identify the account with PayULatam",
        required_if_provider='payulatam')
    payulatam_account_id = fields.Char(
        string="PayU Latam Account ID",
        help=
        "The ID solely used to identify the country-dependent shop with PayULatam",
        required_if_provider='payulatam')
    payulatam_api_key = fields.Char(string="PayU Latam API Key",
                                    required_if_provider='payulatam',
                                    groups='base.group_system')

    @api.model
    def _get_compatible_acquirers(self, *args, currency_id=None, **kwargs):
        """ Override of payment to unlist PayU Latam acquirers for unsupported currencies. """
        acquirers = super()._get_compatible_acquirers(*args,
                                                      currency_id=currency_id,
                                                      **kwargs)

        currency = self.env['res.currency'].browse(currency_id).exists()
        if currency and currency.name not in SUPPORTED_CURRENCIES:
            acquirers = acquirers.filtered(lambda a: a.provider != 'payulatam')

        return acquirers

    def _payulatam_generate_sign(self, values, incoming=True):
        """ Generate the signature for incoming or outgoing communications.

        :param dict values: The values used to generate the signature
        :param bool incoming: Whether the signature must be generated for an incoming (PayU Latam to
                              Odoo) or outgoing (Odoo to PayU Latam) communication.
        :return: The signature
        :rtype: str
        """
        if incoming:
            data_string = '~'.join([
                self.payulatam_api_key,
                self.payulatam_merchant_id,
                values['referenceCode'],
                # Observation from the sandbox: PayU Latam is using a rounded value to generate
                # their signature despite saying otherwise in the doc:
                # http://developers.payulatam.com/en/web_checkout/integration.html
                float_repr(float(values.get('TX_VALUE')), 1),
                values['currency'],
                values.get('transactionState'),
            ])
        else:
            data_string = '~'.join([
                self.payulatam_api_key,
                self.payulatam_merchant_id,
                values['referenceCode'],
                float_repr(float(values['amount']), 1),
                values['currency'],
            ])
        return md5(data_string.encode('utf-8')).hexdigest()
Esempio n. 22
0
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
Esempio n. 23
0
class MrpRoutingWorkcenter(models.Model):
    _name = 'mrp.routing.workcenter'
    _description = 'Work Center Usage'
    _order = 'bom_id, sequence, id'
    _check_company_auto = True

    name = fields.Char('Operation', required=True)
    active = fields.Boolean(default=True)
    workcenter_id = fields.Many2one('mrp.workcenter', 'Work Center', required=True, check_company=True)
    sequence = fields.Integer(
        'Sequence', default=100,
        help="Gives the sequence order when displaying a list of routing Work Centers.")
    bom_id = fields.Many2one(
        'mrp.bom', 'Bill of Material',
        index=True, ondelete='cascade', required=True,
        help="The Bill of Material this operation is linked to")
    company_id = fields.Many2one('res.company', 'Company', related='bom_id.company_id')
    worksheet_type = fields.Selection([
        ('pdf', 'PDF'), ('google_slide', 'Google Slide'), ('text', 'Text')],
        string="Work Sheet", default="text",
        help="Defines if you want to use a PDF or a Google Slide as work sheet."
    )
    note = fields.Text('Description', help="Text worksheet description")
    worksheet = fields.Binary('PDF')
    worksheet_google_slide = fields.Char('Google Slide', help="Paste the url of your Google Slide. Make sure the access to the document is public.")
    time_mode = fields.Selection([
        ('auto', 'Compute based on tracked time'),
        ('manual', 'Set duration manually')], string='Duration Computation',
        default='manual')
    time_mode_batch = fields.Integer('Based on', default=10)
    time_computed_on = fields.Char('Computed on last', compute='_compute_time_computed_on')
    time_cycle_manual = fields.Float(
        'Manual Duration', default=60,
        help="Time in minutes:"
        "- In manual mode, time used"
        "- In automatic mode, supposed first time when there aren't any work orders yet")
    time_cycle = fields.Float('Duration', compute="_compute_time_cycle")
    workorder_count = fields.Integer("# Work Orders", compute="_compute_workorder_count")
    workorder_ids = fields.One2many('mrp.workorder', 'operation_id', string="Work Orders")

    @api.depends('time_mode', 'time_mode_batch')
    def _compute_time_computed_on(self):
        for operation in self:
            operation.time_computed_on = _('%i work orders') % operation.time_mode_batch if operation.time_mode != 'manual' else False

    @api.depends('time_cycle_manual', 'time_mode', 'workorder_ids')
    def _compute_time_cycle(self):
        manual_ops = self.filtered(lambda operation: operation.time_mode == 'manual')
        for operation in manual_ops:
            operation.time_cycle = operation.time_cycle_manual
        for operation in self - manual_ops:
            data = self.env['mrp.workorder'].read_group([
                ('operation_id', '=', operation.id),
                ('qty_produced', '>', 0),
                ('state', '=', 'done')], ['operation_id', 'duration', 'qty_produced'], ['operation_id'],
                limit=operation.time_mode_batch)
            count_data = dict((item['operation_id'][0], (item['duration'], item['qty_produced'])) for item in data)
            if count_data.get(operation.id) and count_data[operation.id][1]:
                operation.time_cycle = (count_data[operation.id][0] / count_data[operation.id][1]) * (operation.workcenter_id.capacity or 1.0)
            else:
                operation.time_cycle = operation.time_cycle_manual

    def _compute_workorder_count(self):
        data = self.env['mrp.workorder'].read_group([
            ('operation_id', 'in', self.ids),
            ('state', '=', 'done')], ['operation_id'], ['operation_id'])
        count_data = dict((item['operation_id'][0], item['operation_id_count']) for item in data)
        for operation in self:
            operation.workorder_count = count_data.get(operation.id, 0)
Esempio n. 24
0
class AccountInvoice(models.Model):
    _inherit = 'account.invoice'

    charge_code_id = fields.Many2one('purchase.charge.code', string='Charge Code', compute='_compute_project_code', store=True)

    @api.depends('origin')
    def _compute_project_code(self):
        for record in self:
            source = record.env['purchase.order'].search([('name', '=', record.origin)], limit=1)
            record.charge_code_id = source.charge_code_id

    state = fields.Selection([
        ('draft','Draft'),
        ('to_approve','Ready for Approval'),
        ('open', 'Approved'),
        ('in_payment', 'In Payment'),
        ('paid', 'Exported to QB'),
        ('cancel', 'Cancelled'),
    ], string='Status', index=True, readonly=True, default='draft',
    track_visibility='onchange', copy=False,
    help=" * The 'Draft' status is used when a user is encoding a new and unconfirmed Invoice.\n"
         " * The 'Ready for Approval' status is used when user creates invoice, an invoice number is generated but the Accounting Manager still needs to approve the invoice.\n"
         " * The 'Approved' status is used when the invoice needs to paid by the customer.\n"
         " * The 'In Payment' status is used when payments have been registered for the entirety of the invoice in a journal configured to post entries at bank reconciliation only, and some of them haven't been reconciled with a bank statement line yet.\n"
         " * The 'Exported to QB' status is set automatically when the invoice is paid. Its related journal entries may or may not be reconciled.\n"
         " * The 'Cancelled' status is used when user cancel invoice.")

    @api.model
    def _unlink_confirm_invoice_action(self):
        self.env.ref('account.action_account_invoice_confirm').unlink()

    @api.multi
    def generate_export_data(self, export_sequence):
        header = ['Export #', 'Bill #', 'PO #', 'Charge Code', 'Project Code', 'Vendor Name', 'Total']

        # Get the bill lines that have never been exported before. See comment below
        new_export = self.env['account.invoice.line'].search([
            ('invoice_id', 'in', self.ids),
            ('export_sequence', 'in', ('', False))
        ])
        new_export.write({'export_sequence': export_sequence})
        # not sure if customer wants to export already exported lines or not
        # so for now, only export new lines

        content = []
        for bill in self:  # since they want to group by bill
            bill_group = {}
            for line in bill.invoice_line_ids.filtered(lambda l: l.export_sequence == export_sequence):
                key = (line.export_sequence, line.invoice_id.number, line.purchase_id.name, line.invoice_id.charge_code_id.name, line.project_code.name, line.invoice_id.partner_id.name)
                if not bill_group.get(key):
                    bill_group[key] = 0.0
                bill_group[key] += line.price_total  # assuming they are using the same currency here, might need to revise if they want multicurrency
            content.extend([list(k) + [bill_group.get(k)] for k in bill_group.keys()])

        data = [header] + content
        return _csv_write_rows(data)

    @api.multi
    def action_export(self):
        print('Exporting')

        self = self.env['account.invoice'].search([
            ('id', 'in', self.ids),
            ('state', 'not in', ('draft', 'cancel')),
            ('type', '=', 'in_invoice'),
        ])

        if not self:
            return {}

        export_sequence = self.env['ir.sequence'].next_by_code('export.vendor.bill')
        if not export_sequence:
            raise ValidationError(_('Please define sequence for export vendor bill'))

        Attachment = self.env['ir.attachment'].sudo()
        attachment_name = 'Vendor_Bill_Batch_{}.csv'.format(export_sequence)
        data = self.generate_export_data(export_sequence)

        attachment_vals = {
            'name': attachment_name,
            'datas': base64.encodestring(data),
            'datas_fname': attachment_name,
            'res_model': 'account.invoice',
        }

        Attachment.search([('name', '=', attachment_name)]).unlink()

        attachment = Attachment.create(attachment_vals)

        return {
            'type': 'ir.actions.act_url',
            'url': '/web/content/{}?download=true'.format(attachment.id),
            'target': 'self'
        }

    @api.multi
    def action_invoice_to_approve(self):
        # Method similar to action_invoice_open but before approval stage
        to_approve_invoices = self.filtered(lambda inv: inv.state != 'open')
        if to_approve_invoices.filtered(lambda inv: not inv.partner_id):
            raise UserError(_("The field Vendor is required, please complete it to request approval of the Vendor Bill."))
        if to_approve_invoices.filtered(lambda inv: inv.state != 'draft'):
            raise UserError(_("Invoice must be in draft state in order to request approval of the Accounting Manager."))
        return self.write({'state': 'to_approve'})

    @api.multi
    def action_invoice_open(self):
        # lots of duplicate calls to action_invoice_open, so we remove those already open
        to_open_invoices = self.filtered(lambda inv: inv.state != 'open')
        if to_open_invoices.filtered(lambda inv: not inv.partner_id):
            raise UserError(_("The field Vendor is required, please complete it to validate the Vendor Bill."))
        if to_open_invoices.filtered(lambda inv: inv.state != 'to_approve'):
            raise UserError(_("Invoice must be in Ready to Approve state in order to validate it."))
        if to_open_invoices.filtered(lambda inv: float_compare(inv.amount_total, 0.0, precision_rounding=inv.currency_id.rounding) == -1):
            raise UserError(_("You cannot validate an invoice with a negative total amount. You should create a credit note instead."))
        if to_open_invoices.filtered(lambda inv: not inv.account_id):
            raise UserError(_('No account was found to create the invoice, be sure you have installed a chart of account.'))
        to_open_invoices.action_date_assign()
        to_open_invoices.action_move_create()
        return to_open_invoices.invoice_validate()
Esempio n. 25
0
class marfrig_service_base(models.Model):
    _name = "deposito.service.base"
    _inherit = ['mail.thread', 'mail.activity.mixin', 'portal.mixin']
    _description = "Deposito"
    _order = "id DESC"

    @api.depends('invoices_ids', 'invoice_count')
    def _compute_invoice(self):
        return

    name = fields.Char(default='Borrador')
    referencia = fields.Char(defualt='Referencia de Carpeta')
    stock_operation = fields.Many2one(comodel_name='stock.picking',
                                      string='Movimiento Deposito')
    user_id = fields.Many2one('res.users',
                              string='Usuario',
                              default=lambda self: self.env.user,
                              track_visibility="onchange")
    company_id = fields.Many2one('res.company',
                                 string='Compañia',
                                 default=lambda self: self.env.user.company_id)
    partner_invoice_id = fields.Many2one(comodel_name='res.partner',
                                         string='Cliente',
                                         domain=[('customer', '=', True)])
    pricelist_id = fields.Many2one('product.pricelist', string='Tarifa')
    currency_id = fields.Many2one(comodel_name="res.currency",
                                  string="Moneda",
                                  related="pricelist_id.currency_id",
                                  index=True,
                                  readonly=True,
                                  store=True)
    start_datetime = fields.Datetime(string='Fecha Inicio',
                                     required=True,
                                     index=True,
                                     copy=False,
                                     default=fields.datetime.now())
    stop_datetime = fields.Datetime('Fecha Fin', index=True, copy=False)
    state = fields.Selection([
        ('draft', 'Borrador'),
        ('confirm', 'Confirmado'),
        ('inprocess', 'En proceso'),
        ('invoiced', 'Facturado'),
        ('invoice_rejected', 'Fac. Rechazada'),
        ('cancel', 'Cancelado'),
        ('done', 'Realizado'),
    ],
                             string='Status',
                             index=True,
                             readonly=True,
                             default='draft',
                             track_visibility='onchange',
                             copy=False,
                             store=True)
    dua_aduana = fields.Char(string='Mes', size=3)
    dua_anio = fields.Char(string='Año', size=4)
    dua_numero = fields.Char(string='Dua_Numero', size=6)
    deposito_srv_ids = fields.One2many('deposito.service.products',
                                       'deposito_srv_id',
                                       string='Servicios',
                                       copy=True)
    aduana_destino_id = fields.Many2one('fronteras', 'Aduana Destino')
    origin_id = fields.Many2one(comodel_name='res.partner.address.ext',
                                string='Origen')
    destiny_id = fields.Many2one(comodel_name='res.partner.address.ext',
                                 string='Destino')
    invoices_ids = fields.One2many('account.invoice',
                                   'deposito_operation_id',
                                   string='Facturas de Clientes',
                                   domain=[('type', '=', 'out_invoice')])
    invoice_count = fields.Integer(compute="_compute_invoice",
                                   string='Conteo de Facturas',
                                   copy=False,
                                   default=0,
                                   store=True)
    suppliers_invoices_ids = fields.Many2many('account.invoice',
                                              string='Facturas de Proveedor',
                                              copy=False)
    invoice_id = fields.Many2one('account.invoice')

    @api.multi
    def draft_confirm(self):
        return self.write({
            'state':
            'confirm',
            'name':
            self.env['ir.sequence'].next_by_code('service.deposito') or '/'
        })

    @api.multi
    def confirm_inprocess(self):
        return self.write({'state': 'inprocess'})

    @api.multi
    def confirm_cancel(self):
        return self.write({'state': 'cancel'})

    @api.multi
    def cancel_draft(self):
        return self.write({'state': 'draft'})

    @api.multi
    def action_view_invoice(self):
        '''
        This function returns an action that display existing vendor bills of given purchase order ids.
        When only one found, show the vendor bill immediately.
        '''
        action = self.env.ref('account.action_invoice_tree1')
        result = action.read()[0]
        create_bill = self.env.context.get('create_bill', False)
        # override the context to get rid of the default filtering
        result['context'] = {
            'type': 'out_invoice',
            'default_purchase_id': self.id,
            'default_currency_id': self.currency_id.id,
            'default_company_id': self.company_id.id,
            'company_id': self.company_id.id
        }
        if not self.invoices_ids:
            raise Warning('No tiene Facturas creadas aún')
        # choose the view_mode accordingly
        if len(self.invoices_ids) > 1 and not create_bill:
            result['domain'] = "[('id', 'in', " + str(
                self.invoices_ids.ids) + ")]"
        else:
            res = self.env.ref('account.invoice_form', False)
            result['views'] = [(res and res.id or False, 'form')]
            # Do not set an invoice_id if we want to create a new bill.
            if not create_bill:
                result['res_id'] = self.invoices_ids.id or False
        result['context']['default_origin'] = self.name
        # result['context']['default_reference'] = self.partner_ref
        return result

    def show_service_lines(self):
        context = self._context.copy()
        srv_ids = self.ids
        act_window = self.env['ir.actions.act_window']
        wizard = self
        # open the list view of service product to invoice
        res = act_window.for_xml_id('deposito',
                                    'action_consolidado_servicio_tree')
        # context
        res['context'] = {
            'search_default_uninvoiced': 1,
        }
        products_obj = self.env['producto.servicio.camion']
        ids_guardadas = []
        if self.productos_servicios_camion_ids:
            for prod in self.productos_servicios_camion_ids:
                if prod.is_invoiced and not prod.invoiced:
                    ids_guardadas.append(prod.id)

        if self.cargas_ids:
            for prod in self.cargas_ids:
                for cg in prod.producto_servicio_carga_ids:
                    if cg.is_invoiced and not cg.invoiced:
                        ids_guardadas.append(cg.id)
        # domain
        if srv_ids:
            if not res.get('domain', False) or not isinstance(
                    res.get('domain', False), (list, )):
                res['domain'] = []
            # res['domain'].append(('camion_id', 'in', srv_ids))
            res['domain'].append(('id', 'in', ids_guardadas))
            res['domain'].append(('invoiced', '=', False))
            res['domain'].append(('is_invoiced', '=', True))
        return res
Esempio n. 26
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']
Esempio n. 27
0
class SaleOrder(models.Model):
    _inherit = 'sale.order'

    @api.depends('order_line.price_total', 'discount', 'chargeable_amount')
    def _amount_all(self):
        """
        Compute the total amounts of the SO.
        """
        for order in self:
            amount_untaxed = amount_tax = 0.0
            for line in order.order_line:
                amount_untaxed += line.price_subtotal
                # FORWARDPORT UP TO 10.0
                if order.company_id.tax_calculation_rounding_method == 'round_globally':
                    price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
                    taxes = line.tax_id.compute_all(price, line.order_id.currency_id, line.product_uom_qty, product=line.product_id, partner=order.partner_shipping_id)
                    amount_tax += sum(t.get('amount', 0.0) for t in taxes.get('taxes', []))
                else:
                    amount_tax += line.price_tax
            amount_total = amount_untaxed + amount_tax
            if order.chargeable_amount > 0.0:
                discount = amount_total - order.chargeable_amount
            else:
                discount = order.discount
            amount_total = amount_total - discount
            round_off_amount = self.env['rounding.off'].round_off_value_to_nearest(amount_total)
            order.update({
                'amount_untaxed': order.pricelist_id.currency_id.round(amount_untaxed),
                'amount_tax': order.pricelist_id.currency_id.round(amount_tax),
                'amount_total': amount_total + round_off_amount,
                'round_off_amount': round_off_amount,
                'total_outstanding_balance': order.prev_outstanding_balance + amount_total + round_off_amount
            })

    @api.depends('partner_id')
    def _calculate_balance(self):
        for order in self:
            order.prev_outstanding_balance = 0.0
            order.total_outstanding_balance = 0.0
            total_receivable = order._total_receivable()
            order.prev_outstanding_balance = total_receivable
    
    def _total_receivable(self):
        receivable = 0.0
        if self.partner_id:
            self._cr.execute("""SELECT l.partner_id, at.type, SUM(l.debit-l.credit)
                          FROM account_move_line l
                          LEFT JOIN account_account a ON (l.account_id=a.id)
                          LEFT JOIN account_account_type at ON (a.user_type_id=at.id)
                          WHERE at.type IN ('receivable','payable')
                          AND l.partner_id = %s
                          AND l.full_reconcile_id IS NULL
                          GROUP BY l.partner_id, at.type
                          """, (self.partner_id.id,))
            for pid, type, val in self._cr.fetchall():
                if val is None:
                    val=0
                receivable = (type == 'receivable') and val or -val
        return receivable

    @api.depends('partner_id')
    def _get_partner_details(self):
        for order in self:
            partner = order.partner_id
            order.update({
                'partner_uuid': partner.uuid,
                #'partner_village': partner.village,
            })


    external_id = fields.Char(string="External Id",
                              help="This field is used to store encounter ID of bahmni api call")
    dispensed = fields.Boolean(string="Dispensed",
                               help="Flag to identify whether drug order is dispensed or not.")
    partner_village = fields.Many2one("village.village", string="Partner Village")
    care_setting = fields.Selection([('ipd', 'IPD'),
                                     ('opd', 'OPD')], string="Care Setting")
    provider_name = fields.Char(string="Provider Name")
    discount_percentage = fields.Float(string="Discount Percentage")
    default_quantity = fields.Integer(string="Default Quantity")
    # above field is used to allow setting quantity as -1 in sale order line, when it is created through bahmni
    discount_type = fields.Selection([('none', 'No Discount'),
                                      ('fixed', 'Fixed'),
                                      ('percentage', 'Percentage')], string="Discount Type",
                                     default='none')
    discount = fields.Monetary(string="Discount")
    disc_acc_id = fields.Many2one('account.account', string="Discount Account Head")
    round_off_amount = fields.Float(string="Round Off Amount", compute=_amount_all)
    prev_outstanding_balance = fields.Monetary(string="Previous Outstanding Balance",
                                               compute=_calculate_balance)
    total_outstanding_balance = fields.Monetary(string="Total Outstanding Balance",
                                                compute=_amount_all)
    chargeable_amount = fields.Float(string="Chargeable Amount")
    amount_round_off = fields.Float(string="Round Off Amount")
    # location to identify from which location order is placed.
    location_id = fields.Many2one('stock.location', string="Location")
    partner_uuid = fields.Char(string='Customer UUID', store=True, readonly=True, compute='_get_partner_details')
    shop_id = fields.Many2one('sale.shop', 'Shop', required=True)


    @api.onchange('order_line')
    def onchange_order_line(self):
        '''Calculate discount amount, when discount is entered in terms of %'''
        amount_total = self.amount_untaxed + self.amount_tax
        if self.discount_type == 'fixed':
            self.discount_percentage = self.discount/amount_total * 100
        elif self.discount_type == 'percentage':
            self.discount = amount_total * self.discount_percentage / 100

    @api.onchange('discount', 'discount_percentage', 'discount_type', 'chargeable_amount')
    def onchange_discount(self):
        amount_total = self.amount_untaxed + self.amount_tax
        if self.chargeable_amount:
            if self.discount_type == 'none' and self.chargeable_amount:
                self.discount_type = 'fixed'
                discount = amount_total - self.chargeable_amount
                self.discount_percentage = (discount / amount_total) * 100
        else:
            if self.discount:
                self.discount_percentage = (self.discount / amount_total) * 100
            if self.discount_percentage:
                self.discount = amount_total * self.discount_percentage / 100

    @api.model
    def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
        '''1. make percentage and discount field readonly, when chargeable amount is allowed to enter'''
        result = super(SaleOrder, self).fields_view_get(view_id, view_type, toolbar=toolbar, submenu=submenu)
        if view_type == 'form':
            group_id = self.env.ref("bahmni_sale.group_allow_change_so_charge").id
            doc = etree.XML(result['arch'])
            if group_id in self.env.user.groups_id.ids:
                for node in doc.xpath("//field[@name='discount_percentage']"):
                    node.set('readonly', '1')
                    setup_modifiers(node, result['fields']['discount_percentage'])
                for node in doc.xpath("//field[@name='discount']"):
                    node.set('readonly', '1')
                    setup_modifiers(node, result['fields']['discount'])
                for node in doc.xpath("//field[@name='discount_type']"):
                    node.set('readonly', '1')
                    setup_modifiers(node, result['fields']['discount_type'])
            result['arch'] = etree.tostring(doc)
        return result

    @api.multi
    def _prepare_invoice(self):
        """
        Prepare the dict of values to create the new invoice for a sales order. This method may be
        overridden to implement custom invoice generation (making sure to call super() to establish
        a clean extension chain).
        """
        self.ensure_one()
        journal_id = self.env['account.invoice'].default_get(['journal_id'])['journal_id']
        if not journal_id:
            raise UserError(_('Please define an accounting sale journal for this company.'))
        invoice_vals = {
            'name': self.client_order_ref or '',
            'origin': self.name,
            'type': 'out_invoice',
            'account_id': self.partner_invoice_id.property_account_receivable_id.id,
            'partner_id': self.partner_invoice_id.id,
            'partner_shipping_id': self.partner_shipping_id.id,
            'journal_id': journal_id,
            'currency_id': self.pricelist_id.currency_id.id,
            'comment': self.note,
            'payment_term_id': self.payment_term_id.id,
            'fiscal_position_id': self.fiscal_position_id.id or self.partner_invoice_id.property_account_position_id.id,
            'company_id': self.company_id.id,
            'user_id': self.user_id and self.user_id.id,
            'team_id': self.team_id.id,
            'discount_type': self.discount_type,
            'discount_percentage': self.discount_percentage,
            'disc_acc_id': self.disc_acc_id.id,
            'discount': self.discount,
        }
        return invoice_vals

    @api.model
    def create(self, vals):
        '''Inherited this method to directly convert quotation to sale order, when it is dispensed at location'''
        res = super(SaleOrder, self).create(vals)
        auto_convert_set = self.env['ir.values'].search([('model', '=', 'sale.config.settings'),
                                                         ('name', '=', 'convert_dispensed')]).value
        if auto_convert_set and vals.get('dispensed'):
            # confirm quotation
            res.action_confirm()
            # process the delivery order related to this order
            pickings = self.env['stock.picking'].search([('group_id', '=', res.procurement_group_id.id)]) if res.procurement_group_id else []
            for pick in pickings:
                for ln in pick.pack_operation_product_ids:
                    required_qty = ln.product_qty
                    if ln.product_id.tracking != 'none':
                        # unlinked already populated lot_ids, as in bahmni according to expiry_date assignment is imp.
                        for l in ln.pack_lot_ids:
                            l.unlink()
                        pack_lot_ids = []
                        alloted_lot_ids = []
                        while required_qty != 0:
                            lot_id = self.env['stock.production.lot'].search([('product_id', '=', ln.product_id.id),
                                                                              ('life_date', '>', datetime.combine(date.today(), datetime.min.time()).strftime(DSDF)),
                                                                              ('id', 'not in', alloted_lot_ids)],
                                                                             order='life_date', limit=1)
                            if not lot_id:
                                lot_id = self.env['stock.production.lot'].search([('product_id', '=', ln.product_id.id),
                                                                         ('id', 'not in', alloted_lot_ids)],
                                                                        limit=1, order='id')
                            if lot_id:
                                quant_id = self.env['stock.quant'].search([('lot_id', '=', lot_id.id),
                                                                            ('location_id', '=', ln.location_id.id),
                                                                            ('product_id', '=', ln.product_id.id)])
                                if len(quant_id) == 1:
                                    available_qty = quant_id.qty
                                else:
                                    available_qty = sum([x.qty for x in quant_id])
                                if available_qty <= required_qty:
                                    pack_lot_ids.append((0, 0, {'lot_id': lot_id.id,
                                                                'qty': available_qty,
                                                                'qty_todo': available_qty}))
                                    required_qty = required_qty - available_qty
                                    alloted_lot_ids.append(lot_id.id)
                                elif available_qty > required_qty:
                                    pack_lot_ids.append((0, 0, {'lot_id': lot_id.id,
                                                                'qty': required_qty,
                                                                'qty_todo': required_qty}))
                                    required_qty = 0
                                    alloted_lot_ids.append(lot_id.id)
                        ln.pack_lot_ids = pack_lot_ids
                        ln.qty_done = ln.product_qty
                    elif ln.product_id.tracking == 'none':
                        ln.qty_done = ln.product_qty
                pick.do_new_transfer()
            # create and process the invoice
            ctx = {'active_ids': [res.id]}
            default_vals = self.env['sale.advance.payment.inv'
                                    ].with_context(ctx).default_get(['count', 'deposit_taxes_id',
                                                                     'advance_payment_method', 'product_id',
                                                                     'deposit_account_id'])
            payment_inv_wiz = self.env['sale.advance.payment.inv'].with_context(ctx).create(default_vals)
            payment_inv_wiz.with_context(ctx).create_invoices()
            for inv in res.invoice_ids:
                inv.action_invoice_open()
                account_payment_env = self.env['account.payment']
                fields = account_payment_env.fields_get().keys()
                default_fields = account_payment_env.with_context({'default_invoice_ids': [(4, inv.id, None)]}).default_get(fields)
                journal_id = self.env['account.journal'].search([('type', '=', 'cash')],
                                                                limit=1)
                default_fields.update({'journal_id': journal_id.id})
                payment_method_ids = self.env['account.payment.method'
                                              ].search([('payment_type', '=', default_fields.get('payment_type'))]).ids
                if default_fields.get('payment_type') == 'inbound':
                    journal_payment_methods = journal_id.inbound_payment_method_ids.ids
                elif default_fields.get('payment_type') == 'outbond':
                    journal_payment_methods = journal_id.outbound_payment_method_ids.ids
                common_payment_method = list(set(payment_method_ids).intersection(set(journal_payment_methods)))
                common_payment_method.sort()
                default_fields.update({'payment_method_id': common_payment_method[0]})
                account_payment = account_payment_env.create(default_fields)
                account_payment.post()
        return res
    #By Pass the Invoice wizard while we press the "Create Invoice" button in sale order afer confirmation.
    #So Once we Confirm the sale order it will create the invoice and ask for the register payment.
    @api.multi
    def action_confirm(self):
        res = super(SaleOrder,self).action_confirm()
        #here we need to set condition for if the its enabled then can continuw owise return True in else condition
        if self.env.user.has_group('bahmni_sale.group_skip_invoice_options'):
            for order in self:
                inv_data = order._prepare_invoice()
                created_invoice = self.env['account.invoice'].create(inv_data)

                for line in order.order_line:
                    line.invoice_line_create(created_invoice.id, line.product_uom_qty)

                # Use additional field helper function (for account extensions)
                for line in created_invoice.invoice_line_ids:
                    line._set_additional_fields(created_invoice)

                # Necessary to force computation of taxes. In account_invoice, they are triggered
                # by onchanges, which are not triggered when doing a create.
                created_invoice.compute_taxes()
                created_invoice.message_post_with_view('mail.message_origin_link',
                    values={'self': created_invoice, 'origin': order},
                    subtype_id=self.env.ref('mail.mt_note').id)
                created_invoice.action_invoice_open()#Validate Invoice
                ctx = dict(
                default_invoice_ids = [(4, created_invoice.id, None)]
                )
                reg_pay_form = self.env.ref('account.view_account_payment_invoice_form')
                return {
                    'name': _('Register Payment'),
                    'type': 'ir.actions.act_window',
                    'view_type': 'form',
                    'view_mode': 'form',
                    'res_model': 'account.payment',
                    'views': [(reg_pay_form.id, 'form')],
                    'view_id': reg_pay_form.id,
                    'target': 'new',
                    'context': ctx,
                }
        else:
            return res
    @api.onchange('shop_id')
    def onchange_shop_id(self):
        self.warehouse_id = self.shop_id.warehouse_id.id
        self.location_id = self.shop_id.location_id.id
        self.payment_term_id = self.shop_id.payment_default_id.id
        self.project_id = self.shop_id.project_id.id if self.shop_id.project_id else False
        if self.shop_id.pricelist_id:
            self.pricelist_id = self.shop_id.pricelist_id.id
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
Esempio n. 29
0
class Partner(models.Model):
    _inherit = 'res.partner'

    commercial_name = fields.Char(string="Nombre comercial", index=True)
    registration_name = fields.Char(string="Razón social",
                                    related="name",
                                    store=True,
                                    readonly=False,
                                    index=True)
    l10n_latam_identification_type_id = fields.Many2one(string="Tipo Doc. Id.")
    vat = fields.Char(string="Nro. Doc. Id.")
    state = fields.Selection(string="State",
                             selection=[('habido', 'Habido'),
                                        ('nhabido', 'No Habido')])

    #@api.depends('is_company', 'name', 'parent_id.name', 'type', 'company_name', 'commercial_name')
    #def _compute_display_name(self) :
    #    super(Partner, self)._compute_display_name()
    #    for partner in self :
    #        partner.display_name = (partner.vat + " - " if partner.vat else "") + partner.display_name + (" - " + partner.commercial_name if partner.commercial_name else "")

    def _get_name(self):
        nombre = super(Partner, self)._get_name()
        #if not self._context.get('show_vat') or not self.vat :
        #    nombre = nombre + (self.vat + " ‒ " if self.vat else "")
        return nombre

    def name_get(self):
        res = super(Partner, self).name_get()
        #new_res = res[:]
        #for old_name in res :
        #    new_res.remove(old_name)
        #    new_name = list(old_name)
        #    if isinstance(new_name[0], int) and self.search([('id','=',new_name[0])]) :
        #        new_name.append(self.browse(new_name[0]).vat)
        #        if new_name[2] and (' ‒ ' + new_name[2]) not in new_name[1] :
        #            new_name[1] = new_name[1] + ' ‒ ' + new_name[2] #not the common hyphen
        #        new_name = new_name[:2]
        #    new_res.append(tuple(new_name))
        #return new_res
        return res

    @api.onchange('l10n_latam_identification_type_id', 'vat')
    def vat_change(self):
        if self.l10n_latam_identification_type_id.l10n_pe_vat_code in [
                '1', '6'
        ] and self.vat and self.vat.strip():
            self.update_document()

    def update_document(self):
        if len(
                self
        ) != 1 or not self.l10n_latam_identification_type_id or not self.vat or not self.vat.strip(
        ):
            return False
        cif = self.vat.strip()
        district_id_default = self.env.ref("l10n_pe.district_pe_" +
                                           DEFAULT_ZIPCODE)
        if self.l10n_latam_identification_type_id.l10n_pe_vat_code == '1':
            if len(cif) != 8 or not cif.isdigit():
                cif = 'El DNI' + (
                    len(cif) != 8 and ' debe tener 8 caracteres' or '') + (
                        (len(cif) != 8 and not cif.isdigit()) and ' y '
                        or '') + (
                            (not cif.isdigit())
                            and ' solo debe poseer caracteres numéricos' or '')
                raise Warning(cif)

            d = get_data_doc_number("dni", cif, format="json")
            #d = new_get_data_doc_number("persona", cif, format="json")
            if d['error']:
                return True
            d = d['data']

            self.name = '%s %s %s' % (d['nombres'], d['ape_paterno'],
                                      d['ape_materno'])
            self.street_name = False
            self.country_id = district_id_default.city_id.country_id
            self.state_id = district_id_default.city_id.state_id
            self.city_id = district_id_default.city_id
            self.l10n_pe_district = district_id_default
            self.zip = district_id_default.code
        elif self.l10n_latam_identification_type_id.l10n_pe_vat_code == '6':
            if len(cif) != 11 or not cif.isdigit():
                cif = 'El RUC' + (
                    len(cif) != 11 and ' debe tener 11 caracteres' or '') + (
                        (len(cif) != 11 and not cif.isdigit()) and ' y'
                        or '') + (
                            (not cif.isdigit())
                            and ' solo debe poseer caracteres numéricos' or '')
                raise Warning(cif)
            if not self.check_vat_pe(cif):
                raise Warning('El RUC ingresado no es válido')

            d = get_data_ruc(cif)
            #d = get_data_doc_number('ruc', cif, format='json')
            #d = new_get_data_doc_number('empresa', cif, format='json')
            if d['error']:
                return True
            d = d['data']

            city_ids = self.env['res.city']
            if 'provincia' in d and d['provincia']:
                if d['provincia'].strip().upper(
                ) != district_id_default.city_id.name.upper():
                    city_ids = city_ids.search([('country_id', '=', 173),
                                                ('name', 'ilike',
                                                 d['provincia'].strip())])
                else:
                    city_ids = district_id_default.city_id
            if city_ids:
                if 'distrito' in d and d['distrito']:
                    if d['distrito'].strip().upper(
                    ) != district_id_default.name.upper():
                        district_id_default = self.env[
                            'l10n_pe.res.city.district'].search(
                                [('city_id', 'in', city_ids.ids),
                                 ('name', 'ilike', d['distrito'].strip())],
                                limit=1) or district_id_default

            self.country_id = district_id_default.city_id.country_id
            self.state_id = district_id_default.city_id.state_id
            self.city_id = district_id_default.city_id
            self.l10n_pe_district = district_id_default
            self.zip = district_id_default.code

            ##d = get_data_doc_number('ruc', cif, format='json')
            #self.name = d['nombre'].strip()
            ##self.registration_name = d['nombre'] #related field
            #self.commercial_name = d['nombre_comercial'].strip() or d['nombre'].strip()
            #self.street_name = d['domicilio_fiscal'].strip()
            #self.state = d['condicion_contribuyente'].strip() == 'HABIDO' and 'habido' or 'nhabido'
            #self.is_company = True

            ##d = get_data_ruc(cif)
            self.name = d['razon_social'].strip()
            #self.registration_name = d['razon_social'] #related field
            self.commercial_name = d['nombre_comercial'].strip(
            ) != '-' and d['nombre_comercial'].strip()
            self.street_name = d['domicilio_fiscal'].strip()
            self.state = d['contribuyente_condicion'].strip().upper(
            ) == 'HABIDO' and 'habido' or 'nhabido'
            self.is_company = True

            return True
        return True
Esempio n. 30
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()