Ejemplo n.º 1
0
class Location(models.Model):
    _name = "stock.location"
    _description = "Inventory Locations"
    _parent_name = "location_id"
    _parent_store = True
    _order = 'complete_name'
    _rec_name = 'complete_name'
    _check_company_auto = True

    @api.model
    def default_get(self, fields):
        res = super(Location, self).default_get(fields)
        if 'barcode' in fields and 'barcode' not in res and res.get('complete_name'):
            res['barcode'] = res['complete_name']
        return res

    name = fields.Char('Location Name', required=True)
    complete_name = fields.Char("Full Location Name", compute='_compute_complete_name', recursive=True, store=True)
    active = fields.Boolean('Active', default=True, help="By unchecking the active field, you may hide a location without deleting it.")
    usage = fields.Selection([
        ('supplier', 'Vendor Location'),
        ('view', 'View'),
        ('internal', 'Internal Location'),
        ('customer', 'Customer Location'),
        ('inventory', 'Inventory Loss'),
        ('production', 'Production'),
        ('transit', 'Transit Location')], string='Location Type',
        default='internal', index=True, required=True,
        help="* Vendor Location: Virtual location representing the source location for products coming from your vendors"
             "\n* View: Virtual location used to create a hierarchical structures for your warehouse, aggregating its child locations ; can't directly contain products"
             "\n* Internal Location: Physical locations inside your own warehouses,"
             "\n* Customer Location: Virtual location representing the destination location for products sent to your customers"
             "\n* Inventory Loss: Virtual location serving as counterpart for inventory operations used to correct stock levels (Physical inventories)"
             "\n* Production: Virtual counterpart location for production operations: this location consumes the components and produces finished products"
             "\n* Transit Location: Counterpart location that should be used in inter-company or inter-warehouses operations")
    location_id = fields.Many2one(
        'stock.location', 'Parent Location', index=True, ondelete='cascade', check_company=True,
        help="The parent location that includes this location. Example : The 'Dispatch Zone' is the 'Gate 1' parent location.")
    child_ids = fields.One2many('stock.location', 'location_id', 'Contains')
    child_internal_location_ids = fields.Many2many(
        'stock.location',
        string='Internal locations amoung descendants',
        compute='_compute_child_internal_location_ids',
        recursive=True,
        help='This location (if it\'s internal) and all its descendants filtered by type=Internal.'
    )
    comment = fields.Html('Additional Information')
    posx = fields.Integer('Corridor (X)', default=0, help="Optional localization details, for information purpose only")
    posy = fields.Integer('Shelves (Y)', default=0, help="Optional localization details, for information purpose only")
    posz = fields.Integer('Height (Z)', default=0, help="Optional localization details, for information purpose only")
    parent_path = fields.Char(index=True)
    company_id = fields.Many2one(
        'res.company', 'Company',
        default=lambda self: self.env.company, index=True,
        help='Let this field empty if this location is shared between companies')
    scrap_location = fields.Boolean('Is a Scrap Location?', default=False, help='Check this box to allow using this location to put scrapped/damaged goods.')
    return_location = fields.Boolean('Is a Return Location?', help='Check this box to allow using this location as a return location.')
    removal_strategy_id = fields.Many2one('product.removal', 'Removal Strategy', help="Defines the default method used for suggesting the exact location (shelf) where to take the products from, which lot etc. for this location. This method can be enforced at the product category level, and a fallback is made on the parent locations if none is set here.")
    putaway_rule_ids = fields.One2many('stock.putaway.rule', 'location_in_id', 'Putaway Rules')
    barcode = fields.Char('Barcode', copy=False)
    quant_ids = fields.One2many('stock.quant', 'location_id')
    cyclic_inventory_frequency = fields.Integer("Inventory Frequency (Days)", default=0, help=" When different than 0, inventory count date for products stored at this location will be automatically set at the defined frequency.")
    last_inventory_date = fields.Date("Last Effective Inventory", readonly=True, help="Date of the last inventory at this location.")
    next_inventory_date = fields.Date("Next Expected Inventory", compute="_compute_next_inventory_date", store=True, help="Date for next planned inventory based on cyclic schedule.")
    warehouse_view_ids = fields.One2many('stock.warehouse', 'view_location_id', readonly=True)
    warehouse_id = fields.Many2one('stock.warehouse', compute='_compute_warehouse_id', recursive=True)
    storage_category_id = fields.Many2one('stock.storage.category', string='Storage Category')
    outgoing_move_line_ids = fields.One2many('stock.move.line', 'location_id', help='Technical: used to compute weight.')
    incoming_move_line_ids = fields.One2many('stock.move.line', 'location_dest_id', help='Technical: used to compute weight.')
    net_weight = fields.Float('Net Weight', compute="_compute_weight")
    forecast_weight = fields.Float('Forecasted Weight', compute="_compute_weight")

    _sql_constraints = [('barcode_company_uniq', 'unique (barcode,company_id)', 'The barcode for a location must be unique per company !'),
                        ('inventory_freq_nonneg', 'check(cyclic_inventory_frequency >= 0)', 'The inventory frequency (days) for a location must be non-negative')]

    @api.depends('outgoing_move_line_ids.product_qty', 'incoming_move_line_ids.product_qty',
                 'outgoing_move_line_ids.state', 'incoming_move_line_ids.state',
                 'outgoing_move_line_ids.product_id.weight', 'outgoing_move_line_ids.product_id.weight',
                 'quant_ids.quantity', 'quant_ids.product_id.weight')
    def _compute_weight(self):
        for location in self:
            location.net_weight = 0
            quants = location.quant_ids.filtered(lambda q: q.product_id.type != 'service')
            incoming_move_lines = location.incoming_move_line_ids.filtered(lambda ml: ml.product_id.type != 'service' and ml.state not in ['draft', 'done', 'cancel'])
            outgoing_move_lines = location.outgoing_move_line_ids.filtered(lambda ml: ml.product_id.type != 'service' and ml.state not in ['draft', 'done', 'cancel'])
            for quant in quants:
                location.net_weight += quant.product_id.weight * quant.quantity
            location.forecast_weight = location.net_weight
            for line in incoming_move_lines:
                location.forecast_weight += line.product_id.weight * line.product_qty
            for line in outgoing_move_lines:
                location.forecast_weight -= line.product_id.weight * line.product_qty

    @api.depends('name', 'location_id.complete_name', 'usage')
    def _compute_complete_name(self):
        for location in self:
            if location.location_id and location.usage != 'view':
                location.complete_name = '%s/%s' % (location.location_id.complete_name, location.name)
            else:
                location.complete_name = location.name

    @api.depends('cyclic_inventory_frequency', 'last_inventory_date', 'usage', 'company_id')
    def _compute_next_inventory_date(self):
        for location in self:
            if location.company_id and location.usage in ['internal', 'transit'] and location.cyclic_inventory_frequency > 0:
                try:
                    if location.last_inventory_date:
                        days_until_next_inventory = location.cyclic_inventory_frequency - (fields.Date.today() - location.last_inventory_date).days
                        if days_until_next_inventory <= 0:
                            location.next_inventory_date = fields.Date.today() + timedelta(days=1)
                        else:
                            location.next_inventory_date = location.last_inventory_date + timedelta(days=days_until_next_inventory)
                    else:
                        location.next_inventory_date = fields.Date.today() + timedelta(days=location.cyclic_inventory_frequency)
                except OverflowError:
                    raise UserError(_("The selected Inventory Frequency (Days) creates a date too far into the future."))
            else:
                location.next_inventory_date = False

    @api.depends('location_id.warehouse_id', 'warehouse_view_ids')
    def _compute_warehouse_id(self):
        warehouses = self.env['stock.warehouse'].search([('view_location_id', 'parent_of', self.ids)])
        view_by_wh = OrderedDict((wh.view_location_id.id, wh.id) for wh in warehouses)
        self.warehouse_id = False
        for loc in self:
            path = set(int(loc_id) for loc_id in loc.parent_path.split('/')[:-1])
            for view_location_id in view_by_wh:
                if view_location_id in path:
                    loc.warehouse_id = view_by_wh[view_location_id]
                    break

    @api.depends('child_ids.usage', 'child_ids.child_internal_location_ids')
    def _compute_child_internal_location_ids(self):
        # batch reading optimization is not possible because the field has recursive=True
        for loc in self:
            loc.child_internal_location_ids = self.search([('id', 'child_of', loc.id), ('usage', '=', 'internal')])

    @api.onchange('usage')
    def _onchange_usage(self):
        if self.usage not in ('internal', 'inventory'):
            self.scrap_location = False

    def write(self, values):
        if 'company_id' in values:
            for location in self:
                if location.company_id.id != values['company_id']:
                    raise UserError(_("Changing the company of this record is forbidden at this point, you should rather archive it and create a new one."))
        if 'usage' in values and values['usage'] == 'view':
            if self.mapped('quant_ids'):
                raise UserError(_("This location's usage cannot be changed to view as it contains products."))
        if 'usage' in values or 'scrap_location' in values:
            modified_locations = self.filtered(
                lambda l: any(l[f] != values[f] if f in values else False
                              for f in {'usage', 'scrap_location'}))
            reserved_quantities = self.env['stock.move.line'].search_count([
                ('location_id', 'in', modified_locations.ids),
                ('product_qty', '>', 0),
            ])
            if reserved_quantities:
                raise UserError(_(
                    "You cannot change the location type or its use as a scrap"
                    " location as there are products reserved in this location."
                    " Please unreserve the products first."
                ))
        if 'active' in values:
            if values['active'] == False:
                for location in self:
                    warehouses = self.env['stock.warehouse'].search([('active', '=', True), '|', ('lot_stock_id', '=', location.id), ('view_location_id', '=', location.id)])
                    if warehouses:
                        raise UserError(_("You cannot archive the location %s as it is"
                        " used by your warehouse %s") % (location.display_name, warehouses[0].display_name))

            if not self.env.context.get('do_not_check_quant'):
                children_location = self.env['stock.location'].with_context(active_test=False).search([('id', 'child_of', self.ids)])
                internal_children_locations = children_location.filtered(lambda l: l.usage == 'internal')
                children_quants = self.env['stock.quant'].search(['&', '|', ('quantity', '!=', 0), ('reserved_quantity', '!=', 0), ('location_id', 'in', internal_children_locations.ids)])
                if children_quants and values['active'] == False:
                    raise UserError(_('You still have some product in locations %s') %
                        (', '.join(children_quants.mapped('location_id.display_name'))))
                else:
                    super(Location, children_location - self).with_context(do_not_check_quant=True).write({
                        'active': values['active'],
                    })

        return super(Location, self).write(values)

    @api.model
    def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
        """ search full name and barcode """
        args = args or []
        if operator == 'ilike' and not (name or '').strip():
            domain = []
        elif operator in expression.NEGATIVE_TERM_OPERATORS:
            domain = [('barcode', operator, name), ('complete_name', operator, name)]
        else:
            domain = ['|', ('barcode', operator, name), ('complete_name', operator, name)]
        return self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid)

    def _get_putaway_strategy(self, product, quantity=0, package=None, packaging=None):
        """Returns the location where the product has to be put, if any compliant
        putaway strategy is found. Otherwise returns self.
        The quantity should be in the default UOM of the product, it is used when
        no package is specified.
        """
        # find package type on package or packaging
        package_type = self.env['stock.package.type']
        if package:
            package_type = package.package_type_id
        elif packaging:
            package_type = packaging.package_type_id

        putaway_rules = self.env['stock.putaway.rule']
        putaway_rules |= self.putaway_rule_ids.filtered(lambda x: x.product_id == product and (package_type in x.package_type_ids or package_type == x.package_type_ids))
        categ = product.categ_id
        while categ:
            putaway_rules |= self.putaway_rule_ids.filtered(lambda x: x.category_id == categ and (package_type in x.package_type_ids or package_type == x.package_type_ids))
            categ = categ.parent_id
        if package_type:
            putaway_rules |= self.putaway_rule_ids.filtered(lambda x: not x.product_id and (package_type in x.package_type_ids or package_type == x.package_type_ids))

        # get current product qty (qty in current quants and future qty on assigned ml) of all child locations
        qty_by_location = defaultdict(lambda: 0)
        locations = self.child_internal_location_ids
        if locations.storage_category_id:
            move_line_data = self.env['stock.move.line'].read_group([
                ('product_id', '=', product.id),
                ('location_dest_id', 'in', locations.ids),
                ('state', 'not in', ['draft', 'done', 'cancel'])
            ], ['location_dest_id', 'product_id', 'product_qty:array_agg', 'qty_done:array_agg', 'product_uom_id:array_agg'], ['location_dest_id'])
            quant_data = self.env['stock.quant'].read_group([
                ('product_id', '=', product.id),
                ('location_id', 'in', locations.ids),
            ], ['location_id', 'product_id', 'quantity:sum'], ['location_id'])

            for values in move_line_data:
                uoms = self.env['uom.uom'].browse(values['product_uom_id'])
                qty_done = sum(max(ml_uom._compute_quantity(float(qty), product.uom_id), float(qty_reserved))
                               for qty_reserved, qty, ml_uom in zip(values['product_qty'], values['qty_done'], list(uoms)))
                qty_by_location[values['location_dest_id'][0]] = qty_done
            for values in quant_data:
                qty_by_location[values['location_id'][0]] += values['quantity']

        putaway_location = putaway_rules._get_putaway_location(product, quantity, package, qty_by_location)
        if not putaway_location:
            putaway_location = locations[0] if locations and self.usage == 'view' else self

        return putaway_location

    def _get_next_inventory_date(self):
        """ Used to get the next inventory date for a quant located in this location. It is
        based on:
        1. Does the location have a cyclic inventory set?
        2. If not 1, then is there an annual inventory date set (for its company)?
        3. If not 1 and 2, then quants have no next inventory date."""
        if self.usage not in ['internal', 'transit']:
            return False
        next_inventory_date = False
        if self.next_inventory_date:
            next_inventory_date = self.next_inventory_date
        elif self.company_id.annual_inventory_month:
            today = fields.Date.today()
            annual_inventory_month = int(self.company_id.annual_inventory_month)
            # Manage 0 and negative annual_inventory_day
            annual_inventory_day = max(self.company_id.annual_inventory_day, 1)
            max_day = calendar.monthrange(today.year, annual_inventory_month)[1]
            # Manage annual_inventory_day bigger than last_day
            annual_inventory_day = min(annual_inventory_day, max_day)
            next_inventory_date = today.replace(
                month=annual_inventory_month, day=annual_inventory_day)
            if next_inventory_date <= today:
                # Manage leap year with the february
                max_day = calendar.monthrange(today.year + 1, annual_inventory_month)[1]
                annual_inventory_day = min(annual_inventory_day, max_day)
                next_inventory_date = next_inventory_date.replace(
                    day=annual_inventory_day, year=today.year + 1)
        return next_inventory_date

    def should_bypass_reservation(self):
        self.ensure_one()
        return self.usage in ('supplier', 'customer', 'inventory', 'production') or self.scrap_location or (self.usage == 'transit' and not self.company_id)

    def _check_can_be_used(self, product, quantity=0, package=None, location_qty=0):
        """Check if product/package can be stored in the location. Quantity
        should in the default uom of product, it's only used when no package is
        specified."""
        self.ensure_one()
        if package and package.package_type_id:
            return self._check_package_storage(product, package)
        return self._check_product_storage(product, quantity, location_qty)

    def _check_product_storage(self, product, quantity, location_qty):
        """Check if a number of product can be stored in the location. Quantity
        should in the default uom of product."""
        self.ensure_one()
        if self.storage_category_id:
            # check weight
            if self.storage_category_id.max_weight < self.forecast_weight + product.weight * quantity:
                return False
            # check if only allow new product when empty
            if self.storage_category_id.allow_new_product == "empty" and any(float_compare(q.quantity, 0, precision_rounding=q.product_id.uom_id.rounding) > 0 for q in self.quant_ids):
                return False
            # check if only allow same product
            if self.storage_category_id.allow_new_product == "same" and self.quant_ids and self.quant_ids.product_id != product:
                return False

            # check if enough space
            product_capacity = self.storage_category_id.product_capacity_ids.filtered(lambda pc: pc.product_id == product)
            # To handle new line without quantity in order to avoid suggesting a location already full
            if product_capacity and location_qty >= product_capacity.quantity:
                return False
            if product_capacity and quantity + location_qty > product_capacity.quantity:
                return False
        return True

    def _check_package_storage(self, product, package):
        """Check if the given package can be stored in the location."""
        self.ensure_one()
        if self.storage_category_id:
            # check weight
            if self.storage_category_id.max_weight < self.forecast_weight + package.package_type_id.max_weight:
                return False
            # check if only allow new product when empty
            if self.storage_category_id.allow_new_product == "empty" and any(float_compare(q.quantity, 0, precision_rounding=q.product_id.uom_id.rounding) > 0 for q in self.quant_ids):
                return False
            # check if only allow same product
            if self.storage_category_id.allow_new_product == "same" and self.quant_ids and self.quant_ids.product_id != product:
                return False
            # check if enough space
            package_capacity = self.storage_category_id.package_capacity_ids.filtered(lambda pc: pc.package_type_id == package.package_type_id)
            if package_capacity:
                package_number = len(self.quant_ids.package_id.filtered(lambda q: q.package_type_id == package.package_type_id))
                if package_number >= package_capacity.quantity:
                    return False
        return True
Ejemplo n.º 2
0
class buy_order(models.Model):
    _name = "buy.order"
    _inherit = ['mail.thread']
    _description = u"购货订单"
    _order = 'date desc, id desc'

    @api.one
    @api.depends('line_ids.subtotal', 'discount_amount')
    def _compute_amount(self):
        '''当订单行和优惠金额改变时,改变优惠后金额'''
        total = sum(line.subtotal for line in self.line_ids)
        self.amount = total - self.discount_amount

    @api.one
    @api.depends('line_ids.quantity', 'line_ids.quantity_in')
    def _get_buy_goods_state(self):
        '''返回收货状态'''
        if all(line.quantity_in == 0 for line in self.line_ids):
            self.goods_state = u'未入库'
        elif any(line.quantity > line.quantity_in for line in self.line_ids):
            self.goods_state = u'部分入库'
        else:
            self.goods_state = u'全部入库'

    @api.model
    def _default_warehouse_dest_impl(self):
        if self.env.context.get('warehouse_dest_type'):
            return self.env['warehouse'].get_warehouse_by_type(
                self.env.context.get('warehouse_dest_type'))

        return self.env['warehouse'].browse()

    @api.model
    def _default_warehouse_dest(self):
        '''获取默认调入仓库'''
        return self._default_warehouse_dest_impl()

    @api.one
    @api.depends('type')
    def _get_money_state(self):
        '''计算购货订单付款/退款状态'''
        receipts = self.env['buy.receipt'].search([('order_id', '=', self.id)])
        if all(receipt.invoice_id.reconciled == 0 for receipt in receipts):
            self.money_state = (self.type == 'buy') and u'未付款' or u'未退款'
        elif all(receipt.invoice_id.reconciled == receipt.invoice_id.amount
                 for receipt in receipts):
            self.money_state = (self.type == 'buy') and u'全部付款' or u'全部退款'
        else:
            self.money_state = (self.type == 'buy') and u'部分付款' or u'部分退款'

    partner_id = fields.Many2one('partner',
                                 u'供应商',
                                 states=READONLY_STATES,
                                 ondelete='restrict',
                                 help=u'供应商')
    date = fields.Date(u'单据日期',
                       states=READONLY_STATES,
                       default=lambda self: fields.Date.context_today(self),
                       index=True,
                       copy=False,
                       help=u"默认是订单创建日期")
    planned_date = fields.Date(
        u'要求交货日期',
        states=READONLY_STATES,
        default=lambda self: fields.Date.context_today(self),
        index=True,
        copy=False,
        help=u"订单的要求交货日期")
    name = fields.Char(u'单据编号',
                       index=True,
                       copy=False,
                       help=u"购货订单的唯一编号,当创建时它会自动生成下一个编号。")
    type = fields.Selection([('buy', u'购货'), ('return', u'退货')],
                            u'类型',
                            default='buy',
                            states=READONLY_STATES,
                            help=u'购货订单的类型,分为购货或退货')
    warehouse_dest_id = fields.Many2one('warehouse',
                                        u'调入仓库',
                                        default=_default_warehouse_dest,
                                        ondelete='restrict',
                                        states=READONLY_STATES,
                                        help=u'将产品调入到该仓库')
    invoice_by_receipt = fields.Boolean(
        string=u"按收货结算",
        default=True,
        help=u'如未勾选此项,可在资金行里输入付款金额,订单保存后,采购人员可以单击资金行上的【确认】按钮。')
    line_ids = fields.One2many('buy.order.line',
                               'order_id',
                               u'购货订单行',
                               states=READONLY_STATES,
                               copy=True,
                               help=u'购货订单的明细行,不能为空')
    note = fields.Text(u'备注', help=u'单据备注')
    discount_rate = fields.Float(u'优惠率(%)',
                                 states=READONLY_STATES,
                                 digits=dp.get_precision('Amount'),
                                 help=u'整单优惠率')
    discount_amount = fields.Float(u'优惠金额',
                                   states=READONLY_STATES,
                                   track_visibility='always',
                                   digits=dp.get_precision('Amount'),
                                   help=u'整单优惠金额,可由优惠率自动计算出来,也可手动输入')
    amount = fields.Float(u'优惠后金额',
                          store=True,
                          readonly=True,
                          compute='_compute_amount',
                          track_visibility='always',
                          digits=dp.get_precision('Amount'),
                          help=u'总金额减去优惠金额')
    prepayment = fields.Float(u'预付款',
                              states=READONLY_STATES,
                              digits=dp.get_precision('Amount'),
                              help=u'输入预付款审核购货订单,会产生一张付款单')
    bank_account_id = fields.Many2one('bank.account',
                                      u'结算账户',
                                      ondelete='restrict',
                                      help=u'用来核算和监督企业与其他单位或个人之间的债权债务的结算情况')
    approve_uid = fields.Many2one('res.users',
                                  u'审核人',
                                  copy=False,
                                  ondelete='restrict',
                                  help=u'审核单据的人')
    state = fields.Selection(BUY_ORDER_STATES,
                             u'审核状态',
                             readonly=True,
                             help=u"购货订单的审核状态",
                             index=True,
                             copy=False,
                             default='draft')
    goods_state = fields.Char(u'收货状态',
                              compute=_get_buy_goods_state,
                              default=u'未入库',
                              store=True,
                              help=u"购货订单的收货状态",
                              index=True,
                              copy=False)
    cancelled = fields.Boolean(u'已终止', help=u'该单据是否已终止')
    pay_ids = fields.One2many("payment.plan",
                              "buy_id",
                              string=u"付款计划",
                              help=u'分批付款时使用付款计划')
    money_state = fields.Char(u'付/退款状态',
                              compute=_get_money_state,
                              copy=False,
                              help=u'购货订单生成的采购入库单或退货单的付/退款状态')

    @api.onchange('discount_rate', 'line_ids')
    def onchange_discount_rate(self):
        '''当优惠率或购货订单行发生变化时,单据优惠金额发生变化'''
        total = sum(line.subtotal for line in self.line_ids)
        self.discount_amount = total * self.discount_rate * 0.01

    @api.onchange('partner_id')
    def onchange_partner_id(self):
        if self.partner_id:
            for line in self.line_ids:
                if line.goods_id.tax_rate and self.partner_id.tax_rate:
                    if line.goods_id.tax_rate >= self.partner_id.tax_rate:
                        line.tax_rate = self.partner_id.tax_rate
                    else:
                        line.tax_rate = line.goods_id.tax_rate
                elif line.goods_id.tax_rate and not self.partner_id.tax_rate:
                    line.tax_rate = line.goods_id.tax_rate
                elif not line.goods_id.tax_rate and self.partner_id.tax_rate:
                    line.tax_rate = self.partner_id.tax_rate
                else:
                    line.tax_rate = self.env.user.company_id.import_tax_rate

    @api.multi
    def unlink(self):
        for order in self:
            if order.state == 'done':
                raise UserError(u'不能删除已审核的单据(%s)' % order.name)

        return super(buy_order, self).unlink()

    def _get_vals(self):
        '''返回创建 money_order 时所需数据'''
        flag = (self.type == 'buy' and 1 or -1)  # 用来标志入库或退货
        amount = flag * self.amount
        this_reconcile = flag * self.prepayment
        money_lines = [{
            'bank_id': self.bank_account_id.id,
            'amount': this_reconcile,
        }]
        return {
            'partner_id': self.partner_id.id,
            'date': fields.Date.context_today(self),
            'line_ids': [(0, 0, line) for line in money_lines],
            'amount': amount,
            'reconciled': this_reconcile,
            'to_reconcile': amount,
            'state': 'draft',
            'origin_name': self.name,
        }

    @api.one
    def generate_payment_order(self):
        '''由购货订单生成付款单'''
        # 入库单/退货单
        if self.prepayment:
            money_order = self.with_context(
                type='pay').env['money.order'].create(self._get_vals())
            return money_order

    @api.one
    def buy_order_done(self):
        '''审核购货订单'''
        if self.state == 'done':
            raise UserError(u'请不要重复审核!')
        if not self.line_ids:
            raise UserError(u'请输入产品明细行!')
        for line in self.line_ids:
            if line.quantity <= 0 or line.price_taxed < 0:
                raise UserError(u'产品 %s 的数量和含税单价不能小于0!' % line.goods_id.name)
        if not self.bank_account_id and self.prepayment:
            raise UserError(u'预付款不为空时,请选择结算账户!')
        # 采购预付款生成付款单
        self.generate_payment_order()
        self.buy_generate_receipt()
        self.state = 'done'
        self.approve_uid = self._uid

    @api.one
    def buy_order_draft(self):
        '''反审核购货订单'''
        if self.state == 'draft':
            raise UserError(u'请不要重复反审核!')
        if self.goods_state != u'未入库':
            raise UserError(u'该购货订单已经收货,不能反审核!')
        # 查找产生的入库单并删除
        receipt = self.env['buy.receipt'].search([('order_id', '=', self.name)
                                                  ])
        receipt.unlink()
        #查找产生的付款单并反审核,删除
        money_order = self.env['money.order'].search([('origin_name', '=',
                                                       self.name)])
        if money_order:
            money_order.money_order_draft()
            money_order.unlink()
        self.state = 'draft'
        self.approve_uid = ''

    @api.one
    def get_receipt_line(self, line, single=False):
        '''返回采购入库/退货单行'''
        qty = 0
        discount_amount = 0
        if single:
            qty = 1
            discount_amount = (line.discount_amount /
                               ((line.quantity - line.quantity_in) or 1))
        else:
            qty = line.quantity - line.quantity_in
            discount_amount = line.discount_amount
        return {
            'buy_line_id':
            line.id,
            'goods_id':
            line.goods_id.id,
            'attribute_id':
            line.attribute_id.id,
            'goods_uos_qty':
            line.goods_id.conversion and qty / line.goods_id.conversion or qty,
            'uos_id':
            line.goods_id.uos_id.id,
            'goods_qty':
            qty,
            'uom_id':
            line.uom_id.id,
            'cost_unit':
            line.price,
            'price_taxed':
            line.price_taxed,
            'discount_rate':
            line.discount_rate,
            'discount_amount':
            discount_amount,
            'tax_rate':
            line.tax_rate,
            'note':
            line.note or '',
        }

    def _generate_receipt(self, receipt_line):
        '''根据明细行生成入库单或退货单'''
        # 如果退货,warehouse_dest_id,warehouse_id要调换
        warehouse = (self.type == 'buy'
                     and self.env.ref("warehouse.warehouse_supplier")
                     or self.warehouse_dest_id)
        warehouse_dest = (self.type == 'buy' and self.warehouse_dest_id
                          or self.env.ref("warehouse.warehouse_supplier"))
        rec = (self.type == 'buy' and self.with_context(is_return=False)
               or self.with_context(is_return=True))
        receipt_id = rec.env['buy.receipt'].create({
            'partner_id':
            self.partner_id.id,
            'warehouse_id':
            warehouse.id,
            'warehouse_dest_id':
            warehouse_dest.id,
            'date':
            self.planned_date,
            'date_due':
            self.planned_date,
            'order_id':
            self.id,
            'origin':
            'buy.receipt',
            'note':
            self.note,
            'discount_rate':
            self.discount_rate,
            'discount_amount':
            self.discount_amount,
            'invoice_by_receipt':
            self.invoice_by_receipt,
        })
        if self.type == 'buy':
            receipt_id.write(
                {'line_in_ids': [(0, 0, line[0]) for line in receipt_line]})
        else:
            receipt_id.write(
                {'line_out_ids': [(0, 0, line[0]) for line in receipt_line]})
        return receipt_id

    @api.one
    def buy_generate_receipt(self):
        '''由购货订单生成采购入库/退货单'''
        receipt_line = []  # 采购入库/退货单行

        for line in self.line_ids:
            # 如果订单部分入库,则点击此按钮时生成剩余数量的入库单
            to_in = line.quantity - line.quantity_in
            if to_in <= 0:
                continue
            if line.goods_id.force_batch_one:
                i = 0
                while i < to_in:
                    i += 1
                    receipt_line.append(
                        self.get_receipt_line(line, single=True))
            else:
                receipt_line.append(self.get_receipt_line(line, single=False))

        if not receipt_line:
            return {}
        receipt_id = self._generate_receipt(receipt_line)
        view_id = (self.type == 'buy'
                   and self.env.ref('buy.buy_receipt_form').id
                   or self.env.ref('buy.buy_return_form').id)
        name = (self.type == 'buy' and u'采购入库单' or u'采购退货单')

        return {
            'name': name,
            'view_type': 'form',
            'view_mode': 'form',
            'view_id': False,
            'views': [(view_id, 'form')],
            'res_model': 'buy.receipt',
            'type': 'ir.actions.act_window',
            'domain': [('id', '=', receipt_id)],
            'target': 'current',
        }
Ejemplo n.º 3
0
class RecurrenceRule(models.Model):
    _name = 'calendar.recurrence'
    _description = 'Event Recurrence Rule'

    name = fields.Char(compute='_compute_name', store=True)
    base_event_id = fields.Many2one('calendar.event',
                                    ondelete='set null',
                                    copy=False)  # store=False ?
    calendar_event_ids = fields.One2many('calendar.event', 'recurrence_id')
    event_tz = fields.Selection(
        _tz_get,
        string='Timezone',
        default=lambda self: self.env.context.get('tz') or self.env.user.tz)
    rrule = fields.Char(compute='_compute_rrule',
                        inverse='_inverse_rrule',
                        store=True)
    dtstart = fields.Datetime(compute='_compute_dtstart')
    rrule_type = fields.Selection(RRULE_TYPE_SELECTION, default='weekly')
    end_type = fields.Selection(END_TYPE_SELECTION, default='count')
    interval = fields.Integer(default=1)
    count = fields.Integer(default=1)
    mo = fields.Boolean()
    tu = fields.Boolean()
    we = fields.Boolean()
    th = fields.Boolean()
    fr = fields.Boolean()
    sa = fields.Boolean()
    su = fields.Boolean()
    month_by = fields.Selection(MONTH_BY_SELECTION, default='date')
    day = fields.Integer(default=1)
    weekday = fields.Selection(WEEKDAY_SELECTION, string='Weekday')
    byday = fields.Selection(BYDAY_SELECTION, string='By day')
    until = fields.Date('Repeat Until')

    _sql_constraints = [
        ('month_day',
         "CHECK (rrule_type != 'monthly' OR month_by != 'day' OR day >= 1 AND day <= 31)",
         "The day must be between 1 and 31"),
    ]

    @api.depends('rrule')
    def _compute_name(self):
        for recurrence in self:
            period = dict(RRULE_TYPE_SELECTION)[recurrence.rrule_type]
            every = _("Every %s %s, ") % (recurrence.interval, period)

            if recurrence.end_type == 'count':
                end = _("for %s events") % recurrence.count
            elif recurrence.end_type == 'end_date':
                end = _("until %s") % recurrence.until
            else:
                end = ''

            if recurrence.rrule_type == 'weeky':
                weekdays = recurrence._get_week_days()
                weekday_fields = (self._fields[weekday_to_field(w)]
                                  for w in weekdays)
                on = _("on %s,") % ", ".join(
                    [field.string for field in weekday_fields])
            elif recurrence.rrule_type == 'monthly':
                if recurrence.month_by == 'day':
                    weekday_label = dict(BYDAY_SELECTION)[recurrence.byday]
                    on = _("on the %(position)s %(weekday)s, ") % {
                        'position': recurrence.byday,
                        'weekday': weekday_label
                    }
                else:
                    on = _("day %s, ") % recurrence.day
            else:
                on = ''
            recurrence.name = every + on + end

    @api.depends('calendar_event_ids.start')
    def _compute_dtstart(self):
        groups = self.env['calendar.event'].read_group(
            [('recurrence_id', 'in', self.ids)], ['start:min'],
            ['recurrence_id'])
        start_mapping = {
            group['recurrence_id'][0]: group['start']
            for group in groups
        }
        for recurrence in self:
            recurrence.dtstart = start_mapping.get(recurrence.id)

    @api.depends('byday', 'until', 'rrule_type', 'month_by', 'interval',
                 'count', 'end_type', 'mo', 'tu', 'we', 'th', 'fr', 'sa', 'su',
                 'day', 'weekday')
    def _compute_rrule(self):
        for recurrence in self:
            recurrence.rrule = recurrence._rrule_serialize()

    def _inverse_rrule(self):
        for recurrence in self:
            if recurrence.rrule:
                values = self._rrule_parse(recurrence.rrule,
                                           recurrence.dtstart)
                recurrence.write(values)

    def _reconcile_events(self, ranges):
        """
        :param ranges: iterable of tuples (datetime_start, datetime_stop)
        :return: tuple (events of the recurrence already in sync with ranges,
                 and ranges not covered by any events)
        """
        ranges = set(ranges)

        synced_events = self.calendar_event_ids.filtered(
            lambda e: e._range() in ranges)

        existing_ranges = set(event._range() for event in synced_events)
        ranges_to_create = (event_range for event_range in ranges
                            if event_range not in existing_ranges)
        return synced_events, ranges_to_create

    def _apply_recurrence(self):
        """Create missing events in the recurrence and detach events which no longer
        follow the recurrence rules.
        :return: detached events
        """
        event_vals = []
        keep = self.env['calendar.event']
        for recurrence in self.filtered('base_event_id'):
            self.calendar_event_ids |= recurrence.base_event_id
            event = recurrence.base_event_id or recurrence._get_first_event(
                include_outliers=False)
            duration = event.stop - event.start
            ranges = set(recurrence._get_ranges(event.start, duration))

            events_to_keep, ranges = recurrence._reconcile_events(ranges)
            keep |= events_to_keep
            [base_values] = event.copy_data()
            event_vals += [
                dict(base_values,
                     start=start,
                     stop=stop,
                     recurrence_id=recurrence.id) for start, stop in ranges
            ]

        events = self.calendar_event_ids - keep
        detached_events = self._detach_events(events)
        self.env['calendar.event'].with_context(
            no_mail_to_attendees=True,
            mail_create_nolog=True).create(event_vals)
        return detached_events

    def _split_from(self, event, recurrence_values=None):
        """Stops the current recurrence at the given event and creates a new one starting
        with the event.
        :param event: starting point of the new recurrence
        :param recurrence_values: values applied to the new recurrence
        :return: new recurrence
        """
        if recurrence_values is None:
            recurrence_values = {}
        event.ensure_one()
        if not self:
            return
        [values] = self.copy_data()
        detached_events = self._stop_at(event)

        count = recurrence_values.get('count', 0) or len(detached_events)
        return self.create({
            **values,
            **recurrence_values,
            'base_event_id': event.id,
            'calendar_event_ids': [(6, 0, detached_events.ids)],
            'count': max(count, 1),
        })

    def _stop_at(self, event):
        """Stops the recurrence at the given event. Detach the event and all following
        events from the recurrence.

        :return: detached events from the recurrence
        """
        self.ensure_one()
        events = self._get_events_from(event.start)
        detached_events = self._detach_events(events)
        if not self.calendar_event_ids:
            self.with_context(archive_on_error=True).unlink()
            return detached_events

        if event.allday:
            until = self._get_start_of_period(event.start_date)
        else:
            until_datetime = self._get_start_of_period(event.start)
            until_timezoned = pytz.utc.localize(until_datetime).astimezone(
                self._get_timezone())
            until = until_timezoned.date()
        self.write({
            'end_type': 'end_date',
            'until': until - relativedelta(days=1),
        })
        return detached_events

    @api.model
    def _detach_events(self, events):
        events.write({
            'recurrence_id': False,
            'recurrency': False,
        })
        return events

    def _write_events(self, values, dtstart=None):
        """
        Write values on events in the recurrence.
        :param values: event values
        :param dstart: if provided, only write events starting from this point in time
        """
        events = self._get_events_from(
            dtstart) if dtstart else self.calendar_event_ids
        return events.with_context(no_mail_to_attendees=True,
                                   dont_notify=True).write(
                                       dict(values,
                                            recurrence_update='self_only'))

    def _rrule_serialize(self):
        """
        Compute rule string according to value type RECUR of iCalendar
        :return: string containing recurring rule (empty if no rule)
        """
        if self.interval <= 0:
            raise UserError(_('The interval cannot be negative.'))
        if self.end_type == 'count' and self.count <= 0:
            raise UserError(_('The number of repetitions cannot be negative.'))

        return str(self._get_rrule()) if self.rrule_type else ''

    @api.model
    def _rrule_parse(self, rule_str, date_start):
        # LUL TODO clean this mess
        data = {}
        day_list = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']

        if 'Z' in rule_str and not date_start.tzinfo:
            date_start = pytz.utc.localize(date_start)
        rule = rrule.rrulestr(rule_str, dtstart=date_start)

        data['rrule_type'] = freq_to_select(rule._freq)
        data['count'] = rule._count
        data['interval'] = rule._interval
        data['until'] = rule._until
        # Repeat weekly
        if rule._byweekday:
            for weekday in day_list:
                data[weekday] = False  # reset
            for weekday_index in rule._byweekday:
                weekday = rrule.weekday(weekday_index)
                data[weekday_to_field(weekday.weekday)] = True
                data['rrule_type'] = 'weekly'

        # Repeat monthly by nweekday ((weekday, weeknumber), )
        if rule._bynweekday:
            data['weekday'] = day_list[list(rule._bynweekday)[0][0]].upper()
            data['byday'] = str(list(rule._bynweekday)[0][1])
            data['month_by'] = 'day'
            data['rrule_type'] = 'monthly'

        if rule._bymonthday:
            data['day'] = list(rule._bymonthday)[0]
            data['month_by'] = 'date'
            data['rrule_type'] = 'monthly'

        # Repeat yearly but for odoo it's monthly, take same information as monthly but interval is 12 times
        if rule._bymonth:
            data['interval'] *= 12

        if data.get('until'):
            data['end_type'] = 'end_date'
        elif data.get('count'):
            data['end_type'] = 'count'
        else:
            data['end_type'] = 'forever'
        return data

    def _get_start_of_period(self, dt):
        if self.rrule_type == 'daily':
            start = dt
        elif self.rrule_type == 'weekly':
            lang = self.env['res.lang']._lang_get(self.env.user.lang)
            week_start = int(
                lang.week_start)  # lang.week_start ranges from '1' to '7'
            week_start = rrule.weekday(week_start -
                                       1)  # expects an int from 0 to 6
            start = dt + relativedelta(weekday=week_start(-1))
        elif self.rrule_type == 'monthly':
            start = dt + relativedelta(day=1)
        elif self.rrule_type == 'yearly':
            start = dt
        return start

    def _get_first_event(self, include_outliers=False):
        if not self.calendar_event_ids:
            return self.env['calendar.event']
        events = self.calendar_event_ids.sorted('start')
        if not include_outliers:
            events -= self._get_outliers()
        return events[:1]

    def _get_outliers(self):
        synced_events = self.env['calendar.event']
        for recurrence in self:
            if recurrence.calendar_event_ids:
                start = min(recurrence.calendar_event_ids.mapped('start'))
                starts = set(recurrence._get_occurrences(start))
                synced_events |= recurrence.calendar_event_ids.filtered(
                    lambda e: e.start in starts)
        return self.calendar_event_ids - synced_events

    def _get_ranges(self, start, event_duration):
        starts = self._get_occurrences(start)
        return ((start, start + event_duration) for start in starts)

    def _get_timezone(self):
        return pytz.timezone(self.event_tz or self.env.context.get('tz')
                             or 'UTC')

    def _get_occurrences(self, dtstart):
        """
        Get ocurrences of the rrule
        :param dtstart: start of the recurrence
        :return: iterable of datetimes
        """
        self.ensure_one()
        dtstart = self._get_start_of_period(dtstart)
        if self._is_allday():
            return self._get_rrule(dtstart=dtstart)

        timezone = self._get_timezone()
        # Localize the starting datetime to avoid missing the first occurrence
        dtstart = pytz.utc.localize(dtstart).astimezone(timezone)
        # dtstart is given as a naive datetime, but it actually represents a timezoned datetime
        # (rrule package expects a naive datetime)
        occurences = self._get_rrule(dtstart=dtstart.replace(tzinfo=None))

        # Special timezoning is needed to handle DST (Daylight Saving Time) changes.
        # Given the following recurrence:
        #   - monthly
        #   - 1st of each month
        #   - timezone US/Eastern (UTC−05:00)
        #   - at 6am US/Eastern = 11am UTC
        #   - from 2019/02/01 to 2019/05/01.
        # The naive way would be to store:
        # 2019/02/01 11:00 - 2019/03/01 11:00 - 2019/04/01 11:00 - 2019/05/01 11:00 (UTC)
        #
        # But a DST change occurs on 2019/03/10 in US/Eastern timezone. US/Eastern is now UTC−04:00.
        # From this point in time, 11am (UTC) is actually converted to 7am (US/Eastern) instead of the expected 6am!
        # What should be stored is:
        # 2019/02/01 11:00 - 2019/03/01 11:00 - 2019/04/01 10:00 - 2019/05/01 10:00 (UTC)
        #                                                  *****              *****
        return (timezone.localize(occurrence, is_dst=False).astimezone(
            pytz.utc).replace(tzinfo=None) for occurrence in occurences)

    def _get_events_from(self, dtstart):
        return self.env['calendar.event'].search([
            ('id', 'in', self.calendar_event_ids.ids), ('start', '>=', dtstart)
        ])

    def _get_week_days(self):
        """
        :return: tuple of rrule weekdays for this recurrence.
        """
        return tuple(
            rrule.weekday(weekday_index) for weekday_index, weekday in {
                rrule.MO.weekday: self.mo,
                rrule.TU.weekday: self.tu,
                rrule.WE.weekday: self.we,
                rrule.TH.weekday: self.th,
                rrule.FR.weekday: self.fr,
                rrule.SA.weekday: self.sa,
                rrule.SU.weekday: self.su,
            }.items() if weekday)

    def _is_allday(self):
        """Returns whether a majority of events are allday or not (there might be some outlier events)
        """
        score = sum(1 if e.allday else -1 for e in self.calendar_event_ids)
        return score >= 0

    def _get_rrule(self, dtstart=None):
        self.ensure_one()
        freq = self.rrule_type
        rrule_params = dict(
            dtstart=dtstart,
            interval=self.interval,
        )
        if freq == 'monthly' and self.month_by == 'date':  # e.g. every 15th of the month
            rrule_params['bymonthday'] = self.day
        elif freq == 'monthly' and self.month_by == 'day':  # e.g. every 2nd Monday in the month
            rrule_params['byweekday'] = getattr(rrule, self.weekday)(int(
                self.byday))  # e.g. MO(+2) for the second Monday of the month
        elif freq == 'weekly':
            weekdays = self._get_week_days()
            if not weekdays:
                raise UserError(
                    _("You have to choose at least one day in the week"))
            rrule_params['byweekday'] = weekdays

        if self.end_type == 'count':  # e.g. stop after X occurence
            rrule_params['count'] = min(self.count, MAX_RECURRENT_EVENT)
        elif self.end_type == 'forever':
            rrule_params['count'] = MAX_RECURRENT_EVENT
        elif self.end_type == 'end_date':  # e.g. stop after 12/10/2020
            rrule_params['until'] = datetime.combine(self.until, time.max)
        return rrule.rrule(freq_to_rrule(freq), **rrule_params)
Ejemplo n.º 4
0
class CashFlow(models.TransientModel):
    _name = 'cash.flow'
    _description = 'Cash flow'

    date_start = fields.Date('Start Date', required=True, default='2021-01-01')
    date_end = fields.Date('End Date', required=True, default='2021-01-31')
    data_file = fields.Binary(readonly=True)
    data_file_fname = fields.Char()
    getted = fields.Boolean('Getted', default=False)
    report_format = fields.Selection([('csv', 'CSV'), ('pdf', 'PDF')],
                                     string="Report",
                                     default="csv")
    # cash_flow_detail_ids = fields.One2many(
    #     'cash.flow.detail', 'cash_flow_id', 'Deatils')

    @api.multi
    @keep_wizard_open
    def calculate(self):
        account_ids = (6808, 6809, 6810, 6811, 6812, 6813, 6814, 6815, 6816,
                       6817, 6818, 6819, 6820, 7192, 6821, 6822, 6823, 6824,
                       6825, 7194, 6826, 6827, 7177, 7288)
        AccountMoveLine_Obj = self.env['account.move.line']
        dict_list = []
        data = []
        fieldnames = [
            'CUENTAS', 'INICIAL', 'TRASPASOS', 'INGRESOS', 'RENDIMIENTOS',
            'ENTRADAS', 'PAGOS', 'COMIS.', 'RET', 'SALIDAS', 'SALDO'
        ]
        account_name = ""

        for account in account_ids:
            account_name = self.env['account.account'].search([('id', '=',
                                                                account)]).name
            statement_id = self.env['account.bank.statement'].search(
                [('date', '<=', self.date_start),
                 ('state', '=', 'confirm'), '&',
                 ('journal_id.default_credit_account_id', '=', account),
                 ('journal_id.default_debit_account_id', '=', account)],
                order="date desc",
                limit=1)
            balance = statement_id.balance_end

            move_line_transfer_ids = AccountMoveLine_Obj.search(
                [('date', '>', self.date_start), ('date', '<=', self.date_end),
                 ('move_id.move_type', '=', 'transfer'),
                 ('move_id.state', '=', 'posted'), ('credit', '>', 0.00),
                 ('account_id', '=', account)],
                order="date")
            credit = sum([line.credit for line in move_line_transfer_ids])

            move_line_transfer_ids = AccountMoveLine_Obj.search(
                [('date', '>', self.date_start), ('date', '<=', self.date_end),
                 ('move_id.move_type', '=', 'transfer'),
                 ('move_id.state', '=', 'posted'), ('debit', '>', 0.00),
                 ('account_id', '=', account)],
                order="date")
            debit = sum([line.debit for line in move_line_transfer_ids])

            move_line_yield_ids = AccountMoveLine_Obj.search(
                [('date', '>', self.date_start), ('date', '<=', self.date_end),
                 ('move_id.move_type', '=', 'yield'),
                 ('move_id.state', '=', 'posted'), ('debit', '>', 0.00),
                 ('account_id.id', '=', account)],
                order="date")
            yield_ = sum([line.debit for line in move_line_yield_ids])

            move_line_income_ids = AccountMoveLine_Obj.search(
                [('date', '>', self.date_start), ('date', '<=', self.date_end),
                 ('move_id.move_type', '=', 'inbound'),
                 ('move_id.state', '=', 'posted'), ('debit', '>', 0.00),
                 ('account_id.id', '=', account)],
                order="date")
            income = sum([line.debit for line in move_line_income_ids])

            move_line_expense_ids = AccountMoveLine_Obj.search(
                [('create_date', '>', self.date_start),
                 ('create_date', '<=', self.date_end),
                 ('move_id.move_type', '=', 'outbound'),
                 ('move_id.state', '=', 'posted'), ('credit', '>', 0.00),
                 ('account_id.id', '=', account)],
                order="date")
            exprenses = sum([line.credit for line in move_line_expense_ids])

            move_line_commission_ids = AccountMoveLine_Obj.search(
                [('create_date', '>', self.date_start),
                 ('create_date', '<=', self.date_end),
                 ('move_id.move_type', '=', 'commissions'),
                 ('move_id.state', '=', 'posted'), ('credit', '>', 0.00),
                 ('account_id.id', '=', account)],
                order="date")
            commissions = sum(
                [line.credit for line in move_line_commission_ids])

            move_line_withholdings_ids = AccountMoveLine_Obj.search(
                [('create_date', '>', self.date_start),
                 ('create_date', '<=', self.date_end),
                 ('move_id.move_type', '=', 'withholdings'),
                 ('move_id.state', '=', 'posted'), ('credit', '>', 0.00),
                 ('account_id.id', '=', account)],
                order="date")
            withholdings = sum(
                [line.credit for line in move_line_withholdings_ids])

            data_data = {
                'CUENTAS':
                account_name,
                'INICIAL':
                balance,
                # 'TRASPASOS+': credit,
                # 'TRASPASOS-': debit,
                'TRASPASOS':
                credit - debit,
                'INGRESOS':
                income,
                'RENDIMIENTOS':
                yield_,
                'ENTRADAS':
                income + yield_,
                'PAGOS':
                exprenses,
                'COMIS':
                commissions,
                'RET':
                withholdings,
                'SALIDAS':
                exprenses + commissions + withholdings,
                'SALDO':
                balance + (credit - debit) + (income + yield_) -
                (exprenses + commissions + withholdings)
            }
            data.append(data_data)

        self.getted = True
        if self.report_format == 'csv':
            fname = "cash_flow_%s.csv" % self.date_end.strftime("%d-%m-%y")
            self.data_file_fname = fname
            self.data_file = base64.b64encode(data_to_bytes(fieldnames, data))
        else:
            detail = self.env['cash.flow.detail']
            for d in data:
                print(d)
                detail.create({
                    'cashflow_id': self.id,
                    'account_name': d['CUENTAS'],
                    'initial_balance': d['INICIAL'],
                    'transfer_amount': d['TRASPASOS'],
                    'income_ammount': d['INGRESOS'],
                    'yield_amount': d['RENDIMIENTOS'],
                    'expense_amount': d['PAGOS'],
                    'commissions_amount': d['COMIS'],
                    'withholdings_amount': d['RET']
                })

    @api.multi
    def print_report(self, docids, data=None):
        if self.getted:
            report = self.env['ir.actions.report']._get_report_from_name(
                'cash_flow.report_template')
            return report.report_action(self)
        else:
            raise exceptions.Warning('No se han generado el reporte')
Ejemplo n.º 5
0
class DonationDonation(models.Model):
    _name = 'donation.donation'
    _inherit = ['mail.thread']
    _description = 'Donation'
    _order = 'id desc'

    @api.depends(
        'line_ids.unit_price', 'line_ids.quantity',
        'line_ids.product_id', 'donation_date', 'currency_id', 'company_id')
    def _compute_total(self):
        for donation in self:
            total = tax_receipt_total = 0.0
            donation_currency = donation.currency_id
            for line in donation.line_ids:
                line_total = line.quantity * line.unit_price
                total += line_total
                if line.tax_receipt_ok:
                    tax_receipt_total += line_total

            donation.amount_total = total
            donation_currency =\
                donation.currency_id.with_context(date=donation.donation_date)
            company_currency = donation.company_currency_id
            total_company_currency = donation_currency.compute(
                total, company_currency)
            tax_receipt_total_cc = donation_currency.compute(
                tax_receipt_total, company_currency)
            donation.amount_total_company_currency = total_company_currency
            donation.tax_receipt_total = tax_receipt_total_cc

    # We don't want a depends on partner_id.country_id, because if the partner
    # moves to another country, we want to keep the old country for
    # past donations to have good statistics
    @api.depends('partner_id')
    def _compute_country_id(self):
        for donation in self:
            donation.country_id = donation.partner_id.country_id

    @api.model
    def _default_currency(self):
        company = self.env['res.company']._company_default_get(
            'donation.donation')
        return company.currency_id

    currency_id = fields.Many2one(
        'res.currency',
        string='Currency',
        required=True,
        states={'done': [('readonly', True)]},
        track_visibility='onchange',
        ondelete='restrict',
        default=_default_currency
    )
    partner_id = fields.Many2one(
        'res.partner',
        string='Donor',
        required=True,
        index=True,
        states={'done': [('readonly', True)]},
        track_visibility='onchange',
        ondelete='restrict'
    )
    commercial_partner_id = fields.Many2one(
        related='partner_id.commercial_partner_id',
        string='Parent Donor',
        readonly=True,
        store=True,
        index=True,
        compute_sudo=True
    )
    # country_id is here to have stats per country
    # WARNING : I can't put a related field, because when someone
    # writes on the country_id of a partner, it will trigger a write
    # on all it's donations, including donations in other companies
    # which will be blocked by the record rule
    country_id = fields.Many2one(
        'res.country',
        string='Country',
        compute='_compute_country_id',
        store=True,
        readonly=True,
        compute_sudo=True
    )
    check_total = fields.Monetary(
        string='Check Amount',
        states={'done': [('readonly', True)]},
        currency_field='currency_id',
        track_visibility='onchange'
    )
    amount_total = fields.Monetary(
        compute='_compute_total',
        string='Amount Total',
        currency_field='currency_id',
        store=True,
        compute_sudo=True,
        readonly=True,
        track_visibility='onchange'
    )
    amount_total_company_currency = fields.Monetary(
        compute='_compute_total',
        string='Amount Total in Company Currency',
        currency_field='company_currency_id',
        compute_sudo=True,
        store=True,
        readonly=True
    )
    donation_date = fields.Date(
        string='Donation Date',
        required=True,
        states={'done': [('readonly', True)]},
        index=True,
        track_visibility='onchange'
    )
    company_id = fields.Many2one(
        'res.company',
        string='Company',
        required=True,
        states={'done': [('readonly', True)]},
        default=lambda self: self.env['res.company']._company_default_get(
            'donation.donation'))
    line_ids = fields.One2many(
        'donation.line',
        'donation_id',
        string='Donation Lines',
        states={'done': [('readonly', True)]},
    )
    move_id = fields.Many2one(
        'account.move',
        string='Account Move',
        readonly=True,
        copy=False
    )
    number = fields.Char(
        related='move_id.name',
        readonly=True,
        store=True,
        string='Donation Number'
    )
    journal_id = fields.Many2one(
        'account.journal',
        string='Payment Method',
        required=True,
        domain=[
            ('type', 'in', ('bank', 'cash')),
            ('allow_donation', '=', True)],
        states={'done': [('readonly', True)]},
        track_visibility='onchange',
        default=lambda self: self.env.user.context_donation_journal_id
    )
    payment_ref = fields.Char(
        string='Payment Reference',
        states={'done': [('readonly', True)]}
    )
    state = fields.Selection([
        ('draft', 'Draft'),
        ('done', 'Done'),
        ('cancel', 'Cancelled')],
        string='State',
        readonly=True,
        copy=False,
        default='draft',
        index=True,
        track_visibility='onchange'
    )
    company_currency_id = fields.Many2one(
        related='company_id.currency_id',
        string="Company Currency",
        readonly=True,
        store=True,
        compute_sudo=True
    )
    campaign_id = fields.Many2one(
        'donation.campaign',
        string='Donation Campaign',
        track_visibility='onchange',
        ondelete='restrict',
        default=lambda self: self.env.user.context_donation_campaign_id
    )
    tax_receipt_id = fields.Many2one(
        'donation.tax.receipt',
        string='Tax Receipt',
        readonly=True,
        copy=False,
        track_visibility='onchange'
    )
    tax_receipt_option = fields.Selection([
        ('none', 'None'),
        ('each', 'For Each Donation'),
        ('annual', 'Annual Tax Receipt')],
        string='Tax Receipt Option',
        states={'done': [('readonly', True)]},
        index=True,
        track_visibility='onchange'
    )
    tax_receipt_total = fields.Monetary(
        compute='_compute_total',
        string='Tax Receipt Eligible Amount',
        store=True,
        readonly=True,
        currency_field='company_currency_id',
        compute_sudo=True,
        help="Eligible Tax Receipt Sub-total in Company Currency"
    )

    def _prepare_each_tax_receipt(self):
        self.ensure_one()
        vals = {
            'company_id': self.company_id.id,
            'currency_id': self.company_currency_id.id,
            'donation_date': self.donation_date,
            'amount': self.tax_receipt_total,
            'type': 'each',
            'partner_id': self.commercial_partner_id.id,
        }
        return vals

    def _prepare_move_line_name(self):
        self.ensure_one()
        name = _('Donation of %s') % self.partner_id.name
        return name

    def _prepare_counterpart_move_line(
            self, name, amount_total_company_cur, total_amount_currency,
            currency_id):
        self.ensure_one()
        precision = self.env['decimal.precision'].precision_get('Account')
        if float_compare(
                amount_total_company_cur, 0, precision_digits=precision) == 1:
            debit = amount_total_company_cur
            credit = 0
            total_amount_currency = self.amount_total
        else:
            credit = amount_total_company_cur * -1
            debit = 0
            total_amount_currency = self.amount_total * -1
        vals = {
            'debit': debit,
            'credit': credit,
            'name': name,
            'account_id': self.journal_id.default_debit_account_id.id,
            'partner_id': self.commercial_partner_id.id,
            'currency_id': currency_id,
            'amount_currency': (
                currency_id and total_amount_currency or 0.0),
        }
        return vals

    def _prepare_donation_move(self):
        self.ensure_one()
        if not self.journal_id.default_debit_account_id:
            raise UserError(
                _("Missing Default Debit Account on journal '%s'.")
                % self.journal_id.name)

        movelines = []
        if self.company_id.currency_id.id != self.currency_id.id:
            currency_id = self.currency_id.id
        else:
            currency_id = False
        # Note : we can have negative donations for donors that use direct
        # debit when their direct debit rejected by the bank
        amount_total_company_cur = 0.0
        total_amount_currency = 0.0
        name = self._prepare_move_line_name()

        aml = {}
        # key = (account_id, analytic_account_id)
        # value = {'credit': ..., 'debit': ..., 'amount_currency': ...}
        precision = self.env['decimal.precision'].precision_get('Account')
        for donation_line in self.line_ids:
            if donation_line.in_kind:
                continue
            amount_total_company_cur += donation_line.amount_company_currency
            account = donation_line.with_context(
                force_company=self.company_id.id).product_id.product_tmpl_id.\
                _get_product_accounts()['income']
            account_id = account.id
            analytic_account_id = donation_line.get_analytic_account_id()
            amount_currency = 0.0
            if float_compare(
                    donation_line.amount_company_currency, 0,
                    precision_digits=precision) == 1:
                credit = donation_line.amount_company_currency
                debit = 0
                amount_currency = donation_line.amount * -1
            else:
                debit = donation_line.amount_company_currency * -1
                credit = 0
                amount_currency = donation_line.amount

            # TODO Take into account the option group_invoice_lines ?
            if (account_id, analytic_account_id) in aml:
                aml[(account_id, analytic_account_id)]['credit'] += credit
                aml[(account_id, analytic_account_id)]['debit'] += debit
                aml[(account_id, analytic_account_id)]['amount_currency'] \
                    += amount_currency
            else:
                aml[(account_id, analytic_account_id)] = {
                    'credit': credit,
                    'debit': debit,
                    'amount_currency': amount_currency,
                    }

        if not aml:  # for full in-kind donation
            return False

        for (account_id, analytic_account_id), content in aml.items():
            movelines.append((0, 0, {
                'name': name,
                'credit': content['credit'],
                'debit': content['debit'],
                'account_id': account_id,
                'analytic_account_id': analytic_account_id,
                'partner_id': self.commercial_partner_id.id,
                'currency_id': currency_id,
                'amount_currency': (
                    currency_id and content['amount_currency'] or 0.0),
                }))

        # counter-part
        ml_vals = self._prepare_counterpart_move_line(
            name, amount_total_company_cur, total_amount_currency,
            currency_id)
        movelines.append((0, 0, ml_vals))

        vals = {
            'journal_id': self.journal_id.id,
            'date': self.donation_date,
            'ref': self.payment_ref,
            'line_ids': movelines,
            }
        return vals

    def validate(self):
        check_total = self.env['res.users'].has_group(
            'donation.group_donation_check_total')
        for donation in self:
            if donation.donation_date > fields.Date.context_today(self):
                raise UserError(_(
                    'The date of the donation of %s should be today '
                    'or in the past, not in the future!')
                    % donation.partner_id.name)            
            if not donation.line_ids:
                raise UserError(_(
                    "Cannot validate the donation of %s because it doesn't "
                    "have any lines!") % donation.partner_id.name)

            if float_is_zero(
                    donation.amount_total,
                    precision_rounding=donation.currency_id.rounding):
                raise UserError(_(
                    "Cannot validate the donation of %s because the "
                    "total amount is 0 !") % donation.partner_id.name)

            if donation.state != 'draft':
                raise UserError(_(
                    "Cannot validate the donation of %s because it is not "
                    "in draft state.") % donation.partner_id.name)

            if check_total and float_compare(
                    donation.check_total, donation.amount_total,
                    precision_rounding=donation.currency_id.rounding):
                raise UserError(_(
                    "The amount of the donation of %s (%s) is different "
                    "from the sum of the donation lines (%s).") % (
                    donation.partner_id.name, donation.check_total,
                    donation.amount_total))

            vals = {'state': 'done'}

            if not float_is_zero(
                    donation.amount_total,
                    precision_rounding=donation.currency_id.rounding):
                move_vals = donation._prepare_donation_move()
                # when we have a full in-kind donation: no account move
                if move_vals:
                    move = self.env['account.move'].create(move_vals)
                    move.post()
                    vals['move_id'] = move.id
                else:
                    donation.message_post(_(
                        'Full in-kind donation: no account move generated'))

            receipt = donation.generate_each_tax_receipt()
            if receipt:
                vals['tax_receipt_id'] = receipt.id

            donation.write(vals)
        return

    def generate_each_tax_receipt(self):
        self.ensure_one()
        receipt = False
        if (
                self.tax_receipt_option == 'each' and
                not self.tax_receipt_id and
                not float_is_zero(
                    self.tax_receipt_total,
                    precision_rounding=self.company_currency_id.rounding)):
            receipt_vals = self._prepare_each_tax_receipt()
            receipt = self.env['donation.tax.receipt'].create(receipt_vals)
        return receipt

    def save_default_values(self):
        self.ensure_one()
        self.env.user.write({
            'context_donation_journal_id': self.journal_id.id,
            'context_donation_campaign_id': self.campaign_id.id,
            })

    def done2cancel(self):
        '''from Done state to Cancel state'''
        for donation in self:
            if donation.tax_receipt_id:
                raise UserError(_(
                    "You cannot cancel this donation because "
                    "it is linked to the tax receipt %s. You should first "
                    "delete this tax receipt (but it may not be legally "
                    "allowed).")
                    % donation.tax_receipt_id.number)
            if donation.move_id:
                donation.move_id.button_cancel()
                donation.move_id.unlink()
            donation.state = 'cancel'

    def cancel2draft(self):
        '''from Cancel state to Draft state'''
        for donation in self:
            if donation.move_id:
                raise UserError(_(
                    "A cancelled donation should not be linked to "
                    "an account move"))
            if donation.tax_receipt_id:
                raise UserError(_(
                    "A cancelled donation should not be linked to "
                    "a tax receipt"))
            donation.state = 'draft'

    def unlink(self):
        for donation in self:
            if donation.state == 'done':
                raise UserError(_(
                    "The donation '%s' is in Done state, so you cannot "
                    "delete it.") % donation.display_name)
            if donation.move_id:
                raise UserError(_(
                    "The donation '%s' is linked to an account move, "
                    "so you cannot delete it.") % donation.display_name)
            if donation.tax_receipt_id:
                raise UserError(_(
                    "The donation '%s' is linked to the tax receipt %s, "
                    "so you cannot delete it.")
                    % (donation.display_name, donation.tax_receipt_id.number))
        return super(DonationDonation, self).unlink()

    def name_get(self):
        res = []
        for donation in self:
            partner = donation.sudo().partner_id
            if donation.state == 'draft':
                name = _('Draft Donation of %s') % partner.name
            elif donation.state == 'cancel':
                name = _('Cancelled Donation of %s') % partner.name
            else:
                name = donation.number
            res.append((donation.id, name))
        return res

    @api.onchange('partner_id')
    def partner_id_change(self):
        if self.partner_id:
            self.tax_receipt_option = self.partner_id.tax_receipt_option

    @api.onchange('tax_receipt_option')
    def tax_receipt_option_change(self):
        res = {}
        if (
                self.partner_id and
                self.partner_id.tax_receipt_option == 'annual' and
                self.tax_receipt_option != 'annual'):
            res = {
                'warning': {
                    'title': _('Error:'),
                    'message':
                    _('You cannot change the Tax Receipt '
                        'Option when it is Annual.'),
                    },
                }
            self.tax_receipt_option = 'annual'
        return res

    @api.model
    def auto_install_l10n(self):
        """Helper function for calling a method that is not accessible directly
        from XML data.
        """
        _auto_install_l10n(self.env.cr, None)
Ejemplo n.º 6
0
class FleetVehicleLogServices(models.Model):
    _name = 'fleet.vehicle.log.services'
    _inherit = ['mail.thread', 'mail.activity.mixin']
    _rec_name = 'service_type_id'
    _description = 'Services for vehicles'

    active = fields.Boolean(default=True)
    vehicle_id = fields.Many2one('fleet.vehicle',
                                 'Vehicle',
                                 required=True,
                                 help='Vehicle concerned by this log')
    amount = fields.Monetary('Cost')
    description = fields.Char('Description')
    odometer_id = fields.Many2one(
        'fleet.vehicle.odometer',
        'Odometer',
        help='Odometer measure of the vehicle at the moment of this log')
    odometer = fields.Float(
        compute="_get_odometer",
        inverse='_set_odometer',
        string='Odometer Value',
        help='Odometer measure of the vehicle at the moment of this log')
    odometer_unit = fields.Selection(related='vehicle_id.odometer_unit',
                                     string="Unit",
                                     readonly=True)
    date = fields.Date(help='Date when the cost has been executed',
                       default=fields.Date.context_today)
    company_id = fields.Many2one('res.company',
                                 'Company',
                                 default=lambda self: self.env.company)
    currency_id = fields.Many2one('res.currency',
                                  related='company_id.currency_id')
    purchaser_id = fields.Many2one('res.partner',
                                   string="Driver",
                                   compute='_compute_purchaser_id',
                                   readonly=False,
                                   store=True)
    inv_ref = fields.Char('Vendor Reference')
    vendor_id = fields.Many2one('res.partner', 'Vendor')
    notes = fields.Text()
    service_type_id = fields.Many2one(
        'fleet.service.type',
        'Service Type',
        required=True,
        default=lambda self: self.env.ref('fleet.type_service_service_8',
                                          raise_if_not_found=False),
    )
    state = fields.Selection([
        ('todo', 'To Do'),
        ('running', 'Running'),
        ('done', 'Done'),
        ('cancelled', 'Cancelled'),
    ],
                             default='todo',
                             string='Stage')

    def _get_odometer(self):
        self.odometer = 0
        for record in self:
            if record.odometer_id:
                record.odometer = record.odometer_id.value

    def _set_odometer(self):
        for record in self:
            if not record.odometer:
                raise UserError(
                    _('Emptying the odometer value of a vehicle is not allowed.'
                      ))
            odometer = self.env['fleet.vehicle.odometer'].create({
                'value':
                record.odometer,
                'date':
                record.date or fields.Date.context_today(record),
                'vehicle_id':
                record.vehicle_id.id
            })
            self.odometer_id = odometer

    @api.model_create_multi
    def create(self, vals_list):
        for data in vals_list:
            if 'odometer' in data and not data['odometer']:
                # if received value for odometer is 0, then remove it from the
                # data as it would result to the creation of a
                # odometer log with 0, which is to be avoided
                del data['odometer']
        return super(FleetVehicleLogServices, self).create(vals_list)

    @api.depends('vehicle_id')
    def _compute_purchaser_id(self):
        for service in self:
            service.purchaser_id = service.vehicle_id.driver_id
class HrEmployeePrivate(models.Model):
    """
    NB: Any field only available on the model hr.employee (i.e. not on the
    hr.employee.public model) should have `groups="hr.group_hr_user"` on its
    definition to avoid being prefetched when the user hasn't access to the
    hr.employee model. Indeed, the prefetch loads the data for all the fields
    that are available according to the group defined on them.
    """
    _name = "hr.employee"
    _description = "Employee"
    _order = 'name'
    _inherit = [
        'hr.employee.base', 'mail.thread', 'mail.activity.mixin',
        'resource.mixin', 'image.mixin'
    ]
    _mail_post_access = 'read'

    @api.model
    def _default_image(self):
        image_path = get_module_resource('hr', 'static/src/img',
                                         'default_image.png')
        return base64.b64encode(open(image_path, 'rb').read())

    # resource and user
    # required on the resource, make sure required="True" set in the view
    name = fields.Char(string="Employee Name",
                       related='resource_id.name',
                       store=True,
                       readonly=False,
                       tracking=True)
    user_id = fields.Many2one('res.users',
                              'User',
                              related='resource_id.user_id',
                              store=True,
                              readonly=False)
    user_partner_id = fields.Many2one(related='user_id.partner_id',
                                      related_sudo=False,
                                      string="User's partner")
    active = fields.Boolean('Active',
                            related='resource_id.active',
                            default=True,
                            store=True,
                            readonly=False)
    company_id = fields.Many2one('res.company', required=True)
    # private partner
    address_home_id = fields.Many2one(
        'res.partner',
        'Address',
        help=
        'Enter here the private address of the employee, not the one linked to your company.',
        groups="hr.group_hr_user",
        tracking=True,
        domain=
        "['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
    is_address_home_a_company = fields.Boolean(
        'The employee address has a company linked',
        compute='_compute_is_address_home_a_company',
    )
    private_email = fields.Char(related='address_home_id.email',
                                string="Private Email",
                                groups="hr.group_hr_user")
    country_id = fields.Many2one('res.country',
                                 'Nationality (Country)',
                                 groups="hr.group_hr_user",
                                 tracking=True)
    gender = fields.Selection([('male', 'Male'), ('female', 'Female'),
                               ('other', 'Other')],
                              groups="hr.group_hr_user",
                              tracking=True)
    marital = fields.Selection([('single', 'Single'), ('married', 'Married'),
                                ('cohabitant', 'Legal Cohabitant'),
                                ('widower', 'Widower'),
                                ('divorced', 'Divorced')],
                               string='Marital Status',
                               groups="hr.group_hr_user",
                               default='single',
                               tracking=True)
    spouse_complete_name = fields.Char(string="Spouse Complete Name",
                                       groups="hr.group_hr_user",
                                       tracking=True)
    spouse_birthdate = fields.Date(string="Spouse Birthdate",
                                   groups="hr.group_hr_user",
                                   tracking=True)
    children = fields.Integer(string='Number of Children',
                              groups="hr.group_hr_user",
                              tracking=True)
    place_of_birth = fields.Char('Place of Birth',
                                 groups="hr.group_hr_user",
                                 tracking=True)
    country_of_birth = fields.Many2one('res.country',
                                       string="Country of Birth",
                                       groups="hr.group_hr_user",
                                       tracking=True)
    birthday = fields.Date('Date of Birth',
                           groups="hr.group_hr_user",
                           tracking=True)
    ssnid = fields.Char('SSN No',
                        help='Social Security Number',
                        groups="hr.group_hr_user",
                        tracking=True)
    sinid = fields.Char('SIN No',
                        help='Social Insurance Number',
                        groups="hr.group_hr_user",
                        tracking=True)
    identification_id = fields.Char(string='Identification No',
                                    groups="hr.group_hr_user",
                                    tracking=True)
    passport_id = fields.Char('Passport No',
                              groups="hr.group_hr_user",
                              tracking=True)
    bank_account_id = fields.Many2one(
        'res.partner.bank',
        'Bank Account Number',
        domain=
        "[('partner_id', '=', address_home_id), '|', ('company_id', '=', False), ('company_id', '=', company_id)]",
        groups="hr.group_hr_user",
        tracking=True,
        help='Employee bank salary account')
    permit_no = fields.Char('Work Permit No',
                            groups="hr.group_hr_user",
                            tracking=True)
    visa_no = fields.Char('Visa No', groups="hr.group_hr_user", tracking=True)
    visa_expire = fields.Date('Visa Expire Date',
                              groups="hr.group_hr_user",
                              tracking=True)
    additional_note = fields.Text(string='Additional Note',
                                  groups="hr.group_hr_user",
                                  tracking=True)
    certificate = fields.Selection([
        ('graduate', 'Graduate'),
        ('bachelor', 'Bachelor'),
        ('master', 'Master'),
        ('doctor', 'Doctor'),
        ('other', 'Other'),
    ],
                                   'Certificate Level',
                                   default='other',
                                   groups="hr.group_hr_user",
                                   tracking=True)
    study_field = fields.Char("Field of Study",
                              groups="hr.group_hr_user",
                              tracking=True)
    study_school = fields.Char("School",
                               groups="hr.group_hr_user",
                               tracking=True)
    emergency_contact = fields.Char("Emergency Contact",
                                    groups="hr.group_hr_user",
                                    tracking=True)
    emergency_phone = fields.Char("Emergency Phone",
                                  groups="hr.group_hr_user",
                                  tracking=True)
    km_home_work = fields.Integer(string="Home-Work Distance",
                                  groups="hr.group_hr_user",
                                  tracking=True)

    image_1920 = fields.Image(default=_default_image)
    phone = fields.Char(related='address_home_id.phone',
                        related_sudo=False,
                        readonly=False,
                        string="Private Phone",
                        groups="hr.group_hr_user")
    # employee in company
    child_ids = fields.One2many('hr.employee',
                                'parent_id',
                                string='Direct subordinates')
    category_ids = fields.Many2many('hr.employee.category',
                                    'employee_category_rel',
                                    'emp_id',
                                    'category_id',
                                    groups="hr.group_hr_manager",
                                    string='Tags')
    # misc
    notes = fields.Text('Notes', groups="hr.group_hr_user")
    color = fields.Integer('Color Index', default=0, groups="hr.group_hr_user")
    barcode = fields.Char(string="Badge ID",
                          help="ID used for employee identification.",
                          groups="hr.group_hr_user",
                          copy=False)
    pin = fields.Char(
        string="PIN",
        groups="hr.group_hr_user",
        copy=False,
        help=
        "PIN used to Check In/Out in Kiosk Mode (if enabled in Configuration)."
    )
    departure_reason = fields.Selection([('fired', 'Fired'),
                                         ('resigned', 'Resigned'),
                                         ('retired', 'Retired')],
                                        string="Departure Reason",
                                        groups="hr.group_hr_user",
                                        copy=False,
                                        tracking=True)
    departure_description = fields.Text(string="Additional Information",
                                        groups="hr.group_hr_user",
                                        copy=False,
                                        tracking=True)
    departure_date = fields.Date(string="Departure Date",
                                 groups="hr.group_hr_user",
                                 copy=False,
                                 tracking=True)
    message_main_attachment_id = fields.Many2one(groups="hr.group_hr_user")

    _sql_constraints = [
        ('barcode_uniq', 'unique (barcode)',
         "The Badge ID must be unique, this one is already assigned to another employee."
         ),
        ('user_uniq', 'unique (user_id, company_id)',
         "A user cannot be linked to multiple employees in the same company.")
    ]

    def name_get(self):
        if self.check_access_rights('read', raise_exception=False):
            return super(HrEmployeePrivate, self).name_get()
        return self.env['hr.employee.public'].browse(self.ids).name_get()

    def _read(self, fields):
        if self.check_access_rights('read', raise_exception=False):
            return super(HrEmployeePrivate, self)._read(fields)

        res = self.env['hr.employee.public'].browse(self.ids).read(fields)
        for r in res:
            record = self.browse(r['id'])
            record._update_cache({k: v
                                  for k, v in r.items() if k in fields},
                                 validate=False)

    def read(self, fields, load='_classic_read'):
        if self.check_access_rights('read', raise_exception=False):
            return super(HrEmployeePrivate, self).read(fields, load=load)
        private_fields = set(fields).difference(
            self.env['hr.employee.public']._fields.keys())
        if private_fields:
            raise AccessError(
                _('The fields "%s" you try to read is not available on the public employee profile.'
                  ) % (','.join(private_fields)))
        return self.env['hr.employee.public'].browse(self.ids).read(fields,
                                                                    load=load)

    @api.model
    def load_views(self, views, options=None):
        if self.check_access_rights('read', raise_exception=False):
            return super(HrEmployeePrivate, self).load_views(views,
                                                             options=options)
        return self.env['hr.employee.public'].load_views(views,
                                                         options=options)

    @api.model
    def _search(self,
                args,
                offset=0,
                limit=None,
                order=None,
                count=False,
                access_rights_uid=None):
        """
            We override the _search because it is the method that checks the access rights
            This is correct to override the _search. That way we enforce the fact that calling
            search on an hr.employee returns a hr.employee recordset, even if you don't have access
            to this model, as the result of _search (the ids of the public employees) is to be
            browsed on the hr.employee model. This can be trusted as the ids of the public
            employees exactly match the ids of the related hr.employee.
        """
        if self.check_access_rights('read', raise_exception=False):
            return super(HrEmployeePrivate,
                         self)._search(args,
                                       offset=offset,
                                       limit=limit,
                                       order=order,
                                       count=count,
                                       access_rights_uid=access_rights_uid)
        ids = self.env['hr.employee.public']._search(
            args,
            offset=offset,
            limit=limit,
            order=order,
            count=count,
            access_rights_uid=access_rights_uid)
        if not count and isinstance(ids, Query):
            # the result is expected from this table, so we should link tables
            ids = super(HrEmployeePrivate,
                        self.sudo())._search([('id', 'in', ids)])
        return ids

    def get_formview_id(self, access_uid=None):
        """ Override this method in order to redirect many2one towards the right model depending on access_uid """
        if access_uid:
            self_sudo = self.with_user(access_uid)
        else:
            self_sudo = self

        if self_sudo.check_access_rights('read', raise_exception=False):
            return super(HrEmployeePrivate,
                         self).get_formview_id(access_uid=access_uid)
        # Hardcode the form view for public employee
        return self.env.ref('hr.hr_employee_public_view_form').id

    def get_formview_action(self, access_uid=None):
        """ Override this method in order to redirect many2one towards the right model depending on access_uid """
        res = super(HrEmployeePrivate,
                    self).get_formview_action(access_uid=access_uid)
        if access_uid:
            self_sudo = self.with_user(access_uid)
        else:
            self_sudo = self

        if not self_sudo.check_access_rights('read', raise_exception=False):
            res['res_model'] = 'hr.employee.public'

        return res

    @api.constrains('pin')
    def _verify_pin(self):
        for employee in self:
            if employee.pin and not employee.pin.isdigit():
                raise ValidationError(
                    _("The PIN must be a sequence of digits."))

    @api.onchange('user_id')
    def _onchange_user(self):
        if self.user_id:
            self.update(self._sync_user(self.user_id, bool(self.image_1920)))
            if not self.name:
                self.name = self.user_id.name

    @api.onchange('resource_calendar_id')
    def _onchange_timezone(self):
        if self.resource_calendar_id and not self.tz:
            self.tz = self.resource_calendar_id.tz

    def _sync_user(self, user, employee_has_image=False):
        vals = dict(
            work_email=user.email,
            user_id=user.id,
        )
        if not employee_has_image:
            vals['image_1920'] = user.image_1920
        if user.tz:
            vals['tz'] = user.tz
        return vals

    @api.model
    def create(self, vals):
        if vals.get('user_id'):
            user = self.env['res.users'].browse(vals['user_id'])
            vals.update(
                self._sync_user(
                    user,
                    vals.get('image_1920') == self._default_image()))
            vals['name'] = vals.get('name', user.name)
        employee = super(HrEmployeePrivate, self).create(vals)
        url = '/web#%s' % url_encode(
            {
                'action': 'hr.plan_wizard_action',
                'active_id': employee.id,
                'active_model': 'hr.employee',
                'menu_id': self.env.ref('hr.menu_hr_root').id,
            })
        employee._message_log(body=_(
            '<b>Congratulations!</b> May I recommend you to setup an <a href="%s">onboarding plan?</a>'
        ) % (url))
        if employee.department_id:
            self.env['mail.channel'].sudo().search([
                ('subscription_department_ids', 'in',
                 employee.department_id.id)
            ])._subscribe_users()
        return employee

    def write(self, vals):
        if 'address_home_id' in vals:
            account_id = vals.get('bank_account_id') or self.bank_account_id.id
            if account_id:
                self.env['res.partner.bank'].browse(
                    account_id).partner_id = vals['address_home_id']
        if vals.get('user_id'):
            # Update the profile pictures with user, except if provided
            vals.update(
                self._sync_user(self.env['res.users'].browse(vals['user_id']),
                                bool(vals.get('image_1920'))))
        res = super(HrEmployeePrivate, self).write(vals)
        if vals.get('department_id') or vals.get('user_id'):
            department_id = vals['department_id'] if vals.get(
                'department_id') else self[:1].department_id.id
            # When added to a department or changing user, subscribe to the channels auto-subscribed by department
            self.env['mail.channel'].sudo().search([
                ('subscription_department_ids', 'in', department_id)
            ])._subscribe_users()
        return res

    def unlink(self):
        resources = self.mapped('resource_id')
        super(HrEmployeePrivate, self).unlink()
        return resources.unlink()

    def toggle_active(self):
        res = super(HrEmployeePrivate, self).toggle_active()
        unarchived_employees = self.filtered(lambda employee: employee.active)
        unarchived_employees.write({
            'departure_reason': False,
            'departure_description': False,
            'departure_date': False
        })
        archived_addresses = unarchived_employees.mapped(
            'address_home_id').filtered(lambda addr: not addr.active)
        archived_addresses.toggle_active()
        if len(self) == 1 and not self.active:
            return {
                'type': 'ir.actions.act_window',
                'name': _('Register Departure'),
                'res_model': 'hr.departure.wizard',
                'view_mode': 'form',
                'target': 'new',
                'context': {
                    'active_id': self.id
                },
                'views': [[False, 'form']]
            }
        return res

    def generate_random_barcode(self):
        for employee in self:
            employee.barcode = '041' + "".join(
                choice(digits) for i in range(9))

    @api.depends('address_home_id.parent_id')
    def _compute_is_address_home_a_company(self):
        """Checks that chosen address (res.partner) is not linked to a company.
        """
        for employee in self:
            try:
                employee.is_address_home_a_company = employee.address_home_id.parent_id.id is not False
            except AccessError:
                employee.is_address_home_a_company = False

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

    @api.model
    def get_import_templates(self):
        return [{
            'label': _('Import Template for Employees'),
            'template': '/hr/static/xls/hr_employee.xls'
        }]

    def _post_author(self):
        """
        When a user updates his own employee's data, all operations are performed
        by super user. However, tracking messages should not be posted as OdooBot
        but as the actual user.
        This method is used in the overrides of `_message_log` and `message_post`
        to post messages as the correct user.
        """
        real_user = self.env.context.get('binary_field_real_user')
        if self.env.is_superuser() and real_user:
            self = self.with_user(real_user)
        return self

    # ---------------------------------------------------------
    # Messaging
    # ---------------------------------------------------------

    def _message_log(self, **kwargs):
        return super(HrEmployeePrivate,
                     self._post_author())._message_log(**kwargs)

    @api.returns('mail.message', lambda value: value.id)
    def message_post(self, **kwargs):
        return super(HrEmployeePrivate,
                     self._post_author()).message_post(**kwargs)

    def _sms_get_partner_fields(self):
        return ['user_partner_id']

    def _sms_get_number_fields(self):
        return ['mobile_phone']
Ejemplo n.º 8
0
class ResPartner(models.Model):
    _inherit = 'res.partner'

    ar_qt_samples = fields.Date(
        compute='_compute_ar_qt_samples',
        string='Muestras',
    )
    ar_qt_profession = fields.Char(string='Profesión', size=35)
    # ar_qt_questionnaire_todocesped_show
    ar_qt_questionnaire_todocesped_show = fields.Boolean(
        compute='_compute_ar_qt_questionnaire_todocesped_show',
        store=False,
    )

    @api.multi
    def _compute_ar_qt_questionnaire_todocesped_show(self):
        for item in self:
            item.ar_qt_questionnaire_todocesped_show = False
            if item.customer and (item.ar_qt_activity_type == 'todocesped'
                                  or item.ar_qt_activity_type == 'evert'
                                  or item.ar_qt_activity_type == 'both'):
                item.ar_qt_questionnaire_todocesped_show = True

    # ar_qt_questionnaire_arelux_show
    ar_qt_questionnaire_arelux_show = fields.Boolean(
        compute='_compute_ar_qt_questionnaire_arelux_show',
        store=False,
    )

    @api.multi
    def _compute_ar_qt_questionnaire_arelux_show(self):
        for item in self:
            item.ar_qt_questionnaire_arelux_show = False
            if item.customer and item.ar_qt_activity_type == 'arelux':
                item.ar_qt_questionnaire_arelux_show = True

    ar_qt_customer_type = fields.Selection([
        ('particular', 'Particular'),
        ('profesional', 'Profesional'),
    ],
                                           size=15,
                                           string='Tipo de cliente',
                                           default='particular')

    @api.onchange('ar_qt_customer_type')
    def change_ar_qt_customer_type(self):
        # Todocesped
        self.ar_qt_todocesped_interest_product_1 = 0
        self.ar_qt_todocesped_interest_product_2 = 0
        self.ar_qt_todocesped_interest_product_3 = 0
        self.ar_qt_todocesped_interest_product_4 = 0
        self.ar_qt_todocesped_interest_product_all = False
        self.ar_qt_todocesped_interest_products_not_yet = False

        self.ar_qt_todocesped_contact_form = []
        self.ar_qt_todocesped_contact_form_other = ''

        self.ar_qt_todocesped_is_recommendation = False
        self.ar_qt_todocesped_recommendation_partner_id = 0

        self.ar_qt_todocesped_pr_where_install = ''
        self.ar_qt_todocesped_pr_where_install_other = ''
        self.ar_qt_todocesped_pr_budget_instalation = False
        self.ar_qt_todocesped_pr_type_surface = []
        self.ar_qt_todocesped_pr_type_surface_other = ''
        self.ar_qt_todocesped_pr_specific_segment = []
        self.ar_qt_todocesped_pr_specific_segment_other = ''
        self.ar_qt_todocesped_pr_why_install_it = []
        self.ar_qt_todocesped_pr_why_install_it_other = ''
        self.ar_qt_todocesped_pr_who_values_more = []
        self.ar_qt_todocesped_pr_who_values_more_other = ''

        self.ar_qt_todocesped_pf_customer_type = ''
        self.ar_qt_todocesped_pf_customer_type_other = ''
        self.ar_qt_todocesped_pf_install_artificial_grass = False
        self.ar_qt_todocesped_pf_type_customers_sale = []
        self.ar_qt_todocesped_pf_stock_capacity = []
        self.ar_qt_todocesped_pf_valuation_thing = []
        self.ar_qt_todocesped_pf_valuation_thing_other = ''
        # Arelux
        self.ar_qt_arelux_interest_product_1 = 0
        self.ar_qt_arelux_interest_product_2 = 0
        self.ar_qt_arelux_interest_product_3 = 0
        self.ar_qt_arelux_interest_product_4 = 0
        self.ar_qt_arelux_interest_product_all = False

        self.ar_qt_arelux_interest_product_not_yet = False
        self.ar_qt_arelux_valuation_thing = []
        self.ar_qt_arelux_valuation_thing_other = ''

        self.ar_qt_arelux_contact_form = []
        self.ar_qt_arelux_contact_form_other = ''

        self.ar_qt_arelux_is_recommendation = False
        self.ar_qt_arelux_recommendation_partner_id = 0

        self.ar_qt_arelux_pr_ql_product = []
        self.ar_qt_arelux_pr_ql_product_waterproofing = ''
        self.ar_qt_arelux_pr_ql_product_waterproofing_other = ''
        self.ar_qt_arelux_pr_ql_product_thermal_paints = ''
        self.ar_qt_arelux_pr_ql_product_thermal_paints_other = ''
        self.ar_qt_arelux_pr_ql_product_reflective_insulators = ''
        self.ar_qt_arelux_pr_ql_product_reflective_insulators_other = ''
        self.ar_qt_arelux_pr_ql_product_surface_treatment = ''
        self.ar_qt_arelux_pr_ql_product_surface_treatment_other = ''
        self.ar_qt_arelux_pr_insall_the_same = False
        self.ar_qt_arelux_pr_reason_buy = []
        self.ar_qt_arelux_pr_reason_buy_other = ''

        self.ar_qt_arelux_pf_customer_type = ''
        self.ar_qt_arelux_pf_customer_type_other = ''
        self.ar_qt_arelux_pf_install = False
        self.ar_qt_arelux_pf_type_customers_sale = []
        self.ar_qt_arelux_pf_stock_capacity = []

    ar_qt_activity_type = fields.Selection([
        ('todocesped', 'Todocesped'),
        ('arelux', 'Arelux'),
        ('evert', 'Evert'),
        ('both', 'Ambos'),
    ],
                                           size=15,
                                           string='Tipo de actividad',
                                           default='todocesped')

    @api.multi
    @api.onchange('ar_qt_activity_type', 'customer')
    def change_ar_qt_activity_type(self):
        for item in self:
            item._get_ar_qt_questionnaire_todocesped_show()
            item._get_ar_qt_questionnaire_arelux_show()

    is_potential_advertise_oniad = fields.Boolean(
        string="Potencial para OniAd")
    # Profesional
    ar_qt_pf_frequency_customer_type = fields.Selection(
        [
            ('none', 'Cliente sin ventas'),
            ('puntual', 'Cliente puntual'),
            ('loyalized', 'Cliente fidelizado'),
            ('recurrent', 'Cliente recurrente'),
        ],
        size=15,
        string='Tipo de cliente (frecuencia)',
        default='puntual')

    ar_qt_pf_sale_customer_type = fields.Selection(
        [
            ('bronze', 'Cliente bronce'),
            ('silver', 'Cliente plata'),
            ('gold', 'Cliente oro'),
            ('diamond', 'Cliente diamante'),
        ],
        size=15,
        string='Tipo de cliente (ventas)',
        default='bronze')

    @api.multi
    def _compute_ar_qt_samples(self):
        for item in self:
            order_ids = self.env['sale.order'].search([
                ('state', 'in', ('sale', 'done')), ('partner_id', '=', item.id)
            ])
            if order_ids:
                for order_id in order_ids:
                    for order_line in order_id.order_line:
                        if order_line.product_id.id == 97:
                            item.ar_qt_samples = order_id.confirmation_date

    @api.model
    def create(self, values):
        record = super(ResPartner, self).create(values)
        # operations
        if record.parent_id:
            if record.parent_id.ar_qt_activity_type:
                record.ar_qt_activity_type = record.parent_id.ar_qt_activity_type

            if record.parent_id.ar_qt_customer_type:
                record.ar_qt_customer_type = record.parent_id.ar_qt_customer_type
        # return
        return record

    @api.model
    def cron_action_generate_customer_type(self):
        current_date = datetime.today()
        start_date = current_date + relativedelta(months=-12, day=1)
        end_date = datetime(current_date.year, start_date.month,
                            1) + relativedelta(months=1, days=-1)
        partner_ids = self.env['res.partner'].search([('customer', '=', True),
                                                      ('active', '=', True)])
        if partner_ids:
            partner_ids_real = partner_ids.mapped('id')
            sale_order_ids = self.env['sale.order'].search([
                ('state', 'in', ('sale', 'done')),
                ('partner_id', 'in', partner_ids_real),
                ('amount_total', '>', 0),
                ('confirmation_date', '>=', start_date.strftime("%Y-%m-%d")),
                ('confirmation_date', '<=', end_date.strftime("%Y-%m-%d"))
            ])
            if sale_order_ids:
                for partner_id in partner_ids:
                    # default
                    ar_qt_pf_frequency_customer_type_item = 'none'
                    ar_qt_pf_sale_customer_type_item = 'bronze'
                    # filter
                    sale_order_items = filter(
                        lambda x: x['partner_id'] == partner_id,
                        sale_order_ids)
                    if len(sale_order_items) > 0:
                        # ar_qt_pf_frequency_customer_type_item
                        sale_order_ids_total = len(sale_order_items)
                        if sale_order_ids_total >= 1 \
                                and sale_order_ids_total <= 2:
                            ar_qt_pf_frequency_customer_type_item = 'puntual'
                        elif sale_order_ids_total > 2 \
                                and sale_order_ids_total <= 5:
                            ar_qt_pf_frequency_customer_type_item = 'loyalized'
                        elif sale_order_ids_total >= 6:
                            ar_qt_pf_frequency_customer_type_item = 'recurrent'
                        # ar_qt_pf_sale_customer_type_item
                        amount_totals = map(lambda x: x['amount_total'],
                                            sale_order_items)
                        partner_amount_total = sum(map(float, amount_totals))

                        if partner_amount_total >= 6000 \
                                and partner_amount_total <= 20000:
                            ar_qt_pf_sale_customer_type_item = 'silver'
                        elif partner_amount_total > 20000 \
                                and partner_amount_total <= 40000:
                            ar_qt_pf_sale_customer_type_item = 'gold'
                        elif partner_amount_total > 40000:
                            ar_qt_pf_sale_customer_type_item = 'diamond'
                    # update
                    partner_id.ar_qt_pf_frequency_customer_type = \
                        ar_qt_pf_frequency_customer_type_item
                    partner_id.ar_qt_pf_sale_customer_type = \
                        ar_qt_pf_sale_customer_type_item
Ejemplo n.º 9
0
class SaleOrder(models.Model):

    _inherit = 'sale.order'

    product_category_id = fields.Many2one(
        'product.category',
        string='Business Line',
        domain="[('is_business_line','=',True)]")

    company_id = fields.Many2one(
        default=lambda self: self.env.ref('vcls-hr.company_VCFR'))

    business_mode = fields.Selection(
        [
            #('t_and_m', 'T&M'),
            #('fixed_price', 'Fixed Price'),
            ('all', 'All'),
            ('services', 'Services'),
            ('rates', 'Rates'),
            ('subscriptions', 'Subscriptions'),
        ],
        default='all',
        string="Product Type")

    deliverable_id = fields.Many2one('product.deliverable',
                                     string="Deliverable")
    expected_start_date = fields.Date()
    expected_end_date = fields.Date()

    scope_of_work = fields.Html(string="Scope of Work")

    user_id = fields.Many2one(
        'res.users',
        track_visibility='onchange',
        domain=lambda self: [("groups_id", "=", self.env['res.groups'].search(
            [('name', '=', 'Account Manager')]).id)])

    #We never use several projects per quotation, so we highlight the 1st of the list
    project_id = fields.Many2one(
        'project.project',
        string='Project Name',
        compute='_compute_project_id',
        store=True,
    )

    parent_id = fields.Many2one(
        'sale.order',
        string="Parent Quotation",
        copy=True,
    )

    internal_ref = fields.Char(
        string="Ref",
        store=True,
    )

    name = fields.Char(string='Order Reference',
                       required=True,
                       copy=False,
                       readonly=True,
                       states={'draft': [('readonly', False)]},
                       index=True,
                       default=lambda self: 'New')

    ###############
    # ORM METHODS #
    ###############

    @api.model
    def create(self, vals):
        #if related to an opportunity
        if 'opportunity_id' in vals:
            opp_id = vals.get('opportunity_id')
            opp = self.env['crm.lead'].browse(opp_id)

            if 'parent_id' in vals:  #in this case, we are upselling and add a numerical index to the reference of the original quotation
                parent_id = vals.get('parent_id')
                parent = self.env['sale.order'].browse(parent_id)
                other_childs = self.sudo().with_context(
                    active_test=False).search([('opportunity_id', '=', opp_id),
                                               ('parent_id', '=', parent_id)])
                if other_childs:  #this is not the 1st upsell
                    index = len(other_childs) + 1
                else:  #this is the 1st upsell
                    index = 1
                vals['internal_ref'] = "{}.{}".format(parent.internal_ref,
                                                      index)

            else:  #in this case, we add an alpha index to the reference of the opp
                prev_quote = self.sudo().with_context(
                    active_test=False).search([('opportunity_id', '=', opp_id),
                                               ('parent_id', '=', False)])
                if prev_quote:
                    index = len(prev_quote) + 1
                else:
                    index = 1
                vals['internal_ref'] = "{}-{}".format(
                    opp.internal_ref, self.get_alpha_index(index))

            vals['name'] = "{} | {}".format(vals['internal_ref'], vals['name'])

            #default expected_start_date and expected_end_date
            expected_start_date = opp.expected_start_date
            if expected_start_date:
                vals['expected_start_date'] = expected_start_date
                vals[
                    'expected_end_date'] = expected_start_date + relativedelta(
                        months=+3)

        result = super(SaleOrder, self).create(vals)
        return result

    @api.model
    def write(self, vals):
        if 'expected_start_date' in vals:
            expected_start_date = fields.Date.from_string(
                vals['expected_start_date'])
            if self.expected_end_date and self.expected_start_date and expected_start_date:
                vals['expected_end_date'] = expected_start_date + (
                    self.expected_end_date - self.expected_start_date)
            if self.tasks_ids:
                for task_id in self.tasks_ids:
                    forecasts = self.env['project.forecast'].search([
                        ('task_id', '=', task_id.id)
                    ])
                    for forecast in forecasts:
                        forecast.write(
                            {'start_date': vals['expected_start_date']})

        return super(SaleOrder, self).write(vals)

    ###################
    # COMPUTE METHODS #
    ###################

    @api.depends('project_ids')
    def _compute_project_id(self):
        for so in self:
            if so.parent_id.project_id:
                so.project_id = so.parent_id.project_id
            elif so.project_ids:
                so.project_id = so.project_ids[0]

    ################
    # TOOL METHODS #
    ################

    @api.multi
    def upsell(self):
        for rec in self:
            new_order = rec.copy({'order_line': False, 'parent_id': rec.id})
            """
            pending_section = None

            #we loop in source lines to copy rate ones only
            for line in rec.order_line:
                if line.display_type == 'line_section':
                    pending_section = line
                    continue
                elif line.product_id.type == 'service' and \
                    line.product_id.service_policy == 'delivered_timesheet' and \
                    line.product_id.service_tracking in ('no', 'project_only'):
                    if pending_section:
                        pending_section.copy({'order_id': new_order.id})
                        pending_section = None
                    line.copy({'order_id': new_order.id,
                               'project_id': line.project_id.id,
                               'analytic_line_ids': [(6, 0, line.analytic_line_ids.ids)]})"""
        return {
            'type': 'ir.actions.act_window',
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'sale.order',
            'target': 'current',
            'res_id': new_order.id,
        }

    """ @api.multi
    def copy_data(self, default=None):
        default['name']="I DO TEST"
        return super(SaleOrder, self).copy_data(default)"""

    @api.model
    def get_alpha_index(self, index):
        map = {
            1: 'A',
            2: 'B',
            3: 'C',
            4: 'D',
            5: 'E',
            6: 'F',
            7: 'G',
            8: 'H',
            9: 'I',
            10: 'J',
            11: 'K',
            12: 'L',
            13: 'M',
            14: 'O',
            15: 'P',
            16: 'Q',
            17: 'R',
            18: 'S',
            19: 'T',
            20: 'U',
            21: 'V',
            22: 'W',
            23: 'X',
            24: 'Y',
            26: 'Z'
        }

        suffix = ""
        for i in range(math.ceil(index / 26)):
            key = index - i * 26
            suffix += "{}".format(map[key % 26])

        # need to add scope
        return suffix
Ejemplo n.º 10
0
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),
                                        ('category_id.name', 'ilike',
                                         "Teacher")
                                    ])

    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)

    @api.depends('seats', 'attendee_ids')
    def _taken_seats(self):
        for r in self:
            if not r.seats:
                r.taken_seats = 0.0
            else:
                r.taken_seats = 100.0 * len(r.attendee_ids) / r.seats

    @api.depends('start_date', 'duration')
    def _get_end_date(self):
        for r in self:
            if not (r.start_date and r.duration):
                r.end_date = r.start_date
                continue

            # Add duration to start_date, but: Monday + 5 days = Saturday, so
            # subtract one second to get on Friday instead
            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.onchange('seats', 'attendee_ids')
    def _verify_valid_seats(self):
        if self.seats < 0:
            return {
                'warning': {
                    'title': _("Too many attendees"),
                    'message': _("Increase seats or remove excess attendees"),
                },
            }
        if self.seats < len(self.attendee_ids):
            return {
                'warning': {
                    'title': "Too many attendees",
                    'message': "Increase seats or remove excess attendees",
                },
            }

    @api.constrains('instructor_id', 'attendee_ids')
    def _check_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"))
Ejemplo n.º 11
0
class BuyAdjust(models.Model):
    _name = "buy.adjust"
    _inherit = ['mail.thread']
    _description = u"采购变更单"
    _order = 'date desc, id desc'

    name = fields.Char(u'单据编号', copy=False, help=u'变更单编号,保存时可自动生成')
    order_id = fields.Many2one('buy.order',
                               u'原始单据',
                               states=READONLY_STATES,
                               copy=False,
                               ondelete='restrict',
                               help=u'要调整的原始购货订单,只能调整已确认且没有全部入库的购货订单')
    date = fields.Date(u'单据日期',
                       states=READONLY_STATES,
                       default=lambda self: fields.Date.context_today(self),
                       index=True,
                       copy=False,
                       help=u'变更单创建日期,默认是当前日期')
    line_ids = fields.One2many('buy.adjust.line',
                               'order_id',
                               u'变更单行',
                               states=READONLY_STATES,
                               copy=True,
                               help=u'变更单明细行,不允许为空')
    approve_uid = fields.Many2one('res.users',
                                  u'确认人',
                                  copy=False,
                                  ondelete='restrict',
                                  help=u'确认变更单的人')
    state = fields.Selection(BUY_ORDER_STATES,
                             u'确认状态',
                             index=True,
                             copy=False,
                             default='draft',
                             track_visibility='onchange',
                             help=u'变更单确认状态')
    note = fields.Text(u'备注', help=u'单据备注')
    user_id = fields.Many2one(
        'res.users',
        u'经办人',
        ondelete='restrict',
        states=READONLY_STATES,
        default=lambda self: self.env.user,
        help=u'单据经办人',
    )
    company_id = fields.Many2one(
        'res.company',
        string=u'公司',
        change_default=True,
        default=lambda self: self.env['res.company']._company_default_get())

    def _get_vals(self, line):
        '''返回创建 buy order line 时所需数据'''
        return {
            'order_id': self.order_id.id,
            'goods_id': line.goods_id.id,
            'attribute_id': line.attribute_id.id,
            'quantity': line.quantity,
            'uom_id': line.uom_id.id,
            'price_taxed': line.price_taxed,
            'discount_rate': line.discount_rate,
            'discount_amount': line.discount_amount,
            'tax_rate': line.tax_rate,
            'note': line.note or '',
        }

    @api.one
    def buy_adjust_done(self):
        '''确认采购变更单:
        当调整后数量 < 原单据中已入库数量,则报错;
        当调整后数量 > 原单据中已入库数量,则更新原单据及入库单分单的数量;
        当调整后数量 = 原单据中已入库数量,则更新原单据数量,删除入库单分单;
        当新增商品时,则更新原单据及入库单分单明细行。
        '''
        if self.state == 'done':
            raise UserError(u'请不要重复确认!\n采购变更单%s已确认' % self.name)
        if not self.line_ids:
            raise UserError(u'请输入商品明细行!')
        for line in self.line_ids:
            if line.price_taxed < 0:
                raise UserError(u'商品含税单价不能小于0!\n单价:%s' % line.price_taxed)
        buy_receipt = self.env['buy.receipt'].search([('order_id', '=',
                                                       self.order_id.id),
                                                      ('state', '=', 'draft')])
        if not buy_receipt:
            raise UserError(u'采购入库单已全部入库,不能调整')
        for line in self.line_ids:
            # 检查属性是否填充,防止无权限人员不填就可以保存
            if line.using_attribute and not line.attribute_id:
                raise UserError(u'请输入商品:%s 的属性' % line.goods_id.name)
            origin_line = self.env['buy.order.line'].search([
                ('goods_id', '=', line.goods_id.id),
                ('attribute_id', '=', line.attribute_id.id),
                ('order_id', '=', self.order_id.id)
            ])
            if len(origin_line) > 1:
                raise UserError(u'要调整的商品%s在原始单据中不唯一' % line.goods_id.name)
            if origin_line:
                origin_line.quantity += line.quantity  # 调整后数量
                new_note = u'变更单:%s %s。\n' % (self.name, line.note)
                origin_line.note = (origin_line.note
                                    and origin_line.note + new_note
                                    or new_note)
                if origin_line.quantity < origin_line.quantity_in:
                    raise UserError(u'%s调整后数量不能小于原订单已入库数量' %
                                    line.goods_id.name)
                elif origin_line.quantity > origin_line.quantity_in:
                    # 查找出原购货订单产生的草稿状态的入库单明细行,并更新它
                    move_line = self.env['wh.move.line'].search([
                        ('buy_line_id', '=', origin_line.id),
                        ('state', '=', 'draft')
                    ])
                    if move_line:
                        move_line.goods_qty += line.quantity
                        move_line.note = (move_line.note and move_line.note
                                          or move_line.note + origin_line.note)
                    else:
                        raise UserError(u'商品%s已全部入库,建议新建购货订单' %
                                        line.goods_id.name)
                # 调整后数量与已入库数量相等时,删除产生的入库单分单
                else:
                    buy_receipt.unlink()
            else:
                new_line = self.env['buy.order.line'].create(
                    self._get_vals(line))
                receipt_line = []
                if line.goods_id.force_batch_one:
                    i = 0
                    while i < line.quantity:
                        i += 1
                        receipt_line.append(
                            self.order_id.get_receipt_line(new_line,
                                                           single=True))
                else:
                    receipt_line.append(
                        self.order_id.get_receipt_line(new_line, single=False))
                buy_receipt.write(
                    {'line_in_ids': [(0, 0, li[0]) for li in receipt_line]})
        self.state = 'done'
        self.approve_uid = self._uid
Ejemplo n.º 12
0
class AccountDocumentRelated(models.Model):
    _name = 'br_account.document.related'
    _description = "Documentos Relacionados"

    invoice_id = fields.Many2one('account.invoice',
                                 'Documento Fiscal',
                                 ondelete='cascade')
    invoice_related_id = fields.Many2one('account.invoice',
                                         'Documento Fiscal',
                                         ondelete='cascade')
    document_type = fields.Selection([('nf', 'NF'), ('nfe', 'NF-e'),
                                      ('cte', 'CT-e'),
                                      ('nfrural', 'NF Produtor'),
                                      ('cf', 'Cupom Fiscal')],
                                     'Tipo Documento',
                                     required=True)
    access_key = fields.Char('Chave de Acesso', size=44)
    serie = fields.Char(u'Série', size=12)
    internal_number = fields.Char(u'Número', size=32)
    state_id = fields.Many2one('res.country.state',
                               'Estado',
                               domain="[('country_id.code', '=', 'BR')]")
    cnpj_cpf = fields.Char('CNPJ/CPF', size=18)
    cpfcnpj_type = fields.Selection([('cpf', 'CPF'), ('cnpj', 'CNPJ')],
                                    'Tipo Doc.',
                                    default='cnpj')
    inscr_est = fields.Char('Inscr. Estadual/RG', size=16)
    date = fields.Date('Data')
    fiscal_document_id = fields.Many2one('br_account.fiscal.document',
                                         'Documento')

    @api.one
    @api.constrains('cnpj_cpf')
    def _check_cnpj_cpf(self):
        check_cnpj_cpf = True
        if self.cnpj_cpf:
            if self.cpfcnpj_type == 'cnpj':
                if not fiscal.validate_cnpj(self.cnpj_cpf):
                    check_cnpj_cpf = False
            elif not fiscal.validate_cpf(self.cnpj_cpf):
                check_cnpj_cpf = False
        if not check_cnpj_cpf:
            raise UserError(_('CNPJ/CPF do documento relacionado é invalido!'))

    @api.one
    @api.constrains('inscr_est')
    def _check_ie(self):
        check_ie = True
        if self.inscr_est:
            uf = self.state_id and self.state_id.code.lower() or ''
            try:
                mod = __import__('odoo.addons.br_base.tools.fiscal', globals(),
                                 locals(), 'fiscal')

                validate = getattr(mod, 'validate_ie_%s' % uf)
                if not validate(self.inscr_est):
                    check_ie = False
            except AttributeError:
                if not fiscal.validate_ie_param(uf, self.inscr_est):
                    check_ie = False
        if not check_ie:
            raise UserError(
                _('Inscrição Estadual do documento fiscal inválida!'))

    @api.onchange('invoice_related_id')
    def onchange_invoice_related_id(self):
        if not self.invoice_related_id:
            return
        inv_id = self.invoice_related_id
        if not inv_id.product_document_id:
            return

        self.document_type = \
            self.translate_document_type(inv_id.product_document_id.code)

        if inv_id.product_document_id.code in ('55', '57'):
            self.serie = False
            self.internal_number = False
            self.state_id = False
            self.cnpj_cpf = False
            self.cpfcnpj_type = False
            self.date = False
            self.fiscal_document_id = False
            self.inscr_est = False

    def translate_document_type(self, code):
        if code == '55':
            return 'nfe'
        elif code == '04':
            return 'nfrural'
        elif code == '57':
            return 'cte'
        elif code in ('2B', '2C', '2D'):
            return 'cf'
        else:
            return 'nf'
Ejemplo n.º 13
0
class Contract(models.Model):
    _name = 'hr.contract'
    _description = 'Contract'
    _inherit = ['mail.thread', 'mail.activity.mixin']

    name = fields.Char('Contract Reference', required=True)
    active = fields.Boolean(default=True)
    structure_type_id = fields.Many2one('hr.payroll.structure.type', string="Salary Structure Type")
    employee_id = fields.Many2one('hr.employee', string='Employee', tracking=True, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
    department_id = fields.Many2one('hr.department', compute='_compute_employee_contract', store=True, readonly=False,
        domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", string="Department")
    job_id = fields.Many2one('hr.job', compute='_compute_employee_contract', store=True, readonly=False,
        domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", string='Job Position')
    date_start = fields.Date('Start Date', required=True, default=fields.Date.today, tracking=True,
        help="Start date of the contract.", index=True)
    date_end = fields.Date('End Date', tracking=True,
        help="End date of the contract (if it's a fixed-term contract).")
    trial_date_end = fields.Date('End of Trial Period',
        help="End date of the trial period (if there is one).")
    resource_calendar_id = fields.Many2one(
        'resource.calendar', 'Working Schedule', compute='_compute_employee_contract', store=True, readonly=False,
        default=lambda self: self.env.company.resource_calendar_id.id, copy=False, index=True,
        domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
    wage = fields.Monetary('Wage', required=True, tracking=True, help="Employee's monthly gross wage.")
    contract_wage = fields.Monetary('Contract Wage', compute='_compute_contract_wage')
    notes = fields.Html('Notes')
    state = fields.Selection([
        ('draft', 'New'),
        ('open', 'Running'),
        ('close', 'Expired'),
        ('cancel', 'Cancelled')
    ], string='Status', group_expand='_expand_states', copy=False,
       tracking=True, help='Status of the contract', default='draft')
    company_id = fields.Many2one('res.company', compute='_compute_employee_contract', store=True, readonly=False,
        default=lambda self: self.env.company, required=True)
    company_country_id = fields.Many2one('res.country', string="Company country", related='company_id.country_id', readonly=True)
    country_code = fields.Char(related='company_country_id.code', readonly=True)
    contract_type_id = fields.Many2one('hr.contract.type', "Contract Type")

    """
        kanban_state:
            * draft + green = "Incoming" state (will be set as Open once the contract has started)
            * open + red = "Pending" state (will be set as Closed once the contract has ended)
            * red = Shows a warning on the employees kanban view
    """
    kanban_state = fields.Selection([
        ('normal', 'Grey'),
        ('done', 'Green'),
        ('blocked', 'Red')
    ], string='Kanban State', default='normal', tracking=True, copy=False)
    currency_id = fields.Many2one(string="Currency", related='company_id.currency_id', readonly=True)
    permit_no = fields.Char('Work Permit No', related="employee_id.permit_no", readonly=False)
    visa_no = fields.Char('Visa No', related="employee_id.visa_no", readonly=False)
    visa_expire = fields.Date('Visa Expire Date', related="employee_id.visa_expire", readonly=False)
    hr_responsible_id = fields.Many2one('res.users', 'HR Responsible', tracking=True,
        help='Person responsible for validating the employee\'s contracts.')
    calendar_mismatch = fields.Boolean(compute='_compute_calendar_mismatch')
    first_contract_date = fields.Date(related='employee_id.first_contract_date')

    @api.depends('employee_id.resource_calendar_id', 'resource_calendar_id')
    def _compute_calendar_mismatch(self):
        for contract in self:
            contract.calendar_mismatch = contract.resource_calendar_id != contract.employee_id.resource_calendar_id

    def _expand_states(self, states, domain, order):
        return [key for key, val in type(self).state.selection]

    @api.depends('employee_id')
    def _compute_employee_contract(self):
        for contract in self.filtered('employee_id'):
            contract.job_id = contract.employee_id.job_id
            contract.department_id = contract.employee_id.department_id
            contract.resource_calendar_id = contract.employee_id.resource_calendar_id
            contract.company_id = contract.employee_id.company_id

    @api.onchange('company_id')
    def _onchange_company_id(self):
        if self.company_id:
            structure_types = self.env['hr.payroll.structure.type'].search([
                '|',
                ('country_id', '=', self.company_id.country_id.id),
                ('country_id', '=', False)])
            if structure_types:
                self.structure_type_id = structure_types[0]
            elif self.structure_type_id not in structure_types:
                self.structure_type_id = False

    @api.onchange('structure_type_id')
    def _onchange_structure_type_id(self):
        if self.structure_type_id.default_resource_calendar_id:
            self.resource_calendar_id = self.structure_type_id.default_resource_calendar_id

    @api.constrains('employee_id', 'state', 'kanban_state', 'date_start', 'date_end')
    def _check_current_contract(self):
        """ Two contracts in state [incoming | open | close] cannot overlap """
        for contract in self.filtered(lambda c: (c.state not in ['draft', 'cancel'] or c.state == 'draft' and c.kanban_state == 'done') and c.employee_id):
            domain = [
                ('id', '!=', contract.id),
                ('employee_id', '=', contract.employee_id.id),
                '|',
                    ('state', 'in', ['open', 'close']),
                    '&',
                        ('state', '=', 'draft'),
                        ('kanban_state', '=', 'done') # replaces incoming
            ]

            if not contract.date_end:
                start_domain = []
                end_domain = ['|', ('date_end', '>=', contract.date_start), ('date_end', '=', False)]
            else:
                start_domain = [('date_start', '<=', contract.date_end)]
                end_domain = ['|', ('date_end', '>', contract.date_start), ('date_end', '=', False)]

            domain = expression.AND([domain, start_domain, end_domain])
            if self.search_count(domain):
                raise ValidationError(_('An employee can only have one contract at the same time. (Excluding Draft and Cancelled contracts)'))

    @api.constrains('date_start', 'date_end')
    def _check_dates(self):
        for contract in self:
            if contract.date_end and contract.date_start > contract.date_end:
                raise ValidationError(_(
                    'Contract %(contract)s: start date (%(start)s) must be earlier than contract end date (%(end)s).',
                    contract=contract.name, start=contract.date_start, end=contract.date_end,
                ))

    @api.model
    def update_state(self):
        contracts = self.search([
            ('state', '=', 'open'), ('kanban_state', '!=', 'blocked'),
            '|',
            '&',
            ('date_end', '<=', fields.Date.to_string(date.today() + relativedelta(days=7))),
            ('date_end', '>=', fields.Date.to_string(date.today() + relativedelta(days=1))),
            '&',
            ('visa_expire', '<=', fields.Date.to_string(date.today() + relativedelta(days=60))),
            ('visa_expire', '>=', fields.Date.to_string(date.today() + relativedelta(days=1))),
        ])

        for contract in contracts:
            contract.activity_schedule(
                'mail.mail_activity_data_todo', contract.date_end,
                _("The contract of %s is about to expire.", contract.employee_id.name),
                user_id=contract.hr_responsible_id.id or self.env.uid)

        contracts.write({'kanban_state': 'blocked'})

        self.search([
            ('state', '=', 'open'),
            '|',
            ('date_end', '<=', fields.Date.to_string(date.today() + relativedelta(days=1))),
            ('visa_expire', '<=', fields.Date.to_string(date.today() + relativedelta(days=1))),
        ]).write({
            'state': 'close'
        })

        self.search([('state', '=', 'draft'), ('kanban_state', '=', 'done'), ('date_start', '<=', fields.Date.to_string(date.today())),]).write({
            'state': 'open'
        })

        contract_ids = self.search([('date_end', '=', False), ('state', '=', 'close'), ('employee_id', '!=', False)])
        # Ensure all closed contract followed by a new contract have a end date.
        # If closed contract has no closed date, the work entries will be generated for an unlimited period.
        for contract in contract_ids:
            next_contract = self.search([
                ('employee_id', '=', contract.employee_id.id),
                ('state', 'not in', ['cancel', 'new']),
                ('date_start', '>', contract.date_start)
            ], order="date_start asc", limit=1)
            if next_contract:
                contract.date_end = next_contract.date_start - relativedelta(days=1)
                continue
            next_contract = self.search([
                ('employee_id', '=', contract.employee_id.id),
                ('date_start', '>', contract.date_start)
            ], order="date_start asc", limit=1)
            if next_contract:
                contract.date_end = next_contract.date_start - relativedelta(days=1)

        return True

    def _assign_open_contract(self):
        for contract in self:
            contract.employee_id.sudo().write({'contract_id': contract.id})

    @api.depends('wage')
    def _compute_contract_wage(self):
        for contract in self:
            contract.contract_wage = contract._get_contract_wage()

    def _get_contract_wage(self):
        self.ensure_one()
        return self[self._get_contract_wage_field()]

    def _get_contract_wage_field(self):
        self.ensure_one()
        return 'wage'

    def write(self, vals):
        res = super(Contract, self).write(vals)
        if vals.get('state') == 'open':
            self._assign_open_contract()
        if vals.get('state') == 'close':
            for contract in self.filtered(lambda c: not c.date_end):
                contract.date_end = max(date.today(), contract.date_start)

        calendar = vals.get('resource_calendar_id')
        if calendar:
            self.filtered(lambda c: c.state == 'open' or (c.state == 'draft' and c.kanban_state == 'done')).mapped('employee_id').write({'resource_calendar_id': calendar})

        if 'state' in vals and 'kanban_state' not in vals:
            self.write({'kanban_state': 'normal'})

        return res

    @api.model
    def create(self, vals):
        contracts = super(Contract, self).create(vals)
        if vals.get('state') == 'open':
            contracts._assign_open_contract()
        open_contracts = contracts.filtered(lambda c: c.state == 'open' or c.state == 'draft' and c.kanban_state == 'done')
        # sync contract calendar -> calendar employee
        for contract in open_contracts.filtered(lambda c: c.employee_id and c.resource_calendar_id):
            contract.employee_id.resource_calendar_id = contract.resource_calendar_id
        return contracts

    def _track_subtype(self, init_values):
        self.ensure_one()
        if 'state' in init_values and self.state == 'open' and 'kanban_state' in init_values and self.kanban_state == 'blocked':
            return self.env.ref('hr_contract.mt_contract_pending')
        elif 'state' in init_values and self.state == 'close':
            return self.env.ref('hr_contract.mt_contract_close')
        return super(Contract, self)._track_subtype(init_values)

    def action_open_contract_form(self):
        self.ensure_one()
        return {
            "type": "ir.actions.act_window",
            "res_model": "hr.contract",
            "views": [[False, "form"]],
            "res_id": self.id,
        }
Ejemplo n.º 14
0
class SellAdjust(models.Model):
    _name = "sell.adjust"
    _inherit = ['mail.thread']
    _description = u"销售变更单"
    _order = 'date desc, id desc'

    name = fields.Char(u'单据编号', copy=False, help=u'变更单编号,保存时可自动生成')
    order_id = fields.Many2one('sell.order',
                               u'原始单据',
                               states=READONLY_STATES,
                               copy=False,
                               ondelete='restrict',
                               help=u'要调整的原始销货订单,只能调整已审核且没有全部出库的销货订单')
    date = fields.Date(u'单据日期',
                       states=READONLY_STATES,
                       default=lambda self: fields.Date.context_today(self),
                       index=True,
                       copy=False,
                       help=u'变更单创建日期,默认是当前日期')
    line_ids = fields.One2many('sell.adjust.line',
                               'order_id',
                               u'变更单行',
                               states=READONLY_STATES,
                               copy=True,
                               help=u'变更单明细行,不允许为空')
    approve_uid = fields.Many2one('res.users',
                                  u'审核人',
                                  copy=False,
                                  ondelete='restrict',
                                  help=u'审核变更单的人')
    state = fields.Selection(SELL_ORDER_STATES,
                             u'审核状态',
                             index=True,
                             copy=False,
                             default='draft',
                             help=u'变更单审核状态')
    note = fields.Text(u'备注', help=u'单据备注')
    user_id = fields.Many2one(
        'res.users',
        u'经办人',
        ondelete='restrict',
        states=READONLY_STATES,
        default=lambda self: self.env.user,
        help=u'单据经办人',
    )
    company_id = fields.Many2one(
        'res.company',
        string=u'公司',
        change_default=True,
        default=lambda self: self.env['res.company']._company_default_get())

    @api.one
    def sell_adjust_done(self):
        '''审核销售变更单:
        当调整后数量 < 原单据中已出库数量,则报错;
        当调整后数量 > 原单据中已出库数量,则更新原单据及发货单分单的数量;
        当调整后数量 = 原单据中已出库数量,则更新原单据数量,删除发货单分单;
        当新增商品时,则更新原单据及发货单分单明细行。
        '''
        if self.state == 'done':
            raise UserError(u'请不要重复审核!')
        if not self.line_ids:
            raise UserError(u'请输入商品明细行!')
        delivery = self.env['sell.delivery'].search([('order_id', '=',
                                                      self.order_id.id),
                                                     ('state', '=', 'draft')])
        if not delivery:
            raise UserError(u'销售发货单已全部出库,不能调整')
        for line in self.line_ids:
            origin_line = self.env['sell.order.line'].search([
                ('goods_id', '=', line.goods_id.id),
                ('attribute_id', '=', line.attribute_id.id),
                ('order_id', '=', self.order_id.id)
            ])
            if len(origin_line) > 1:
                raise UserError(u'要调整的商品 %s 在原始单据中不唯一' % line.goods_id.name)
            if origin_line:
                origin_line.quantity += line.quantity  # 调整后数量
                new_note = u'变更单:%s %s。\n' % (self.name, line.note)
                origin_line.note = (origin_line.note
                                    and origin_line.note + new_note
                                    or new_note)
                if origin_line.quantity < origin_line.quantity_out:
                    raise UserError(u' %s 调整后数量不能小于原订单已出库数量' %
                                    line.goods_id.name)
                elif origin_line.quantity > origin_line.quantity_out:
                    # 查找出原销货订单产生的草稿状态的发货单明细行,并更新它
                    move_line = self.env['wh.move.line'].search([
                        ('sell_line_id', '=', origin_line.id),
                        ('state', '=', 'draft')
                    ])
                    if move_line:
                        move_line.goods_qty += line.quantity
                        move_line.note = (move_line.note and move_line.note
                                          or move_line.note + origin_line.note)
                    else:
                        raise UserError(u'商品 %s 已全部入库,建议新建购货订单' %
                                        line.goods_id.name)
                # 调整后数量与已出库数量相等时,删除产生的发货单分单
                else:
                    delivery.unlink()
            else:
                vals = {
                    'order_id': self.order_id.id,
                    'goods_id': line.goods_id.id,
                    'attribute_id': line.attribute_id.id,
                    'quantity': line.quantity,
                    'uom_id': line.uom_id.id,
                    'price_taxed': line.price_taxed,
                    'discount_rate': line.discount_rate,
                    'discount_amount': line.discount_amount,
                    'tax_rate': line.tax_rate,
                    'note': line.note or '',
                }
                new_line = self.env['sell.order.line'].create(vals)
                delivery_line = []
                if line.goods_id.force_batch_one:
                    i = 0
                    while i < line.quantity:
                        i += 1
                        delivery_line.append(
                            self.order_id.get_delivery_line(new_line,
                                                            single=True))
                else:
                    delivery_line.append(
                        self.order_id.get_delivery_line(new_line,
                                                        single=False))
                delivery.write(
                    {'line_out_ids': [(0, 0, li[0]) for li in delivery_line]})
        self.state = 'done'
        self.approve_uid = self._uid
Ejemplo n.º 15
0
class SaleOrder(models.Model):
    _inherit = "sale.order"

    @api.model
    def _default_warehouse_id(self):
        company = self.env.user.company_id.id
        warehouse_ids = self.env['stock.warehouse'].search([('company_id', '=', company)], limit=1)
        return warehouse_ids

    incoterm = fields.Many2one(
        'account.incoterms', 'Incoterms',
        help="International Commercial Terms are a series of predefined commercial terms used in international transactions.")
    picking_policy = fields.Selection([
        ('direct', 'As soon as possible'),
        ('one', 'When all products are ready')],
        string='Shipping Policy', required=True, readonly=True, default='direct',
        states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}
        ,help="If you deliver all products at once, the delivery order will be scheduled based on the greatest "
        "product lead time. Otherwise, it will be based on the shortest.")
    warehouse_id = fields.Many2one(
        'stock.warehouse', string='Warehouse',
        required=True, readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]},
        default=_default_warehouse_id)
    picking_ids = fields.One2many('stock.picking', 'sale_id', string='Pickings')
    delivery_count = fields.Integer(string='Delivery Orders', compute='_compute_picking_ids')
    procurement_group_id = fields.Many2one('procurement.group', 'Procurement Group', copy=False)
    effective_date = fields.Date("Effective Date", compute='_compute_effective_date', store=True, help="Completion date of the first delivery order.")

    @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 == 'customer')
            dates_list = [date for date in pickings.mapped('date_done') if date]
            order.effective_date = dates_list and min(dates_list).date()

    @api.depends('picking_policy')
    def _compute_expected_date(self):
        super(SaleOrder, self)._compute_expected_date()
        for order in self:
            dates_list = []
            confirm_date = fields.Datetime.from_string((order.confirmation_date or order.write_date) if order.state == 'sale' else fields.Datetime.now())
            for line in order.order_line.filtered(lambda x: x.state != 'cancel' and not x._is_delivery()):
                dt = confirm_date + timedelta(days=line.customer_lead or 0.0)
                dates_list.append(dt)
            if dates_list:
                expected_date = min(dates_list) if order.picking_policy == 'direct' else max(dates_list)
                order.expected_date = fields.Datetime.to_string(expected_date)

    @api.multi
    def write(self, values):
        if values.get('order_line') and self.state == 'sale':
            for order in self:
                pre_order_line_qty = {order_line: order_line.product_uom_qty for order_line in order.mapped('order_line')}
        res = super(SaleOrder, self).write(values)
        if values.get('order_line') and self.state == 'sale':
            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(order_line.product_uom_qty, pre_order_line_qty[order_line], order_line.product_uom.rounding) < 0:
                        to_log[order_line] = (order_line.product_uom_qty, pre_order_line_qty[order_line])
                if to_log:
                    documents = self.env['stock.picking']._log_activity_get_documents(to_log, 'move_ids', 'UP')
                    order._log_decrease_ordered_quantity(documents)
        return res

    @api.multi
    def _action_confirm(self):
        for order in self:
            order.order_line._action_launch_stock_rule()
        super(SaleOrder, self)._action_confirm()

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

    @api.onchange('warehouse_id')
    def _onchange_warehouse_id(self):
        if self.warehouse_id.company_id:
            self.company_id = self.warehouse_id.company_id.id

    @api.multi
    def action_view_delivery(self):
        '''
        This function returns an action that display existing delivery orders
        of given sales order ids. It can either be a in a list or in a form
        view, if there is only one delivery order to show.
        '''
        action = self.env.ref('stock.action_picking_tree_all').read()[0]

        pickings = self.mapped('picking_ids')
        if len(pickings) > 1:
            action['domain'] = [('id', 'in', pickings.ids)]
        elif pickings:
            action['views'] = [(self.env.ref('stock.view_picking_form').id, 'form')]
            action['res_id'] = pickings.id
        return action

    @api.multi
    def action_cancel(self):
        documents = None
        for sale_order in self:
            if sale_order.state == 'sale' and sale_order.order_line:
                sale_order_lines_quantities = {order_line: (order_line.product_uom_qty, 0) for order_line in sale_order.order_line}
                documents = self.env['stock.picking']._log_activity_get_documents(sale_order_lines_quantities, 'move_ids', 'UP')
        self.mapped('picking_ids').action_cancel()
        if documents:
            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._log_decrease_ordered_quantity(filtered_documents, cancel=True)
        return super(SaleOrder, self).action_cancel()

    @api.multi
    def _prepare_invoice(self):
        invoice_vals = super(SaleOrder, self)._prepare_invoice()
        invoice_vals['incoterms_id'] = self.incoterm.id or False
        return invoice_vals

    @api.model
    def _get_customer_lead(self, product_tmpl_id):
        super(SaleOrder, self)._get_customer_lead(product_tmpl_id)
        return product_tmpl_id.sale_delay

    def _log_decrease_ordered_quantity(self, documents, cancel=False):

        def _render_note_exception_quantity_so(rendering_context):
            order_exceptions, visited_moves = rendering_context
            visited_moves = list(visited_moves)
            visited_moves = self.env[visited_moves[0]._name].concat(*visited_moves)
            order_line_ids = self.env['sale.order.line'].browse([order_line.id for order in order_exceptions.values() for order_line in order[0]])
            sale_order_ids = order_line_ids.mapped('order_id')
            impacted_pickings = visited_moves.filtered(lambda m: m.state not in ('done', 'cancel')).mapped('picking_id')
            values = {
                'sale_order_ids': sale_order_ids,
                'order_exceptions': order_exceptions.values(),
                'impacted_pickings': impacted_pickings,
                'cancel': cancel
            }
            return self.env.ref('sale_stock.exception_on_so').render(values=values)

        self.env['stock.picking']._log_activity(_render_note_exception_quantity_so, documents)
Ejemplo n.º 16
0
class PaymentAdviceReport(models.Model):
    _name = "payment.advice.report"
    _description = "Payment Advice Analysis"
    _auto = False

    name = fields.Char(readonly=True)
    date = fields.Date(readonly=True)
    year = fields.Char(readonly=True)
    month = fields.Selection([('01', 'January'), ('02', 'February'),
                              ('03', 'March'), ('04', 'April'), ('05', 'May'),
                              ('06', 'June'), ('07', 'July'), ('08', 'August'),
                              ('09', 'September'), ('10', 'October'),
                              ('11', 'November'), ('12', 'December')],
                             readonly=True)
    day = fields.Char(readonly=True)
    state = fields.Selection([
        ('draft', 'Draft'),
        ('confirm', 'Confirmed'),
        ('cancel', 'Cancelled'),
    ],
                             string='Status',
                             index=True,
                             readonly=True)
    employee_id = fields.Many2one('hr.employee',
                                  string='Employee',
                                  readonly=True)
    nbr = fields.Integer(string='# Payment Lines', readonly=True)
    number = fields.Char(readonly=True)
    bysal = fields.Float(string='By Salary', readonly=True)
    bank_id = fields.Many2one('res.bank', string='Bank', readonly=True)
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 readonly=True)
    cheque_nos = fields.Char(string='Cheque Numbers', readonly=True)
    neft = fields.Boolean(string='NEFT Transaction', readonly=True)
    ifsc_code = fields.Char(string='IFSC Code', readonly=True)
    employee_bank_no = fields.Char(string='Employee Bank Account',
                                   required=True)

    @api.model_cr
    def init(self):
        drop_view_if_exists(self.env.cr, self._table)
        self.env.cr.execute("""
            create or replace view payment_advice_report as (
                select
                    min(l.id) as id,
                    sum(l.bysal) as bysal,
                    p.name,
                    p.state,
                    p.date,
                    p.number,
                    p.company_id,
                    p.bank_id,
                    p.chaque_nos as cheque_nos,
                    p.neft,
                    l.employee_id,
                    l.ifsc_code,
                    l.name as employee_bank_no,
                    to_char(p.date, 'YYYY') as year,
                    to_char(p.date, 'MM') as month,
                    to_char(p.date, 'YYYY-MM-DD') as day,
                    1 as nbr
                from
                    hr_payroll_advice as p
                    left join hr_payroll_advice_line as l on (p.id=l.advice_id)
                where
                    l.employee_id IS NOT NULL
                group by
                    p.number,p.name,p.date,p.state,p.company_id,p.bank_id,p.chaque_nos,p.neft,
                    l.employee_id,l.advice_id,l.bysal,l.ifsc_code, l.name
            )
        """)
Ejemplo n.º 17
0
class Agreement(models.Model):
    _name = "agreement"
    _description = "Agreement"
    _inherit = ["mail.thread", "mail.activity.mixin"]

    code = fields.Char(required=True, tracking=True)
    name = fields.Char(required=True, tracking=True)
    partner_id = fields.Many2one(
        "res.partner",
        string="Partner",
        ondelete="restrict",
        domain=[("parent_id", "=", False)],
        tracking=True,
    )
    company_id = fields.Many2one(
        "res.company",
        string="Company",
        default=lambda self: self.env.company,
    )
    is_template = fields.Boolean(
        string="Is a Template?",
        default=False,
        copy=False,
        help="Set if the agreement is a template. "
        "Template agreements don't require a partner.",
    )
    agreement_type_id = fields.Many2one(
        "agreement.type",
        string="Agreement Type",
        help="Select the type of agreement",
    )
    domain = fields.Selection(
        "_domain_selection",
        string="Domain",
        default="sale",
        tracking=True,
    )
    active = fields.Boolean(default=True)
    signature_date = fields.Date(tracking=True)
    start_date = fields.Date(tracking=True)
    end_date = fields.Date(tracking=True)

    @api.model
    def _domain_selection(self):
        return [
            ("sale", _("Sale")),
            ("purchase", _("Purchase")),
        ]

    @api.onchange("agreement_type_id")
    def agreement_type_change(self):
        if self.agreement_type_id and self.agreement_type_id.domain:
            self.domain = self.agreement_type_id.domain

    def name_get(self):
        res = []
        for agr in self:
            name = agr.name
            if agr.code:
                name = "[{}] {}".format(agr.code, agr.name)
            res.append((agr.id, name))
        return res

    _sql_constraints = [(
        "code_partner_company_unique",
        "unique(code, partner_id, company_id)",
        "This agreement code already exists for this partner!",
    )]

    def copy(self, default=None):
        """Always assign a value for code because is required"""
        default = dict(default or {})
        if default.get("code", False):
            return super().copy(default)
        default.setdefault("code", _("%s (copy)") % (self.code))
        return super().copy(default)
class WizardDownloadInvoice(models.TransientModel):
    _name = "wizard.cfdi_download_invoice"

    rfc = fields.Char('RFC', size=13)
    folio = fields.Char('Folio', size=5)
    cliente = fields.Many2one('res.partner', string="Cliente")
    fecha_inicio = fields.Date('Fecha inicial')
    fecha_fin = fields.Date('Fecha final')
    busqueda = fields.Selection([
        ('rfc', 'RFC'),
        ('folio', 'Folio'),
        ('cliente', 'Cliente'),
        ('fechas', 'Rango de fechas',)
    ], default='rfc', string="Tipo de búsqueda")
    file_name = fields.Char('Nombre de archivo')
    file_zip = fields.Binary('Archivo')

    @api.multi
    def download_zip(self):
        if self.busqueda == 'rfc':
            rule = re.compile(
                r'^([A-ZÑ\x26]{3,4}([0-9]{2})(0[1-9]|1[0-2])(0[1-9]|1[0-9]|2[0-9]|3[0-1]))((-)?([A-Z\d]{3}))?$')
            if not rule.search(self.rfc):
                msg = "Formato de RFC Invalido\n"
                msg = msg + "El formato correcto es el siguiente:\n\n"
                msg = msg + "-Apellido Paterno (del cual se van a utilizar las primeras 2 letras). \n"
                msg = msg + "-Apellido Materno (de este solo se utilizará la primera letra).\n"
                msg = msg + "-Nombre(s) (sin importar si tienes uno o dos nombres, solo se utilizará la primera letra del primer nombre).\n"
                msg = msg + "-Fecha de Nacimiento (día, mes y año).\n"
                msg = msg + "-Sexo (Masculino o Femenino).\n"
                msg = msg + "-Entidad Federativa de nacimiento (Estado en el que fue registrado al nacer)."
                raise Warning(msg)
            invoices = self.env["account.invoice"].search(['&', ('cfdi', '=', 'true'),
                                                           ('partner_id.vat', '=', self.rfc)])
            self.fill_zip(invoices, 'RFC')
        elif self.busqueda == 'folio':
            invoices = self.env["account.invoice"].search(['&', ('cfdi', '=', 'true'),
                                                           ('number', 'like', self.folio)])
            self.fill_zip(invoices, 'folio')
        elif self.busqueda == 'cliente':
            invoices = self.env["account.invoice"].search(['&', ('cfdi', '=', 'true'),
                                                           ('partner_id.name', '=', self.cliente.name)])
            self.fill_zip(invoices, 'cliente')
        else:
            if self.fecha_inicio > self.fecha_fin:
                raise Warning("La fecha inicial no puede ser mayor a la fecha final")

            invoices = self.env["account.invoice"].search(['&', '&', ('cfdi', '=', 'true'),
                                                           ('create_date', '>=', self.fecha_inicio),
                                                           ('create_date', '<=', self.fecha_fin)])
            self.fill_zip(invoices, 'rango de fechas')

        return {
            'type': 'ir.actions.act_url',
            'url': '/web/content/wizard.cfdi_download_invoice/%s/file_zip/file.zip' % (
                self.id),
            'target': 'new',
        }

    @api.multi
    def fill_zip(self, invoices, busqueda):
        if(len(invoices) > 0):
            zf = zipfile.ZipFile('/tmp/example.zip', 'w', zipfile.ZIP_DEFLATED)
            i = 0
            for invoice in invoices:
                if invoice.cfdi:
                    try:
                        if i < 5:
                            file_xml = ""
                            if invoice.number:
                                file_xml = "/tmp/%s.xml" % str(invoice.number).replace("/", "_")
                            else:
                                file_xml += "/tmp/%s.xml" % str(invoice.uuid)
                            f = open(file_xml, 'w')
                            f.write(base64.b64decode(invoice.xml_cfdi))
                            f.close()

                            if invoice.state == 'cancel' and invoice.xml_acuse:
                                file_acuse_xml = ""
                                if invoice.number:
                                    file_acuse_xml = "/tmp/acuse_%s.xml" % str(invoice.number).replace("/", "_")
                                else:
                                    file_acuse_xml = "/tmp/acuse_%s.xml" % str(invoice.uuid)
                                f = open(file_acuse_xml, 'w')
                                f.write(base64.b64decode(invoice.xml_acuse))
                                f.close()
                                zf.write(file_acuse_xml, basename(file_acuse_xml))

                            zf.write(file_xml, basename(file_xml))

                            pdf_bin = self.env["report"].get_pdf([invoice.id], "cfdi_invoice.invoice_report_template",
                                                                 data={})
                            file_pdf = ""
                            if invoice.number:
                                file_pdf = "/tmp/%s.pdf" % str(invoice.number).replace("/", "_")
                            else:
                                file_pdf = "/tmp/%s.pdf" % str(invoice.uuid)

                            f = open(file_pdf, 'w')
                            f.write(pdf_bin)
                            f.close()

                            zf.write(file_pdf, basename(file_pdf))
                        i = i + 1
                    finally:
                        pass

            try:
                f = open('/tmp/Leeme.txt', 'w')
                f.write('Sistema desarrollado por DESITEG https://desiteg.tk')
                f.close()
                file_x = "/tmp/Leeme.txt"
                zf.write(file_x, basename(file_x))
            finally:
                zf.close()
            with open('/tmp/example.zip', 'rb') as fin:
                bytes_ok = fin.read()
                bs = bytes_ok.encode("base64")
                self.write({
                    'file_name': 'file.zip',
                    'file_zip': bs
                })
                self.env.cr.commit()
        else:
            warning = u"La búsqueda por %s no arrojo resultados." % busqueda
            raise Warning(warning)
Ejemplo n.º 19
0
class FleetVehicleLogContract(models.Model):
    _inherit = ['mail.thread', 'mail.activity.mixin']
    _name = 'fleet.vehicle.log.contract'
    _description = 'Vehicle Contract'
    _order = 'state desc,expiration_date'

    def compute_next_year_date(self, strdate):
        oneyear = relativedelta(years=1)
        start_date = fields.Date.from_string(strdate)
        return fields.Date.to_string(start_date + oneyear)

    vehicle_id = fields.Many2one('fleet.vehicle',
                                 'Vehicle',
                                 required=True,
                                 help='Vehicle concerned by this log')
    cost_subtype_id = fields.Many2one(
        'fleet.service.type',
        'Type',
        help='Cost type purchased with this cost',
        domain=[('category', '=', 'contract')])
    amount = fields.Monetary('Cost')
    date = fields.Date(help='Date when the cost has been executed')
    company_id = fields.Many2one('res.company',
                                 'Company',
                                 default=lambda self: self.env.company)
    currency_id = fields.Many2one('res.currency',
                                  related='company_id.currency_id')
    name = fields.Char(string='Name',
                       compute='_compute_contract_name',
                       store=True)
    active = fields.Boolean(default=True)
    user_id = fields.Many2one('res.users',
                              'Responsible',
                              default=lambda self: self.env.user,
                              index=True)
    start_date = fields.Date(
        'Contract Start Date',
        default=fields.Date.context_today,
        help='Date when the coverage of the contract begins')
    expiration_date = fields.Date(
        'Contract Expiration Date',
        default=lambda self: self.compute_next_year_date(
            fields.Date.context_today(self)),
        help=
        'Date when the coverage of the contract expirates (by default, one year after begin date)'
    )
    days_left = fields.Integer(compute='_compute_days_left',
                               string='Warning Date')
    insurer_id = fields.Many2one('res.partner', 'Vendor')
    purchaser_id = fields.Many2one(related='vehicle_id.driver_id',
                                   string='Current Driver')
    ins_ref = fields.Char('Reference', size=64, copy=False)
    state = fields.Selection(
        [('futur', 'Incoming'), ('open', 'In Progress'),
         ('expired', 'Expired'), ('closed', 'Closed')],
        'Status',
        default='open',
        readonly=True,
        help='Choose whether the contract is still valid or not',
        tracking=True,
        copy=False)
    notes = fields.Text(
        'Terms and Conditions',
        help=
        'Write here all supplementary information relative to this contract',
        copy=False)
    cost_generated = fields.Monetary('Recurring Cost')
    cost_frequency = fields.Selection([('no', 'No'), ('daily', 'Daily'),
                                       ('weekly', 'Weekly'),
                                       ('monthly', 'Monthly'),
                                       ('yearly', 'Yearly')],
                                      'Recurring Cost Frequency',
                                      default='monthly',
                                      help='Frequency of the recuring cost',
                                      required=True)
    service_ids = fields.Many2many('fleet.service.type',
                                   string="Included Services")

    @api.depends('vehicle_id.name', 'cost_subtype_id')
    def _compute_contract_name(self):
        for record in self:
            name = record.vehicle_id.name
            if record.cost_subtype_id.name:
                name = record.cost_subtype_id.name + ' ' + name
            record.name = name

    @api.depends('expiration_date', 'state')
    def _compute_days_left(self):
        """return a dict with as value for each contract an integer
        if contract is in an open state and is overdue, return 0
        if contract is in a closed state, return -1
        otherwise return the number of days before the contract expires
        """
        for record in self:
            if record.expiration_date and record.state in ['open', 'expired']:
                today = fields.Date.from_string(fields.Date.today())
                renew_date = fields.Date.from_string(record.expiration_date)
                diff_time = (renew_date - today).days
                record.days_left = diff_time > 0 and diff_time or 0
            else:
                record.days_left = -1

    def write(self, vals):
        res = super(FleetVehicleLogContract, self).write(vals)
        if vals.get('expiration_date') or vals.get('user_id'):
            self.activity_reschedule(
                ['fleet.mail_act_fleet_contract_to_renew'],
                date_deadline=vals.get('expiration_date'),
                new_user_id=vals.get('user_id'))
        return res

    def contract_close(self):
        for record in self:
            record.state = 'closed'

    def contract_draft(self):
        for record in self:
            record.state = 'futur'

    def contract_open(self):
        for record in self:
            record.state = 'open'

    @api.model
    def scheduler_manage_contract_expiration(self):
        # This method is called by a cron task
        # It manages the state of a contract, possibly by posting a message on the vehicle concerned and updating its status
        params = self.env['ir.config_parameter'].sudo()
        delay_alert_contract = int(
            params.get_param('hr_fleet.delay_alert_contract', default=30))
        date_today = fields.Date.from_string(fields.Date.today())
        outdated_days = fields.Date.to_string(date_today + relativedelta(
            days=+delay_alert_contract))
        nearly_expired_contracts = self.search([('state', '=', 'open'),
                                                ('expiration_date', '<',
                                                 outdated_days)])

        for contract in nearly_expired_contracts.filtered(
                lambda contract: contract.user_id):
            contract.activity_schedule(
                'fleet.mail_act_fleet_contract_to_renew',
                contract.expiration_date,
                user_id=contract.user_id.id)

        expired_contracts = self.search([
            ('state', 'not in', ['expired', 'closed']),
            ('expiration_date', '<', fields.Date.today())
        ])
        expired_contracts.write({'state': 'expired'})

        futur_contracts = self.search([
            ('state', 'not in', ['futur', 'closed']),
            ('start_date', '>', fields.Date.today())
        ])
        futur_contracts.write({'state': 'futur'})

        now_running_contracts = self.search([('state', '=', 'futur'),
                                             ('start_date', '<=',
                                              fields.Date.today())])
        now_running_contracts.write({'state': 'open'})

    def run_scheduler(self):
        self.scheduler_manage_contract_expiration()
Ejemplo n.º 20
0
class AccountPurchaseLiquidacion(models.Model):
    _inherit = 'account.invoice'

    fecha_recepcion = fields.Date(string=u'Fecha de Recepción', index=True)
    fecha_corte = fields.Date(string=u'Fecha de Corte', index=True)
    fecha_pago = fields.Date(string=u'Fecha de Pago', index=True)
Ejemplo n.º 21
0
class ResCompany(models.Model):
    _inherit = 'hr.employee'

    hr_presence_state = fields.Selection([('present', 'Present'),
                                          ('absent', 'Absent'),
                                          ('to_define', 'To Define')],
                                         default='to_define')
    last_activity = fields.Date(compute="_compute_last_activity")

    def _compute_last_activity(self):
        employees = self.filtered(lambda e: e.user_id)
        presences = self.env['bus.presence'].search([
            ('user_id', 'in', employees.mapped('user_id.id'))
        ])

        for presence in presences:
            presence.user_id.employee_ids.last_activity = presence.last_presence.date(
            )

    @api.model
    def _check_presence(self):
        company = self.env.company_id
        if not company.hr_presence_last_compute_date or \
                company.hr_presence_last_compute_date.day != Datetime.now().day:
            self.env['hr.employee'].search([
                ('department_id.company_id', '=', company.id)
            ]).write({'hr_presence_state': 'to_define'})

        employees = self.env['hr.employee'].search([
            ('department_id.company_id', '=', company.id),
            ('user_id', '!=', False), ('hr_presence_state', '=', 'to_define')
        ])

        # Remove employees on holidays
        leaves = self.env['hr.leave'].search([
            ('state', '=', 'validate'),
            ('date_from', '<=', Datetime.to_string(Datetime.now())),
            ('date_to', '>=', Datetime.to_string(Datetime.now()))
        ])
        employees_on_holiday = leaves.mapped('employee_id')
        employees_on_holiday.write({'hr_presence_state': 'absent'})
        employees = employees - employees_on_holiday

        # Check on system login
        if self.env['ir.config_parameter'].sudo().get_param(
                'hr_presence.hr_presence_control_login'):
            online_employees = employees.filtered(
                lambda employee: employee.user_id.im_status in
                ['away', 'online'])
            online_employees.write({'hr_presence_state': 'present'})
            employees = employees - online_employees

        # Check on IP
        if self.env['ir.config_parameter'].sudo().get_param(
                'hr_presence.hr_presence_control_ip'):
            ip_list = company.hr_presence_control_ip_list
            ip_list = ip_list.split(',') if ip_list else []
            ip_employees = self.env['hr.employee']
            for employee in employees:
                employee_ips = self.env['res.users.log'].search([
                    ('create_uid', '=', employee.user_id.id),
                    ('ip', '!=', False),
                    ('create_date', '>=',
                     Datetime.to_string(Datetime.now().replace(hour=0,
                                                               minute=0,
                                                               second=0,
                                                               microsecond=0)))
                ]).mapped('ip')
                if any([ip in ip_list for ip in employee_ips]):
                    ip_employees |= employee
            ip_employees.write({'hr_presence_state': 'present'})
            employees = employees - ip_employees

        # Check on sent emails
        if self.env['ir.config_parameter'].sudo().get_param(
                'hr_presence.hr_presence_control_email'):
            email_employees = self.env['hr.employee']
            threshold = company.hr_presence_control_email_amount
            for employee in employees:
                sent_emails = self.env['mail.message'].search_count([
                    ('author_id', '=', employee.user_id.partner_id.id),
                    ('date', '>=',
                     Datetime.to_string(Datetime.now().replace(
                         hour=0, minute=0, second=0, microsecond=0))),
                    ('date', '<=', Datetime.to_string(Datetime.now()))
                ])
                if sent_emails >= threshold:
                    email_employees |= employee
            email_employees.write({'hr_presence_state': 'present'})
            employees = employees - email_employees

        company.hr_presence_last_compute_date = Datetime.now()

    @api.model
    def _action_open_presence_view(self):
        # Compute the presence/absence for the employees on the same
        # company than the HR/manager. Then opens the kanban view
        # of the employees with an undefined presence/absence

        _logger.info("Employees presence checked by: %s" % self.env.user.name)

        self._check_presence()

        return {
            "type":
            "ir.actions.act_window",
            "res_model":
            "hr.employee",
            "views": [[
                self.env.ref('hr_presence.hr_employee_view_kanban').id,
                "kanban"
            ], [False, "tree"], [False, "form"]],
            'view_mode':
            'kanban,tree,form',
            "domain": [],
            "name":
            "Employee's Presence to Define",
            "context": {
                'search_default_group_hr_presence_state': 1
            },
        }

    def action_set_present(self):
        if not self.env.user.has_group('hr.group_hr_manager'):
            raise UserError(
                _("You don't have the right to do this. Please contact an Administrator."
                  ))
        self.write({'hr_presence_state': 'present'})

    def action_open_leave_request(self):
        self.ensure_one()
        return {
            "type": "ir.actions.act_window",
            "res_model": "hr.leave",
            "views": [[False, "form"]],
            "view_mode": 'form',
            "context": {
                'default_employee_id': self.id
            },
        }

    def action_send_sms(self):
        self.ensure_one()
        if not self.env.user.has_group('hr.group_hr_manager'):
            raise UserError(
                _("You don't have the right to do this. Please contact an Administrator."
                  ))
        if not self.mobile_phone:
            raise UserError(
                _("There is no professional phone for this employee."))
        body = _(
            """Exception made if there was a mistake of ours, it seems that you are not at your office and there is not request of leaves from you.
Please, take appropriate measures in order to carry out this work absence.
Do not hesitate to contact your manager or the human resource department.""")
        return {
            "type": "ir.actions.act_window",
            "res_model": "sms.send_sms",
            "view_mode": 'form',
            "context": {
                'active_id': self.id,
                'default_message': body,
                'default_recipients': self.mobile_phone,
            },
            "name": "Send SMS",
            "target": "new",
        }

    def action_send_mail(self):
        self.ensure_one()
        if not self.env.user.has_group('hr.group_hr_manager'):
            raise UserError(
                _("You don't have the right to do this. Please contact an Administrator."
                  ))
        if not self.work_email:
            raise UserError(
                _("There is no professional email address for this employee."))
        template = self.env.ref('hr_presence.mail_template_presence', False)
        compose_form = self.env.ref('mail.email_compose_message_wizard_form',
                                    False)
        ctx = dict(
            default_model="hr.employee",
            default_res_id=self.id,
            default_use_template=bool(template),
            default_template_id=template.id,
            default_composition_mode='comment',
            default_is_log=True,
            custom_layout='mail.mail_notification_light',
        )
        return {
            'name': _('Compose Email'),
            'type': 'ir.actions.act_window',
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'mail.compose.message',
            'views': [(compose_form.id, 'form')],
            'view_id': compose_form.id,
            'target': 'new',
            'context': ctx,
        }
Ejemplo n.º 22
0
class Checkout(models.Model):
    _name = 'library.checkout'
    _description = 'Checkout Request'
    _inherit = ['mail.thread', 'mail.activity.mixin']

    @api.multi
    def name_get(self):
        names = []
        for rec in self:
            name = '%s/%s' % (rec.member_id, rec.request_date)
            names.append((rec.id, name))
        return names

    @api.model
    def _default_stage(self):
        Stage = self.env['library.checkout.stage']
        return Stage.search([], limit=1)

    @api.model
    def _group_expand_stage_id(self, stages, domain, order):
        return stages.search([], order=order)

    member_id = fields.Many2one(
        'library.member',
        required=True,
    )
    user_id = fields.Many2one(
        'res.users',
        'Librarian',
        default=lambda s: s.env.uid,
    )
    request_date = fields.Date(default=lambda s: fields.Date.today())
    line_ids = fields.One2many(
        'library.checkout.line',
        'checkout_id',
        string='Borrowed Books',
    )
    stage_id = fields.Many2one(
        'library.checkout.stage',
        default=_default_stage,
        group_expand='_group_expand_stage_id',
    )
    state = fields.Selection(related='stage_id.state')

    checkout_date = fields.Date()
    closed_date = fields.Date()

    @api.onchange('member_id')
    def onchange_member_id(self):
        today = fields.Date.today()
        if self.request_date != today:
            self.request_date = fields.Date.today()
            return {
                'warning': {
                    'title': 'Changed Request Date',
                    'message': 'Request date changed to today.'
                }
            }

    @api.model
    def create(self, vals):
        # Code before create: should use the `vals` dict
        if 'stage_id' in vals:
            Stage = self.env['library.checkout.stage']
            new_state = Stage.browse(vals['stage_id']).state
            if new_state == 'open':
                vals['checkout_date'] = fields.Date.today()
        new_record = super().create(vals)
        # Code after create: can use the `new_record` created
        if new_record.state == 'done':
            raise exceptions.UserError(
                'Not allowed to create a checkout in the done state.')
        return new_record

    @api.multi
    def write(self, vals):
        # Code before write: can use `self`, with the old values
        if 'stage_id' in vals:
            Stage = self.env['library.checkout.stage']
            new_state = Stage.browse(vals['stage_id']).state
            if new_state == 'open' and self.state != 'open':
                vals['checkout_date'] = fields.Date.today()
            if new_state == 'done' and self.state != 'done':
                vals['close_date'] = fields.Date.today()
        super().write(vals)
        # Code after write: can use `self`, with the updated values
        return True
Ejemplo n.º 23
0
class OpenItemsReportWizard(models.TransientModel):
    """Open items report wizard."""

    _name = "open.items.report.wizard"
    _description = "Open Items Report Wizard"
    _inherit = "account_financial_report_abstract_wizard"

    company_id = fields.Many2one(
        comodel_name="res.company",
        default=lambda self: self.env.user.company_id,
        required=False,
        string="Company",
    )
    date_at = fields.Date(required=True, default=fields.Date.context_today)
    date_from = fields.Date(string="Date From")
    target_move = fields.Selection(
        [("posted", "All Posted Entries"), ("all", "All Entries")],
        string="Target Moves",
        required=True,
        default="posted",
    )
    account_ids = fields.Many2many(
        comodel_name="account.account",
        string="Filter accounts",
        domain=[("reconcile", "=", True)],
        required=True,
    )
    hide_account_at_0 = fields.Boolean(
        string="Hide account ending balance at 0",
        default=True,
        help="Use this filter to hide an account or a partner "
        "with an ending balance at 0. "
        "If partners are filtered, "
        "debits and credits totals will not match the trial balance.",
    )
    receivable_accounts_only = fields.Boolean()
    payable_accounts_only = fields.Boolean()
    partner_ids = fields.Many2many(
        comodel_name="res.partner",
        string="Filter partners",
        default=lambda self: self._default_partners(),
    )
    foreign_currency = fields.Boolean(
        string="Show foreign currency",
        help="Display foreign currency for move lines, unless "
        "account currency is not setup through chart of accounts "
        "will display initial and final balance in that currency.",
        default=lambda self: self._default_foreign_currency(),
    )
    show_partner_details = fields.Boolean(
        string="Show Partner Details",
        default=True,
    )
    account_code_from = fields.Many2one(
        comodel_name="account.account",
        string="Account Code From",
        help="Starting account in a range",
    )
    account_code_to = fields.Many2one(
        comodel_name="account.account",
        string="Account Code To",
        help="Ending account in a range",
    )

    @api.onchange("account_code_from", "account_code_to")
    def on_change_account_range(self):
        if (self.account_code_from and self.account_code_from.code.isdigit()
                and self.account_code_to
                and self.account_code_to.code.isdigit()):
            start_range = int(self.account_code_from.code)
            end_range = int(self.account_code_to.code)
            self.account_ids = self.env["account.account"].search([
                ("code", "in", [x for x in range(start_range, end_range + 1)]),
                ("reconcile", "=", True),
            ])
            if self.company_id:
                self.account_ids = self.account_ids.filtered(
                    lambda a: a.company_id == self.company_id)
        return {
            "domain": {
                "account_code_from": [("reconcile", "=", True)],
                "account_code_to": [("reconcile", "=", True)],
            }
        }

    def _default_foreign_currency(self):
        return self.env.user.has_group("base.group_multi_currency")

    @api.onchange("company_id")
    def onchange_company_id(self):
        """Handle company change."""
        if self.company_id and self.partner_ids:
            self.partner_ids = self.partner_ids.filtered(
                lambda p: p.company_id == self.company_id or not p.company_id)
        if self.company_id and self.account_ids:
            if self.receivable_accounts_only or self.payable_accounts_only:
                self.onchange_type_accounts_only()
            else:
                self.account_ids = self.account_ids.filtered(
                    lambda a: a.company_id == self.company_id)
        res = {"domain": {"account_ids": [], "partner_ids": []}}
        if not self.company_id:
            return res
        else:
            res["domain"]["account_ids"] += [("company_id", "=",
                                              self.company_id.id)]
            res["domain"]["partner_ids"] += self._get_partner_ids_domain()
        return res

    @api.onchange("account_ids")
    def onchange_account_ids(self):
        return {"domain": {"account_ids": [("reconcile", "=", True)]}}

    @api.onchange("receivable_accounts_only", "payable_accounts_only")
    def onchange_type_accounts_only(self):
        """Handle receivable/payable accounts only change."""
        domain = [("company_id", "=", self.company_id.id)]
        if self.receivable_accounts_only or self.payable_accounts_only:
            if self.receivable_accounts_only and self.payable_accounts_only:
                domain += [("internal_type", "in", ("receivable", "payable"))]
            elif self.receivable_accounts_only:
                domain += [("internal_type", "=", "receivable")]
            elif self.payable_accounts_only:
                domain += [("internal_type", "=", "payable")]
            self.account_ids = self.env["account.account"].search(domain)
        else:
            self.account_ids = None

    def _print_report(self, report_type):
        self.ensure_one()
        data = self._prepare_report_open_items()
        if report_type == "xlsx":
            report_name = "a_f_r.report_open_items_xlsx"
        else:
            report_name = "account_financial_report.open_items"
        return (self.env["ir.actions.report"].search(
            [("report_name", "=", report_name),
             ("report_type", "=", report_type)],
            limit=1,
        ).report_action(self, data=data))

    def button_export_html(self):
        self.ensure_one()
        report_type = "qweb-html"
        return self._export(report_type)

    def button_export_pdf(self):
        self.ensure_one()
        report_type = "qweb-pdf"
        return self._export(report_type)

    def button_export_xlsx(self):
        self.ensure_one()
        report_type = "xlsx"
        return self._export(report_type)

    def _prepare_report_open_items(self):
        self.ensure_one()
        return {
            "wizard_id": self.id,
            "date_at": fields.Date.to_string(self.date_at),
            "date_from": self.date_from or False,
            "only_posted_moves": self.target_move == "posted",
            "hide_account_at_0": self.hide_account_at_0,
            "foreign_currency": self.foreign_currency,
            "show_partner_details": self.show_partner_details,
            "company_id": self.company_id.id,
            "target_move": self.target_move,
            "account_ids": self.account_ids.ids,
            "partner_ids": self.partner_ids.ids or [],
            "account_financial_report_lang": self.env.lang,
        }

    def _export(self, report_type):
        return self._print_report(report_type)
class WizardSelectMoveTemplate(models.TransientModel):
    _name = "wizard.select.move.template"

    template_id = fields.Many2one('account.move.template', required=True)
    company_id = fields.Many2one(
        'res.company', required=True,
        default=lambda self: self.env.user.company_id)
    partner_id = fields.Many2one('res.partner', 'Partner')
    date = fields.Date(required=True, default=fields.Date.context_today)
    line_ids = fields.One2many(
        'wizard.select.move.template.line', 'template_id')
    state = fields.Selection(
        [('template_selected', 'Template selected')])

    @api.onchange('template_id', 'company_id')
    def onchange_company_id(self):
        template_domain = [('company_id', '=', self.company_id.id)]
        return {'domain': {'template_id': template_domain}}

    @api.multi
    def load_lines(self):
        self.ensure_one()
        lines = self.template_id.template_line_ids
        for line in lines.filtered(lambda l: l.type == 'input'):
            self.env['wizard.select.move.template.line'].create({
                'template_id': self.id,
                'sequence': line.sequence,
                'name': line.name,
                'amount': 0.0,
                'account_id': line.account_id.id,
                'partner_id': line.partner_id.id or self.partner_id.id,
                'move_line_type': line.move_line_type,
                'tax_ids': [(6, 0, line.tax_ids.ids)],
                'tax_line_id': line.tax_line_id.id,
                'analytic_account_id': line.analytic_account_id.id,
                'analytic_tag_ids': [(6, 0, line.analytic_tag_ids.ids)],
            })
        if not self.line_ids:
            return self.load_template()
        self.state = 'template_selected'
        view_rec = self.env.ref('account_move_template.wizard_select_template')
        action = self.env.ref(
            'account_move_template.action_wizard_select_template_by_move')
        result = action.read()[0]
        result['res_id'] = self.id
        result['view_id'] = [view_rec.id]
        result['context'] = self.env.context
        return result

    @api.multi
    def load_template(self):
        self.ensure_one()
        input_lines = {}
        for template_line in self.line_ids:
            input_lines[template_line.sequence] = template_line.amount
        amounts = self.template_id.compute_lines(input_lines)
        name = self.template_id.name
        partner = self.partner_id.id
        moves = self.env['account.move']
        for journal in self.template_id.template_line_ids.mapped('journal_id'):
            lines = []
            move = self._create_move(name, journal.id)
            moves = moves + move
            for line in self.template_id.template_line_ids.filtered(
                    lambda l, j=journal: l.journal_id == j):
                lines.append((0, 0,
                              self._prepare_line(line, amounts, partner)))
            move.write({'line_ids': lines})
        action = self.env.ref('account.action_move_journal_line')
        result = action.read()[0]
        result['domain'] = [('id', 'in', moves.ids)]
        result['name'] = _('Entries from template: %s') % name
        result['context'] = self.env.context
        return result

    @api.model
    def _create_move(self, ref, journal_id):
        return self.env['account.move'].create({
            'ref': ref,
            'journal_id': journal_id,
            'date': self.date,
        })

    @api.model
    def _prepare_line(self, line, amounts, partner_id):
        debit = line.move_line_type == 'dr'
        values = {
            'name': line.name,
            'journal_id': line.journal_id.id,
            'analytic_account_id': line.analytic_account_id.id,
            'account_id': line.account_id.id,
            'credit': not debit and amounts[line.sequence] or 0.0,
            'debit': debit and amounts[line.sequence] or 0.0,
            'partner_id': line.partner_id.id or partner_id,
            'tax_line_id': line.tax_line_id.id,
        }
        if line.analytic_tag_ids:
            values['analytic_tag_ids'] = [(6, 0, line.analytic_tag_ids.ids)]
        if line.tax_ids:
            values['tax_ids'] = [(6, 0, line.tax_ids.ids)]
        return values
Ejemplo n.º 25
0
class ProjectIssue(models.Model):
    _name = "project.issue"
    _description = "Project Issue"
    _inherit = ['mail.thread', 'ir.needaction_mixin']
    _order = "priority desc, create_date desc"
    _mail_post_access = 'read'

    @api.model
    def _get_default_stage_id(self):
        project_id = self.env.context.get('default_project_id')
        if not project_id:
            return False
        return self.stage_find(project_id, [('fold', '=', False)])

    name = fields.Char(string='Issue', required=True)
    active = fields.Boolean(default=True)
    days_since_creation = fields.Integer(compute='_compute_inactivity_days', string='Days since creation date',
                                         help="Difference in days between creation date and current date")
    date_deadline = fields.Date(string='Deadline')
    partner_id = fields.Many2one('res.partner', string='Contact', index=True)
    company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.user.company_id)
    description = fields.Text('Private Note')
    kanban_state = fields.Selection([('normal', 'Normal'), ('blocked', 'Blocked'), ('done', 'Ready for next stage')], string='Kanban State',
                                    track_visibility='onchange', required=True, default='normal',
                                    help="""An Issue's kanban state indicates special situations affecting it:\n
                                           * Normal is the default situation\n
                                           * Blocked indicates something is preventing the progress of this issue\n
                                           * Ready for next stage indicates the issue is ready to be pulled to the next stage""")
    email_from = fields.Char(string='Email', help="These people will receive email.", index=True)
    email_cc = fields.Char(string='Watchers Emails', help="""These email addresses will be added to the CC field of all inbound
        and outbound emails for this record before being sent. Separate multiple email addresses with a comma""")
    date_open = fields.Datetime(string='Assigned', readonly=True, index=True)
    date_closed = fields.Datetime(string='Closed', readonly=True, index=True)
    date = fields.Datetime('Date')
    date_last_stage_update = fields.Datetime(string='Last Stage Update', index=True, default=fields.Datetime.now)
    channel = fields.Char(string='Channel', help="Communication channel.")  # TDE note: is it still used somewhere ?
    tag_ids = fields.Many2many('project.tags', string='Tags')
    priority = fields.Selection([('0', 'Low'), ('1', 'Normal'), ('2', 'High')], 'Priority', index=True, default='0')
    stage_id = fields.Many2one('project.task.type', string='Stage', track_visibility='onchange', index=True,
                               domain="[('project_ids', '=', project_id)]", copy=False,
                               group_expand='_read_group_stage_ids',
                               default=_get_default_stage_id)
    project_id = fields.Many2one('project.project', string='Project', track_visibility='onchange', index=True)
    duration = fields.Float('Duration')
    task_id = fields.Many2one('project.task', string='Task', domain="[('project_id','=',project_id)]",
                              help="You can link this issue to an existing task or directly create a new one from here")
    day_open = fields.Float(compute='_compute_day', string='Days to Assign', store=True)
    day_close = fields.Float(compute='_compute_day', string='Days to Close', store=True)

    user_id = fields.Many2one('res.users', string='Assigned to', index=True, track_visibility='onchange', default=lambda self: self.env.uid)
    working_hours_open = fields.Float(compute='_compute_day', string='Working Hours to assign the Issue', store=True)
    working_hours_close = fields.Float(compute='_compute_day', string='Working Hours to close the Issue', store=True)
    inactivity_days = fields.Integer(compute='_compute_inactivity_days', string='Days since last action',
                                     help="Difference in days between last action and current date")
    color = fields.Integer('Color Index')
    user_email = fields.Char(related='user_id.email', string='User Email', readonly=True)
    date_action_last = fields.Datetime(string='Last Action', readonly=True)
    date_action_next = fields.Datetime(string='Next Action', readonly=True)
    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)

    @api.model
    def _read_group_stage_ids(self, stages, domain, order):
        search_domain = [('id', 'in', stages.ids)]
        # retrieve project_id from the context, add them to already fetched columns (ids)
        if 'default_project_id' in self.env.context:
            search_domain = ['|', ('project_ids', '=', self.env.context['default_project_id'])] + search_domain
        # perform search
        return stages.search(search_domain, order=order)

    @api.multi
    @api.depends('create_date', 'date_closed', 'date_open')
    def _compute_day(self):
        for issue in self:
            # if the working hours on the project are not defined, use default ones (8 -> 12 and 13 -> 17 * 5)
            calendar = issue.project_id.resource_calendar_id

            dt_create_date = fields.Datetime.from_string(issue.create_date)
            if issue.date_open:
                dt_date_open = fields.Datetime.from_string(issue.date_open)
                issue.day_open = (dt_date_open - dt_create_date).total_seconds() / (24.0 * 3600)
                issue.working_hours_open = calendar.get_working_hours(dt_create_date, dt_date_open,
                    compute_leaves=True, resource_id=False, default_interval=(8, 16))

            if issue.date_closed:
                dt_date_closed = fields.Datetime.from_string(issue.date_closed)
                issue.day_close = (dt_date_closed - dt_create_date).total_seconds() / (24.0 * 3600)
                issue.working_hours_close = calendar.get_working_hours(dt_create_date, dt_date_closed,
                    compute_leaves=True, resource_id=False, default_interval=(8, 16))

    @api.multi
    @api.depends('create_date', 'date_action_last', 'date_last_stage_update')
    def _compute_inactivity_days(self):
        current_datetime = fields.Datetime.from_string(fields.Datetime.now())
        for issue in self:
            dt_create_date = fields.Datetime.from_string(issue.create_date)
            issue.days_since_creation = (current_datetime - dt_create_date).days

            if issue.date_action_last:
                issue.inactivity_days = (current_datetime - fields.Datetime.from_string(issue.date_action_last)).days
            elif issue.date_last_stage_update:
                issue.inactivity_days = (current_datetime - fields.Datetime.from_string(issue.date_last_stage_update)).days
            else:
                issue.inactivity_days = (current_datetime - dt_create_date).days

    @api.onchange('partner_id')
    def _onchange_partner_id(self):
        """ This function sets partner email address based on partner
        """
        self.email_from = self.partner_id.email

    @api.onchange('project_id')
    def _onchange_project_id(self):
        if self.project_id:
            if not self.partner_id and not self.email_from:
                self.partner_id = self.project_id.partner_id.id
                self.email_from = self.project_id.partner_id.email
            self.stage_id = self.stage_find(self.project_id.id, [('fold', '=', False)])
        else:
            self.partner_id = False
            self.email_from = False
            self.stage_id = False

    @api.onchange('task_id')
    def _onchange_task_id(self):
        self.user_id = self.task_id.user_id

    @api.multi
    def copy(self, default=None):
        if default is None:
            default = {}
        default.update(name=_('%s (copy)') % (self.name))
        return super(ProjectIssue, self).copy(default=default)

    @api.model
    def create(self, vals):
        context = dict(self.env.context)
        if vals.get('project_id') and not self.env.context.get('default_project_id'):
            context['default_project_id'] = vals.get('project_id')
        if vals.get('user_id') and not vals.get('date_open'):
            vals['date_open'] = fields.Datetime.now()
        if 'stage_id' in vals:
            vals.update(self.update_date_closed(vals['stage_id']))

        # context: no_log, because subtype already handle this
        context['mail_create_nolog'] = True
        return super(ProjectIssue, self.with_context(context)).create(vals)

    @api.multi
    def write(self, vals):
        # stage change: update date_last_stage_update
        if 'stage_id' in vals:
            vals.update(self.update_date_closed(vals['stage_id']))
            vals['date_last_stage_update'] = fields.Datetime.now()
            if 'kanban_state' not in vals:
                vals['kanban_state'] = 'normal'
        # user_id change: update date_open
        if vals.get('user_id') and 'date_open' not in vals:
            vals['date_open'] = fields.Datetime.now()
        return super(ProjectIssue, self).write(vals)

    @api.model
    def get_empty_list_help(self, help):
        return super(ProjectIssue, self.with_context(
            empty_list_help_model='project.project',
            empty_list_help_id=self.env.context.get('default_project_id'),
            empty_list_help_document_name=_("issues")
        )).get_empty_list_help(help)

    # -------------------------------------------------------
    # Stage management
    # -------------------------------------------------------

    def update_date_closed(self, stage_id):
        project_task_type = self.env['project.task.type'].browse(stage_id)
        if project_task_type.fold:
            return {'date_closed': fields.Datetime.now()}
        return {'date_closed': False}

    def stage_find(self, project_id, domain=None, order='sequence'):
        """ Override of the base.stage method
            Parameter of the stage search taken from the issue:
            - project_id: if set, stages must belong to this project or
              be a default case
        """
        search_domain = list(domain) if domain else []
        if project_id:
            search_domain += [('project_ids', '=', project_id)]
        project_task_type = self.env['project.task.type'].search(search_domain, order=order, limit=1)
        return project_task_type.id

    # -------------------------------------------------------
    # Mail gateway
    # -------------------------------------------------------

    @api.multi
    def _track_template(self, tracking):
        self.ensure_one()
        res = super(ProjectIssue, self)._track_template(tracking)
        changes, dummy = tracking[self.id]
        if 'stage_id' in changes and self.stage_id.mail_template_id:
            res['stage_id'] = (self.stage_id.mail_template_id, {'composition_mode': 'mass_mail'})
        return res

    def _track_subtype(self, init_values):
        self.ensure_one()
        if 'kanban_state' in init_values and self.kanban_state == 'blocked':
            return 'project_issue.mt_issue_blocked'
        elif 'kanban_state' in init_values and self.kanban_state == 'done':
            return 'project_issue.mt_issue_ready'
        elif 'user_id' in init_values and self.user_id:  # assigned -> new
            return 'project_issue.mt_issue_new'
        elif 'stage_id' in init_values and self.stage_id and self.stage_id.sequence <= 1:  # start stage -> new
            return 'project_issue.mt_issue_new'
        elif 'stage_id' in init_values:
            return 'project_issue.mt_issue_stage'
        return super(ProjectIssue, self)._track_subtype(init_values)

    @api.multi
    def _notification_recipients(self, message, groups):
        """
        """
        groups = super(ProjectIssue, self)._notification_recipients(message, groups)

        self.ensure_one()
        if not self.user_id:
            take_action = self._notification_link_helper('assign')
            project_actions = [{'url': take_action, 'title': _('I take it')}]
        else:
            new_action_id = self.env.ref('project_issue.project_issue_categ_act0').id
            new_action = self._notification_link_helper('new', action_id=new_action_id)
            project_actions = [{'url': new_action, 'title': _('New Issue')}]

        new_group = (
            'group_project_user', lambda partner: bool(partner.user_ids) and any(user.has_group('project.group_project_user') for user in partner.user_ids), {
                'actions': project_actions,
            })

        return [new_group] + groups

    @api.model
    def message_get_reply_to(self, res_ids, default=None):
        """ Override to get the reply_to of the parent project. """
        issues = self.browse(res_ids)
        project_ids = set(issues.mapped('project_id').ids)
        aliases = self.env['project.project'].message_get_reply_to(list(project_ids), default=default)
        return dict((issue.id, aliases.get(issue.project_id and issue.project_id.id or 0, False)) for issue in issues)

    @api.multi
    def message_get_suggested_recipients(self):
        recipients = super(ProjectIssue, self).message_get_suggested_recipients()
        try:
            for issue in self:
                if issue.partner_id:
                    issue._message_add_suggested_recipient(recipients, partner=issue.partner_id, reason=_('Customer'))
                elif issue.email_from:
                    issue._message_add_suggested_recipient(recipients, email=issue.email_from, reason=_('Customer Email'))
        except AccessError:  # no read access rights -> just ignore suggested recipients because this imply modifying followers
            pass
        return recipients

    @api.multi
    def email_split(self, msg):
        email_list = tools.email_split((msg.get('to') or '') + ',' + (msg.get('cc') or ''))
        # check left-part is not already an alias
        return filter(lambda x: x.split('@')[0] not in self.mapped('project_id.alias_name'), email_list)

    @api.model
    def message_new(self, msg, custom_values=None):
        """ Overrides mail_thread message_new that is called by the mailgateway
            through message_process.
            This override updates the document according to the email.
        """
        # remove default author when going through the mail gateway. Indeed we
        # do not want to explicitly set user_id to False; however we do not
        # want the gateway user to be responsible if no other responsible is
        # found.
        create_context = dict(self.env.context or {})
        create_context['default_user_id'] = False
        defaults = {
            'name':  msg.get('subject') or _("No Subject"),
            'email_from': msg.get('from'),
            'email_cc': msg.get('cc'),
            'partner_id': msg.get('author_id', False),
        }
        if custom_values:
            defaults.update(custom_values)

        res_id = super(ProjectIssue, self.with_context(create_context)).message_new(msg, custom_values=defaults)
        issue = self.browse(res_id)
        email_list = issue.email_split(msg)
        partner_ids = filter(None, issue._find_partner_from_emails(email_list))
        issue.message_subscribe(partner_ids)
        return res_id

    @api.multi
    def message_update(self, msg, update_vals=None):
        """ Override to update the issue according to the email. """
        email_list = self.email_split(msg)
        partner_ids = filter(None, self._find_partner_from_emails(email_list))
        self.message_subscribe(partner_ids)
        return super(ProjectIssue, self).message_update(msg, update_vals=update_vals)

    @api.multi
    @api.returns('mail.message', lambda value: value.id)
    def message_post(self, subtype=None, **kwargs):
        """ Overrides mail_thread message_post so that we can set the date of last action field when
            a new message is posted on the issue.
        """
        self.ensure_one()
        mail_message = super(ProjectIssue, self).message_post(subtype=subtype, **kwargs)
        if subtype:
            self.sudo().write({'date_action_last': fields.Datetime.now()})
        return mail_message

    @api.multi
    def message_get_email_values(self, notif_mail=None):
        self.ensure_one()
        res = super(ProjectIssue, self).message_get_email_values(notif_mail=notif_mail)
        headers = {}
        if res.get('headers'):
            try:
                headers.update(safe_eval(res['headers']))
            except Exception:
                pass
        if self.project_id:
            current_objects = filter(None, headers.get('X-Odoo-Objects', '').split(','))
            current_objects.insert(0, 'project.project-%s, ' % self.project_id.id)
            headers['X-Odoo-Objects'] = ','.join(current_objects)
        if self.tag_ids:
            headers['X-Odoo-Tags'] = ','.join(self.tag_ids.mapped('name'))
        res['headers'] = repr(headers)
        return res
Ejemplo n.º 26
0
class MisReportInstance(models.Model):
    """The MIS report instance combines everything to compute
    a MIS report template for a set of periods."""
    @api.depends("date")
    def _compute_pivot_date(self):
        for record in self:
            if record.date:
                record.pivot_date = record.date
            else:
                record.pivot_date = fields.Date.context_today(record)

    @api.model
    def _default_company_id(self):
        default_company_id = self.env["res.company"]._company_default_get(
            "mis.report.instance")
        return default_company_id

    _name = "mis.report.instance"
    _description = "MIS Report Instance"

    name = fields.Char(required=True, string="Name", translate=True)
    description = fields.Char(related="report_id.description", readonly=True)
    date = fields.Date(string="Base date",
                       help="Report base date "
                       "(leave empty to use current date)")
    pivot_date = fields.Date(compute="_compute_pivot_date",
                             string="Pivot date")
    report_id = fields.Many2one("mis.report", required=True, string="Report")
    period_ids = fields.One2many(
        comodel_name="mis.report.instance.period",
        inverse_name="report_instance_id",
        required=True,
        string="Periods",
        copy=True,
    )
    target_move = fields.Selection(
        [("posted", "All Posted Entries"), ("all", "All Entries")],
        string="Target Moves",
        required=True,
        default="posted",
    )
    company_id = fields.Many2one(
        comodel_name="res.company",
        string="Company",
        default=_default_company_id,
        required=True,
    )
    multi_company = fields.Boolean(
        string="Multiple companies",
        help="Check if you wish to specify "
        "children companies to be searched for data.",
        default=False,
    )
    company_ids = fields.Many2many(
        comodel_name="res.company",
        string="Companies",
        help="Select companies for which data will be searched.",
    )
    query_company_ids = fields.Many2many(
        comodel_name="res.company",
        compute="_compute_query_company_ids",
        help="Companies for which data will be searched.",
    )
    currency_id = fields.Many2one(
        comodel_name="res.currency",
        string="Currency",
        help="Select target currency for the report. "
        "Required if companies have different currencies.",
        required=False,
    )
    landscape_pdf = fields.Boolean(string="Landscape PDF")
    no_auto_expand_accounts = fields.Boolean(
        string="Disable account details expansion")
    display_columns_description = fields.Boolean(
        help="Display the date range details in the column headers.")
    comparison_mode = fields.Boolean(compute="_compute_comparison_mode",
                                     inverse="_inverse_comparison_mode")
    date_range_id = fields.Many2one(comodel_name="date.range",
                                    string="Date Range")
    date_from = fields.Date(string="From")
    date_to = fields.Date(string="To")
    temporary = fields.Boolean(default=False)
    analytic_account_id = fields.Many2one(
        comodel_name="account.analytic.account",
        string="Analytic Account",
        oldname="account_analytic_id",
    )
    analytic_tag_ids = fields.Many2many(comodel_name="account.analytic.tag",
                                        string="Analytic Tags")
    hide_analytic_filters = fields.Boolean(default=True)

    @api.onchange("company_id", "multi_company")
    def _onchange_company(self):
        if self.company_id and self.multi_company:
            self.company_ids = self.env["res.company"].search([
                ("id", "child_of", self.company_id.id)
            ])
        else:
            self.company_ids = False

    @api.multi
    @api.depends("multi_company", "company_id", "company_ids")
    def _compute_query_company_ids(self):
        for rec in self:
            if rec.multi_company:
                rec.query_company_ids = rec.company_ids or rec.company_id
            else:
                rec.query_company_ids = rec.company_id

    @api.model
    def get_filter_descriptions_from_context(self):
        filters = self.env.context.get("mis_report_filters", {})
        analytic_account_id = filters.get("analytic_account_id",
                                          {}).get("value")
        filter_descriptions = []
        if analytic_account_id:
            analytic_account = self.env["account.analytic.account"].browse(
                analytic_account_id)
            filter_descriptions.append(
                _("Analytic Account: %s") % analytic_account.display_name)
        analytic_tag_value = filters.get("analytic_tag_ids", {}).get("value")
        if analytic_tag_value:
            analytic_tag_names = self.resolve_2many_commands(
                "analytic_tag_ids", analytic_tag_value, ["name"])
            filter_descriptions.append(
                _("Analytic Tags: %s") %
                ", ".join([rec["name"] for rec in analytic_tag_names]))
        return filter_descriptions

    @api.multi
    def save_report(self):
        self.ensure_one()
        self.write({"temporary": False})
        action = self.env.ref("mis_builder.mis_report_instance_view_action")
        res = action.read()[0]
        view = self.env.ref("mis_builder.mis_report_instance_view_form")
        res.update({"views": [(view.id, "form")], "res_id": self.id})
        return res

    @api.model
    def _vacuum_report(self, hours=24):
        clear_date = fields.Datetime.to_string(datetime.datetime.now() -
                                               datetime.timedelta(hours=hours))
        reports = self.search([("write_date", "<", clear_date),
                               ("temporary", "=", True)])
        _logger.debug("Vacuum %s Temporary MIS Builder Report", len(reports))
        return reports.unlink()

    @api.multi
    def copy(self, default=None):
        self.ensure_one()
        default = dict(default or {})
        default["name"] = _("%s (copy)") % self.name
        return super(MisReportInstance, self).copy(default)

    def _format_date(self, date):
        # format date following user language
        lang_model = self.env["res.lang"]
        lang = lang_model._lang_get(self.env.user.lang)
        date_format = lang.date_format
        return datetime.datetime.strftime(fields.Date.from_string(date),
                                          date_format)

    @api.multi
    @api.depends("date_from")
    def _compute_comparison_mode(self):
        for instance in self:
            instance.comparison_mode = bool(
                instance.period_ids) and not bool(instance.date_from)

    @api.multi
    def _inverse_comparison_mode(self):
        for record in self:
            if not record.comparison_mode:
                if not record.date_from:
                    record.date_from = fields.Date.context_today(self)
                if not record.date_to:
                    record.date_to = fields.Date.context_today(self)
                record.period_ids.unlink()
                record.write({"period_ids": [(0, 0, {"name": "Default"})]})
            else:
                record.date_from = None
                record.date_to = None

    @api.onchange("date_range_id")
    def _onchange_date_range(self):
        if self.date_range_id:
            self.date_from = self.date_range_id.date_start
            self.date_to = self.date_range_id.date_end

    @api.onchange("date_from", "date_to")
    def _onchange_dates(self):
        if self.date_range_id:
            if (self.date_from != self.date_range_id.date_start
                    or self.date_to != self.date_range_id.date_end):
                self.date_range_id = False

    @api.multi
    def _add_analytic_filters_to_context(self, context):
        self.ensure_one()
        if self.analytic_account_id:
            context["mis_report_filters"]["analytic_account_id"] = {
                "value": self.analytic_account_id.id,
                "operator": "=",
            }
        if self.analytic_tag_ids:
            context["mis_report_filters"]["analytic_tag_ids"] = {
                "value": self.analytic_tag_ids.ids,
                "operator": "all",
            }

    @api.multi
    def _context_with_filters(self):
        self.ensure_one()
        if "mis_report_filters" in self.env.context:
            # analytic filters are already in context, do nothing
            return self.env.context
        context = dict(self.env.context, mis_report_filters={})
        self._add_analytic_filters_to_context(context)
        return context

    @api.multi
    def preview(self):
        self.ensure_one()
        view_id = self.env.ref("mis_builder."
                               "mis_report_instance_result_view_form")
        return {
            "type": "ir.actions.act_window",
            "res_model": "mis.report.instance",
            "res_id": self.id,
            "view_mode": "form",
            "view_type": "form",
            "view_id": view_id.id,
            "target": "current",
            "context": self._context_with_filters(),
        }

    @api.multi
    def print_pdf(self):
        self.ensure_one()
        context = dict(self._context_with_filters(),
                       landscape=self.landscape_pdf)
        return (self.env["report"].with_context(context).get_action(
            self,
            "mis_builder.report_mis_report_instance",
            data=dict(dummy=True),  # required to propagate context
        ))

    @api.multi
    def export_xls(self):
        self.ensure_one()
        context = dict(self._context_with_filters())
        return (self.env["report"].with_context(context).get_action(
            self, "mis.report.instance.xlsx"))

    @api.multi
    def display_settings(self):
        assert len(self.ids) <= 1
        view_id = self.env.ref("mis_builder.mis_report_instance_view_form")
        return {
            "type": "ir.actions.act_window",
            "res_model": "mis.report.instance",
            "res_id": self.id if self.id else False,
            "view_mode": "form",
            "view_type": "form",
            "views": [(view_id.id, "form")],
            "view_id": view_id.id,
            "target": "current",
        }

    def _add_column_actuals(self, aep, kpi_matrix, period, label, description):
        if not period.date_from or not period.date_to:
            raise UserError(
                _("Column %s with actuals source "
                  "must have from/to dates.") % (period.name, ))
        self.report_id.declare_and_compute_period(
            kpi_matrix,
            period.id,
            label,
            description,
            aep,
            period.date_from,
            period.date_to,
            self.target_move,
            period.subkpi_ids,
            period._get_additional_move_line_filter,
            period._get_additional_query_filter,
            aml_model=self.report_id.move_lines_source.model,
            no_auto_expand_accounts=self.no_auto_expand_accounts,
        )

    def _add_column_actuals_alt(self, aep, kpi_matrix, period, label,
                                description):
        if not period.date_from or not period.date_to:
            raise UserError(
                _("Column %s with actuals source "
                  "must have from/to dates.") % (period.name, ))
        self.report_id.declare_and_compute_period(
            kpi_matrix,
            period.id,
            label,
            description,
            aep,
            period.date_from,
            period.date_to,
            None,
            period.subkpi_ids,
            period._get_additional_move_line_filter,
            period._get_additional_query_filter,
            aml_model=period.source_aml_model_id.model,
            no_auto_expand_accounts=self.no_auto_expand_accounts,
        )

    def _add_column_sumcol(self, aep, kpi_matrix, period, label, description):
        kpi_matrix.declare_sum(
            period.id,
            [(c.sign, c.period_to_sum_id.id)
             for c in period.source_sumcol_ids],
            label,
            description,
            period.source_sumcol_accdet,
        )

    def _add_column_cmpcol(self, aep, kpi_matrix, period, label, description):
        kpi_matrix.declare_comparison(
            period.id,
            period.source_cmpcol_to_id.id,
            period.source_cmpcol_from_id.id,
            label,
            description,
        )

    def _add_column(self, aep, kpi_matrix, period, label, description):
        if period.source == SRC_ACTUALS:
            return self._add_column_actuals(aep, kpi_matrix, period, label,
                                            description)
        elif period.source == SRC_ACTUALS_ALT:
            return self._add_column_actuals_alt(aep, kpi_matrix, period, label,
                                                description)
        elif period.source == SRC_SUMCOL:
            return self._add_column_sumcol(aep, kpi_matrix, period, label,
                                           description)
        elif period.source == SRC_CMPCOL:
            return self._add_column_cmpcol(aep, kpi_matrix, period, label,
                                           description)

    @api.multi
    def _compute_matrix(self):
        """ Compute a report and return a KpiMatrix.

        The key attribute of the matrix columns (KpiMatrixCol)
        is guaranteed to be the id of the mis.report.instance.period.
        """
        self.ensure_one()
        aep = self.report_id._prepare_aep(self.query_company_ids,
                                          self.currency_id)
        kpi_matrix = self.report_id.prepare_kpi_matrix(self.multi_company)
        for period in self.period_ids:
            description = None
            if period.mode == MODE_NONE:
                pass
            elif not self.display_columns_description:
                pass
            elif period.date_from == period.date_to and period.date_from:
                description = self._format_date(period.date_from)
            elif period.date_from and period.date_to:
                date_from = self._format_date(period.date_from)
                date_to = self._format_date(period.date_to)
                description = _("from %s to %s") % (date_from, date_to)
            self._add_column(aep, kpi_matrix, period, period.name, description)
        kpi_matrix.compute_comparisons()
        kpi_matrix.compute_sums()
        return kpi_matrix

    @api.multi
    def compute(self):
        self.ensure_one()
        kpi_matrix = self._compute_matrix()
        return kpi_matrix.as_dict()

    @api.multi
    def drilldown(self, arg):
        self.ensure_one()
        period_id = arg.get("period_id")
        expr = arg.get("expr")
        account_id = arg.get("account_id")
        if period_id and expr and AEP.has_account_var(expr):
            period = self.env["mis.report.instance.period"].browse(period_id)
            aep = AEP(self.query_company_ids, self.currency_id,
                      self.report_id.account_model)
            aep.parse_expr(expr)
            aep.done_parsing()
            domain = aep.get_aml_domain_for_expr(
                expr,
                period.date_from,
                period.date_to,
                self.target_move if period.source == SRC_ACTUALS else None,
                account_id,
            )
            domain.extend(period._get_additional_move_line_filter())
            if period.source == SRC_ACTUALS_ALT:
                aml_model_name = period.source_aml_model_id.model
            else:
                aml_model_name = self.report_id.move_lines_source.model
            return {
                "name": u"{} - {}".format(expr, period.name),
                "domain": domain,
                "type": "ir.actions.act_window",
                "res_model": aml_model_name,
                "views": [[False, "list"], [False, "form"]],
                "view_type": "list",
                "view_mode": "list",
                "target": "current",
                "context": {
                    "active_test": False
                },
            }
        else:
            return False
Ejemplo n.º 27
0
class payment(models.Model):
    _name = "payment.plan"
    name = fields.Char(string=u"名称", required=True, help=u'付款计划名称')
    amount_money = fields.Float(string=u"金额", required=True, help=u'付款金额')
    date_application = fields.Date(string=u"申请日期",
                                   readonly=True,
                                   help=u'付款申请日期')
    buy_id = fields.Many2one("buy.order", help=u'关联的购货订单')

    @api.one
    def request_payment(self):
        categ = self.env.ref('money.core_category_purchase')
        if not float_is_zero(self.amount_money, 2):
            source_id = self.env['money.invoice'].create({
                'name':
                self.buy_id.name,
                'partner_id':
                self.buy_id.partner_id.id,
                'category_id':
                categ.id,
                'date':
                fields.Date.context_today(self),
                'amount':
                self.amount_money,
                'reconciled':
                0,
                'to_reconcile':
                self.amount_money,
                'date_due':
                fields.Date.context_today(self),
                'state':
                'draft',
            })
            self.env["money.order"].create({
                'partner_id':
                self.buy_id.partner_id.id,
                'bank_name':
                self.buy_id.partner_id.bank_name,
                'bank_num':
                self.buy_id.partner_id.bank_num,
                'date':
                fields.Date.context_today(self),
                'source_ids': [(0, 0, {
                    'name': source_id.id,
                    'category_id': categ.id,
                    'date': source_id.date,
                    'amount': self.amount_money,
                    'reconciled': 0.0,
                    'to_reconcile': self.amount_money,
                    'this_reconcile': self.amount_money
                })],
                'type':
                'pay',
                'amount':
                self.amount_money,
                'reconciled':
                0,
                'to_reconcile':
                self.amount_money,
                'state':
                'draft',
            })
        self.date_application = datetime.now()
Ejemplo n.º 28
0
class MisReportInstancePeriod(models.Model):
    """ A MIS report instance has the logic to compute
    a report template for a given date period.

    Periods have a duration (day, week, fiscal period) and
    are defined as an offset relative to a pivot date.
    """
    @api.multi
    @api.depends(
        "report_instance_id.pivot_date",
        "report_instance_id.comparison_mode",
        "date_range_type_id",
        "type",
        "offset",
        "duration",
        "mode",
        "manual_date_from",
        "manual_date_to",
        "is_ytd",
    )
    def _compute_dates(self):
        for record in self:
            record.date_from = False
            record.date_to = False
            record.valid = False
            report = record.report_instance_id
            d = fields.Date.from_string(report.pivot_date)
            if not report.comparison_mode:
                record.date_from = report.date_from
                record.date_to = report.date_to
                record.valid = record.date_from and record.date_to
            elif record.mode == MODE_NONE:
                record.date_from = False
                record.date_to = False
                record.valid = True
            elif record.mode == MODE_FIX:
                record.date_from = record.manual_date_from
                record.date_to = record.manual_date_to
                record.valid = record.date_from and record.date_to
            elif record.mode == MODE_REL and record.type == "d":
                date_from = d + datetime.timedelta(days=record.offset)
                date_to = date_from + datetime.timedelta(days=record.duration -
                                                         1)
                record.date_from = fields.Date.to_string(date_from)
                record.date_to = fields.Date.to_string(date_to)
                record.valid = True
            elif record.mode == MODE_REL and record.type == "w":
                date_from = d - datetime.timedelta(d.weekday())
                date_from = date_from + datetime.timedelta(days=record.offset *
                                                           7)
                date_to = date_from + datetime.timedelta(
                    days=(7 * record.duration) - 1)
                record.date_from = fields.Date.to_string(date_from)
                record.date_to = fields.Date.to_string(date_to)
                record.valid = True
            elif record.mode == MODE_REL and record.type == "m":
                date_from = d.replace(day=1)
                date_from = date_from + relativedelta(months=record.offset)
                date_to = (date_from +
                           relativedelta(months=record.duration - 1) +
                           relativedelta(day=31))
                record.date_from = fields.Date.to_string(date_from)
                record.date_to = fields.Date.to_string(date_to)
                record.valid = True
            elif record.mode == MODE_REL and record.type == "y":
                date_from = d.replace(month=1, day=1)
                date_from = date_from + relativedelta(years=record.offset)
                date_to = date_from + relativedelta(years=record.duration - 1)
                date_to = date_to.replace(month=12, day=31)
                record.date_from = fields.Date.to_string(date_from)
                record.date_to = fields.Date.to_string(date_to)
                record.valid = True
            elif record.mode == MODE_REL and record.type == "date_range":
                date_range_obj = record.env["date.range"]
                current_periods = date_range_obj.search([
                    ("type_id", "=", record.date_range_type_id.id),
                    ("date_start", "<=", d),
                    ("date_end", ">=", d),
                    "|",
                    ("company_id", "=", False),
                    (
                        "company_id",
                        "in",
                        record.report_instance_id.query_company_ids.ids,
                    ),
                ])
                if current_periods:
                    # TODO we take the first date range we found as current
                    #      this may be surprising if several companies
                    #      have overlapping date ranges with different dates
                    current_period = current_periods[0]
                    all_periods = date_range_obj.search(
                        [
                            ("type_id", "=", current_period.type_id.id),
                            ("company_id", "=", current_period.company_id.id),
                        ],
                        order="date_start",
                    )
                    p = all_periods.ids.index(
                        current_period.id) + record.offset
                    if p >= 0 and p + record.duration <= len(all_periods):
                        periods = all_periods[p:p + record.duration]
                        record.date_from = periods[0].date_start
                        record.date_to = periods[-1].date_end
                        record.valid = True
            if record.mode == MODE_REL and record.valid and record.is_ytd:
                record.date_from = fields.Date.from_string(
                    record.date_to).replace(day=1, month=1)

    _name = "mis.report.instance.period"
    _description = "MIS Report Instance Period"

    name = fields.Char(size=32, required=True, string="Label", translate=True)
    mode = fields.Selection(
        [
            (MODE_FIX, "Fixed dates"),
            (MODE_REL, "Relative to report base date"),
            (MODE_NONE, "No date filter"),
        ],
        required=True,
        default=MODE_FIX,
    )
    type = fields.Selection(
        [
            ("d", _("Day")),
            ("w", _("Week")),
            ("m", _("Month")),
            ("y", _("Year")),
            ("date_range", _("Date Range")),
        ],
        string="Period type",
    )
    is_ytd = fields.Boolean(
        default=False,
        string="Year to date",
        help="Forces the start date to Jan 1st of the relevant year",
    )
    date_range_type_id = fields.Many2one(
        comodel_name="date.range.type",
        string="Date Range Type",
        domain=[("allow_overlap", "=", False)],
    )
    offset = fields.Integer(string="Offset",
                            help="Offset from current period",
                            default=-1)
    duration = fields.Integer(string="Duration",
                              help="Number of periods",
                              default=1)
    date_from = fields.Date(compute="_compute_dates", string="From (computed)")
    date_to = fields.Date(compute="_compute_dates", string="To (computed)")
    manual_date_from = fields.Date(string="From")
    manual_date_to = fields.Date(string="To")
    date_range_id = fields.Many2one(comodel_name="date.range",
                                    string="Date Range")
    valid = fields.Boolean(compute="_compute_dates",
                           type="boolean",
                           string="Valid")
    sequence = fields.Integer(string="Sequence", default=100)
    report_instance_id = fields.Many2one(
        comodel_name="mis.report.instance",
        string="Report Instance",
        required=True,
        ondelete="cascade",
    )
    report_id = fields.Many2one(related="report_instance_id.report_id")
    normalize_factor = fields.Integer(
        string="Factor",
        help="Factor to use to normalize the period (used in comparison",
        default=1,
    )
    subkpi_ids = fields.Many2many("mis.report.subkpi", string="Sub KPI Filter")

    source = fields.Selection(
        [
            (SRC_ACTUALS, "Actuals"),
            (SRC_ACTUALS_ALT, "Actuals (alternative)"),
            (SRC_SUMCOL, "Sum columns"),
            (SRC_CMPCOL, "Compare columns"),
        ],
        default=SRC_ACTUALS,
        required=True,
        help="Actuals: current data, from accounting and other queries.\n"
        "Actuals (alternative): current data from an "
        "alternative source (eg a database view providing look-alike "
        "account move lines).\n"
        "Sum columns: summation (+/-) of other columns.\n"
        "Compare to column: compare to other column.\n",
    )
    source_aml_model_id = fields.Many2one(
        comodel_name="ir.model",
        string="Move lines source",
        domain=[
            ("field_id.name", "=", "debit"),
            ("field_id.name", "=", "credit"),
            ("field_id.name", "=", "account_id"),
            ("field_id.name", "=", "date"),
            ("field_id.name", "=", "company_id"),
        ],
        help="A 'move line like' model, ie having at least debit, credit, "
        "date, account_id and company_id fields.",
    )
    source_aml_model_name = fields.Char(string="Move lines source model name",
                                        related="source_aml_model_id.model")
    source_sumcol_ids = fields.One2many(
        comodel_name="mis.report.instance.period.sum",
        inverse_name="period_id",
        string="Columns to sum",
    )
    source_sumcol_accdet = fields.Boolean(string="Sum account details")
    source_cmpcol_from_id = fields.Many2one(
        comodel_name="mis.report.instance.period", string="versus")
    source_cmpcol_to_id = fields.Many2one(
        comodel_name="mis.report.instance.period", string="Compare")

    _order = "sequence, id"

    _sql_constraints = [
        ("duration", "CHECK (duration>0)",
         "Wrong duration, it must be positive!"),
        (
            "normalize_factor",
            "CHECK (normalize_factor>0)",
            "Wrong normalize factor, it must be positive!",
        ),
        (
            "name_unique",
            "unique(name, report_instance_id)",
            "Period name should be unique by report",
        ),
    ]

    @api.constrains("source_aml_model_id")
    def _check_source_aml_model_id(self):
        for record in self:
            if record.source_aml_model_id:
                record_model = record.source_aml_model_id.field_id.filtered(
                    lambda r: r.name == "account_id").relation
                report_account_model = record.report_id.account_model
                if record_model != report_account_model:
                    raise ValidationError(
                        _("Actual (alternative) models used in columns must "
                          "have the same account model in the Account field and must "
                          "be the same defined in the "
                          "report template: %s") % report_account_model)

    @api.onchange("date_range_id")
    def _onchange_date_range(self):
        if self.date_range_id:
            self.manual_date_from = self.date_range_id.date_start
            self.manual_date_to = self.date_range_id.date_end

    @api.onchange("manual_date_from", "manual_date_to")
    def _onchange_dates(self):
        if self.date_range_id:
            if (self.manual_date_from != self.date_range_id.date_start
                    or self.manual_date_to != self.date_range_id.date_end):
                self.date_range_id = False

    @api.onchange("source")
    def _onchange_source(self):
        if self.source in (SRC_SUMCOL, SRC_CMPCOL):
            self.mode = MODE_NONE

    @api.model
    def _get_filter_domain_from_context(self):
        filters = []
        mis_report_filters = self.env.context.get("mis_report_filters", {})
        for filter_name, domain in mis_report_filters.items():
            if domain:
                value = domain.get("value")
                operator = domain.get("operator", "=")
                # Operator = 'all' when coming from JS widget
                if operator == "all":
                    if not isinstance(value, list):
                        value = [value]
                    many_ids = self.report_instance_id.resolve_2many_commands(
                        filter_name, value, ["id"])
                    for m in many_ids:
                        filters.append((filter_name, "in", [m["id"]]))
                else:
                    filters.append((filter_name, operator, value))
        return filters

    @api.multi
    def _get_additional_move_line_filter(self):
        """ Prepare a filter to apply on all move lines

        This filter is applied with a AND operator on all
        accounting expression domains. This hook is intended
        to be inherited, and is useful to implement filtering
        on analytic dimensions or operational units.

        The default filter is built from a ``mis_report_filters`` context
        key, which is a list set by the analytic filtering mechanism
        of the mis report widget::

          [(field_name, {'value': value, 'operator': operator})]

        Returns an Odoo domain expression (a python list)
        compatible with account.move.line."""
        self.ensure_one()
        return self._get_filter_domain_from_context()

    @api.multi
    def _get_additional_query_filter(self, query):
        """ Prepare an additional filter to apply on the query

        This filter is combined to the query domain with a AND
        operator. This hook is intended
        to be inherited, and is useful to implement filtering
        on analytic dimensions or operational units.

        Returns an Odoo domain expression (a python list)
        compatible with the model of the query."""
        self.ensure_one()
        return []

    @api.constrains("mode", "source")
    def _check_mode_source(self):
        for rec in self:
            if rec.source in (SRC_ACTUALS, SRC_ACTUALS_ALT):
                if rec.mode == MODE_NONE:
                    raise DateFilterRequired(
                        _("A date filter is mandatory for this source "
                          "in column %s.") % rec.name)
            elif rec.source in (SRC_SUMCOL, SRC_CMPCOL):
                if rec.mode != MODE_NONE:
                    raise DateFilterForbidden(
                        _("No date filter is allowed for this source "
                          "in column %s.") % rec.name)

    @api.constrains("source", "source_cmpcol_from_id", "source_cmpcol_to_id")
    def _check_source_cmpcol(self):
        for rec in self:
            if rec.source == SRC_CMPCOL:
                if not rec.source_cmpcol_from_id or not rec.source_cmpcol_to_id:
                    raise ValidationError(
                        _("Please provide both columns to compare in %s.") %
                        rec.name)
                if rec.source_cmpcol_from_id == rec or rec.source_cmpcol_to_id == rec:
                    raise ValidationError(
                        _("Column %s cannot be compared to itrec.") % rec.name)
                if (rec.source_cmpcol_from_id.report_instance_id !=
                        rec.report_instance_id
                        or rec.source_cmpcol_to_id.report_instance_id !=
                        rec.report_instance_id):
                    raise ValidationError(
                        _("Columns to compare must belong to the same report "
                          "in %s") % rec.name)
Ejemplo n.º 29
0
class sale_order(models.Model):
    _inherit = 'sale.order'

    tour = fields.Many2one('product.product',domain="[('type_service', '=', 'tour')]")
    tour_type = fields.Selection([('tour_ghep', 'Tour Ghép'),('tour_tyc', 'Tour Theo Yêu Cầu')], compute='_get_tour_type', store=True)
    is_tour_booking = fields.Boolean('Đặt Tour')
    so_tien_da_thu = fields.Float(string="Số tiền đã thu",compute="get_so_tien_da_thu")
    so_tien_can_thu = fields.Float(string="Số tiền cần phải thu",compute="get_so_tien_da_thu")
    thu_ho = fields.Float(string="Thu hộ")
    start_date = fields.Date(string="Ngày bắt đầu")
    end_date = fields.Date(string="Ngày kết thúc")
    sale_name = fields.Char(string="Mã số sale",compute="compute_sale_name",store=True)
    ghi_chu = fields.Text(string="Nội dung")
    ve_cap = fields.Float(string="Vé cáp")
    phu_thu = fields.Float(string="Phụ thu")
    phu_thu_line = fields.One2many('phu.thu.line','phu_thu_line_id')
    giam_tru_line = fields.One2many('giam.tru.line','giam_tru_line_id')
    tong_phu_thu = fields.Float(string="Tổng phụ thu",compute="get_tong_phu_thu",store=True)
    tong_giam_tru = fields.Float(string="Tổng giảm trừ",compute="get_tong_giam_tru",store=True)
    danh_sach_khach = fields.Text(string="Danh sách khách")
    khach_san = fields.Text(string="Khách sạn")
    type_san_pham_khac = fields.Many2one('san.pham.khac',string="Loại dịch vụ")
    du_toan_sale_order_line = fields.One2many('du.toan.sale.order','du_toan_sale_order_id')


    @api.multi
    def update_invoice_sale_tour(self):
        for order in self:
            if len(order.invoice_ids) == 1:
                check = False
                invoice_line_ids = order.invoice_ids.mapped('invoice_line_ids')
                for invoice_line_id in invoice_line_ids:
                    if not invoice_line_id.sale_line_ids:
                        self._cr.execute("""DELETE FROM account_invoice_line WHERE id=%s""" % (invoice_line_id.id))
                    else:
                        order_line_id = invoice_line_id.sale_line_ids[0]
                        if order_line_id:
                            if not check:
                                invoice_line_id.write({
                                    'quantity': order_line_id.product_uom_qty,
                                    'price_unit': order_line_id.price_unit,
                                    'tong_phu_thu': order.tong_phu_thu,
                                    'tong_giam_tru': order.tong_giam_tru,
                                })
                            else:
                                invoice_line_id.write({
                                    'quantity': order_line_id.product_uom_qty,
                                    'price_unit': order_line_id.price_unit,
                                })
                            invoice_line_id._compute_price()
                            if order_line_id.product_id != invoice_line_id.product_id:
                                invoice_line_id.product_id = order_line_id.product_id
                                invoice_line_id.name = order_line_id.name
                            check = True
                for invoice_id in order.invoice_ids.filtered(lambda inv: inv.move_id):
                    move_line = self.env['account.move.line']
                    line_not_ext = self.env['account.move.line']
                    move_line_data = invoice_id.get_move_line_from_inv()
                    for line_data in move_line_data:
                        line_data = line_data[2]
                        move_line_change = (invoice_id.move_id.mapped('line_ids') - move_line).filtered(
                            lambda mvl: mvl.product_id.id == line_data.get('product_id', False)
                                        and mvl.account_id.id == line_data.get('account_id', False))
                        if move_line_change:
                            for line in move_line_change:
                                if line.id not in line_not_ext.ids:
                                    line_not_ext += line
                                if line.credit != line_data.get('credit', 0) or line.debit != line_data.get('debit', 0):
                                    self._cr.execute("""UPDATE account_move_line SET credit=%s, debit=%s
                                                    WHERE id=%s""" % (
                                    line_data.get('credit', 0) or 0, line_data.get('debit', 0) or 0, line.id))
                                    self._cr.commit()
                                    order.invoice_ids.residual = line.update_open_amount_residual(line_data.get('credit', 0),line_data.get('debit', 0))
                                    move_line += line
                                    break

    @api.multi
    def open_validate(self):
        action = self.env.ref('vieterp_sale_tour.validate_sale_action').read()[0]
        action['context'] = "{'default_sale_order_id': %s,'default_partner_id': %s}" % (self.id, self.partner_id.id)
        return action

    @api.multi
    def get_so_tien_da_thu(self):
        for rec in self:
            invoices = self.mapped('invoice_ids')
            if invoices:
                for r in invoices:
                    rec.so_tien_can_thu += r.residual
                rec.so_tien_da_thu = rec.amount_total - rec.so_tien_can_thu
            else:
                rec.so_tien_can_thu = rec.amount_total

    @api.depends('phu_thu_line')
    def get_tong_phu_thu(self):
        for rec in self:
            sum = 0
            for r in rec.phu_thu_line:
                sum += r.thanh_tien
            rec.tong_phu_thu = sum

    @api.depends('giam_tru_line')
    def get_tong_giam_tru(self):
        for rec in self:
            sum = 0
            for r in rec.giam_tru_line:
                sum += r.thanh_tien
            rec.tong_giam_tru = sum

    @api.depends('order_line.price_total','tong_phu_thu','tong_giam_tru')
    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
            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_untaxed + amount_tax + order.tong_phu_thu - order.tong_giam_tru,
            })


    @api.depends('tour')
    def _get_tour_type(self):
        for rec in self:
            theo_yeu_cau = self.env.ref('vieterp_sale_tour.theo_yeu_cau')
            if rec.tour and theo_yeu_cau.id in rec.tour.khoi_hanh_type.ids:
                rec.tour_type = 'tour_tyc'
            else:
                rec.tour_type = 'tour_ghep'
    @api.multi
    def name_get(self):
        return [(sale.id, sale.sale_name or '') for sale in self]

    @api.onchange('is_tour_booking')
    def onchange_is_tour_booking(self):
        if not self.is_tour_booking:
            self.tour = False

    @api.onchange('tour')
    def onchange_tour(self):
        self.order_line = []
        for product_id in self.tour.product_variant_ids:
            new_order_line = self.order_line.new({
                'product_id': product_id.id,
                'description': product_id.name,
                'price_unit': product_id.lst_price,
            })
            new_order_line.product_id_change()
            self.order_line += new_order_line

        self.du_toan_sale_order_line = []
        for line in self.tour.dinh_muc_tour_line:
            new_line = self.du_toan_sale_order_line.new({
                'name': line.name,
                'partner_id': line.partner_id.id,
                'so_phong': line.chi_tiet,
                'so_luong': line.so_luong,
                'so_pax': line.so_pax,

            })
            self.du_toan_sale_order_line += new_line

    @api.depends('tour', 'user_id')
    def compute_sale_name(self):
        user_name = ''
        if self.user_id.name:
            user_name = self.user_id.name.split(' ')
            user_name = user_name[len(user_name) - 1]
        self.sale_name = user_name + ' ' + str(self.tour.default_code or '')
class ProductPercentSaleReportWizard(models.TransientModel):
    _name = "product.percent.sale.report.wizard"

    @api.model
    def _default_from_date(self):
        return date.today() - timedelta(days=7)

    # timedelta use for substract days

    to_date = fields.Date(string="To Date", default=date.today())
    from_date = fields.Date(string="From Date", default=_default_from_date)
    data = fields.Binary(string="Data", readonly=True)
    file_name = fields.Char(string="File Name")
    type_of_qty = fields.Selection([("ordered_qty", "Ordered Qty"),
                                    ("delivered_qty", "Delivered Qty"),
                                    ("invoiced_qty", "Invoiced Qty")],
                                   string="Type of Qty")
    with_profit = fields.Boolean(string="With Profit")
    with_percentage_on_total = fields.Boolean(string="Based on Total")

    @api.multi
    def generate_report(self):

        serial_no = 1
        column = 0
        row = 0
        workbook = xlwt.Workbook()
        worksheet = workbook.add_sheet("My Worksheet")
        duration = self.from_date + " " + "to" + " " + self.to_date
        company_name = self.env.user.company_id.partner_id.name
        header = xlwt.easyxf(
            "font: bold on, height 200;border:top thin,right thin,bottom thin,left thin; pattern:pattern solid, fore_colour gray25; alignment: horiz center ,vert center "
        )
        header2 = xlwt.easyxf(
            "font:height 200;border:top thin,right thin,bottom thin,left thin; pattern:fore_colour gray25; alignment: horiz center ,vert center "
        )
        header1 = xlwt.easyxf(
            "font: bold on, height 300;border:top thin,right thin,bottom thin,left thin; pattern:pattern solid, fore_colour gray25; alignment: horiz center ,vert center "
        )
        header3 = xlwt.easyxf(
            "font: bold on, height 260;border:top thin,right thin,bottom thin,left thin; pattern:pattern solid, fore_colour gray25; alignment: horiz center ,vert center "
        )
        header4 = xlwt.easyxf(
            "font: bold on, height 230;border:top thin,right thin,bottom thin,left thin; pattern:pattern solid, fore_colour gray25; alignment: horiz center ,vert center "
        )
        fnames = []
        if self.with_profit:
            if self.with_percentage_on_total:
                fnames = [
                    "Serial No", "Product Name", 'Sale Qty',
                    "Based on Highest Sale %", "Based on Total Sale %",
                    "Profit", "Based on Highest Profit %",
                    "Based on Total Profit %"
                ]

            else:
                fnames = [
                    "Serial No", "Product Name", 'Sale Qty',
                    "Based on Highest Sale %", "Profit",
                    "Based on Highest Profit %"
                ]

        if not self.with_profit:
            if self.with_percentage_on_total:
                fnames = [
                    "Serial No", "Product Name", 'Sale Qty',
                    "Based on Highest Sale %", "Based on Total Sale %"
                ]

            else:
                fnames = [
                    "Serial No", "Product Name", 'Sale Qty',
                    "Based on Highest Sale %"
                ]
        worksheet.write_merge(row, row, column,
                              len(fnames) - 1, company_name, header1)
        row += 1
        worksheet.write_merge(row, row, column,
                              len(fnames) - 1, duration, header3)
        row += 2

        for header_name in fnames:
            worksheet.write(row, column, header_name, header)
            column += 1
        row += 1
        high_sale = 0
        high_profit = 0
        total_sale = 0
        total_profit_report = 0
        list_of_dict = []
        product_ids = self.env["product.product"].search([
            ("exclude_from_report", "=", False)
        ])
        for product_id in product_ids:
            domain = [("product_id", "=", product_id.id),
                      ("order_id.confirmation_date", ">=", self.from_date),
                      ("order_id.confirmation_date", "<=", self.to_date)]
            if self.type_of_qty == "ordered_qty":
                domain.append(("order_id.state", "in", ["done", "sale"]))

            sale_line_ids = self.env["sale.order.line"].search(domain)

            if not sale_line_ids:
                continue
            sale_qty = 0
            total_profit = 0
            if self.type_of_qty == "ordered_qty":
                sale_qty = sum(sale_line_ids.mapped("product_uom_qty"))
                if self.with_profit:
                    for sale_line_id in sale_line_ids:
                        total_profit += sale_line_id.price_unit * sale_line_id.product_uom_qty

            elif self.type_of_qty == "delivered_qty":
                sale_qty = sum(sale_line_ids.mapped("qty_delivered"))
                if self.with_profit:
                    for sale_line_id in sale_line_ids:
                        total_profit += sale_line_id.price_unit * sale_line_id.qty_delivered

            elif self.type_of_qty == "invoiced_qty":
                sale_qty = sum(sale_line_ids.mapped("qty_invoiced"))
                if self.with_profit:
                    for sale_line_id in sale_line_ids:
                        total_profit += sale_line_id.price_unit * sale_line_id.qty_invoiced

            if sale_qty > high_sale:
                high_sale = sale_qty
            if self.with_percentage_on_total:
                total_sale += sale_qty

            if total_profit > high_profit:
                high_profit = total_profit
            if self.with_percentage_on_total:
                total_profit_report += total_profit

            product_dict = {
                "product_name": product_id.name,
                "sale_qty": sale_qty,
                "profit": total_profit
            }
            list_of_dict.append(product_dict)

        for product_dict in list_of_dict:
            sale_percentage = 0
            total_sale_percentage = 0
            if high_sale:
                sale_percentage = round(
                    (product_dict.get("sale_qty") * 100) / high_sale, 2)
            if total_sale:
                total_sale_percentage = round(
                    (product_dict.get("sale_qty") * 100) / total_sale, 2)
            worksheet.write(row, 0, serial_no, header2)
            worksheet.write(row, 1, product_dict.get("product_name"), header2)
            worksheet.write(row, 2, product_dict.get("sale_qty"), header2)
            worksheet.write(row, 3, sale_percentage, header2)
            if self.with_profit:
                profit_percentage = 0
                total_profit_percentage = 0
                if high_profit:
                    profit_percentage = round(
                        (product_dict.get("profit") * 100) / high_profit, 2)

                if self.with_percentage_on_total:
                    if total_profit_report:
                        total_profit_percentage = round(
                            (product_dict.get("profit") * 100) /
                            total_profit_report, 2)
                    worksheet.write(row, 4, total_sale_percentage, header2)
                    worksheet.write(row, 5, product_dict.get("profit"),
                                    header2)
                    worksheet.write(row, 6, profit_percentage, header2)
                    worksheet.write(row, 7, total_profit_percentage, header2)
                else:
                    worksheet.write(row, 4, product_dict.get("profit"),
                                    header2)
                    worksheet.write(row, 5, profit_percentage, header2)
            else:
                if self.with_percentage_on_total:
                    worksheet.write(row, 4, total_sale_percentage, header2)

            row += 1
            serial_no += 1

        # below use for download file
        fp = BytesIO()
        workbook.save(fp)
        fp.seek(0)
        file_new = base64.encodebytes(fp.read())

        fp.close()
        self.write({
            'data': file_new,
            'file_name': "product_percent_sale_report"
        })

        return {
            'type':
            "ir.actions.act_url",
            'url':
            'web/content/?model=product.percent.sale.report.wizard&download=true&field=data&id=%s&filename=%s - %s.xls'
            % (self.id, self.file_name, datetime.now().strftime("%Y-%m-%d")),
            'target':
            'self'
        }