def action_put_in_pack(self): picking_move_lines = self.picking_id.move_line_ids if not self.picking_id.picking_type_id.show_reserved and not self.env.context.get( 'barcode_view'): picking_move_lines = self.picking_id.move_line_nosuggest_ids move_line_ids = picking_move_lines.filtered(lambda ml: float_compare( ml.qty_done, 0.0, precision_rounding=ml.product_uom_id.rounding ) > 0 and not ml.result_package_id) if not move_line_ids: move_line_ids = picking_move_lines.filtered( lambda ml: float_compare(ml.product_uom_qty, 0.0, precision_rounding=ml.product_uom_id. rounding) > 0 and float_compare(ml.qty_done, 0.0, precision_rounding=ml.product_uom_id.rounding ) == 0) delivery_package = self.picking_id._put_in_pack(move_line_ids) # write shipping weight and product_packaging on 'stock_quant_package' if needed if self.delivery_packaging_id: delivery_package.packaging_id = self.delivery_packaging_id if self.shipping_weight: delivery_package.shipping_weight = self.shipping_weight
def _get_ddt_values(self): """ We calculate the link between the invoice lines and the deliveries related to the invoice through the links with the sale order(s). We assume that the first picking was invoiced first. (FIFO) :return: a dictionary with as key the picking and value the invoice line numbers (by counting) """ self.ensure_one() # We don't consider returns/credit notes as we suppose they will lead to more deliveries/invoices as well if self.move_type != "out_invoice" or self.state != 'posted': return {} line_count = 0 invoice_line_pickings = {} for line in self.invoice_line_ids.filtered( lambda l: not l.display_type): line_count += 1 done_moves_related = line.sale_line_ids.mapped( 'move_ids').filtered(lambda m: m.state == 'done' and m. location_dest_id.usage == 'customer') if len(done_moves_related) <= 1: if done_moves_related and line_count not in invoice_line_pickings.get( done_moves_related.picking_id, []): invoice_line_pickings.setdefault( done_moves_related.picking_id, []).append(line_count) else: total_invoices = done_moves_related.mapped( 'sale_line_id.invoice_lines').filtered( lambda l: l.move_id.state == 'posted' and l.move_id. move_type == 'out_invoice').sorted( lambda l: l.move_id.invoice_date) total_invs = [(i.product_uom_id._compute_quantity( i.quantity, i.product_id.uom_id), i) for i in total_invoices] inv = total_invs.pop(0) # Match all moves and related invoice lines FIFO looking for when the matched invoice_line matches line for move in done_moves_related.sorted(lambda m: m.date): rounding = move.product_uom.rounding move_qty = move.product_qty while (float_compare( move_qty, 0, precision_rounding=rounding) > 0): if float_compare(inv[0], move_qty, precision_rounding=rounding) > 0: inv = (inv[0] - move_qty, inv[1]) invoice_line = inv[1] move_qty = 0 if float_compare(inv[0], move_qty, precision_rounding=rounding) <= 0: move_qty -= inv[0] invoice_line = inv[1] if total_invs: inv = total_invs.pop(0) else: move_qty = 0 #abort when not enough matched invoices # If in our FIFO iteration we stumble upon the line we were checking if invoice_line == line and line_count not in invoice_line_pickings.get( move.picking_id, []): invoice_line_pickings.setdefault( move.picking_id, []).append(line_count) return invoice_line_pickings
def action_put_in_pack(self): """ Action to put move lines with 'Done' quantities into a new pack This method follows same logic to stock.picking. """ self.ensure_one() if self.state not in ('done', 'cancel'): picking_move_lines = self.move_line_ids move_line_ids = picking_move_lines.filtered( lambda ml: float_compare(ml.qty_done, 0.0, precision_rounding=ml.product_uom_id. rounding) > 0 and not ml. result_package_id) if not move_line_ids: move_line_ids = picking_move_lines.filtered( lambda ml: float_compare(ml.product_uom_qty, 0.0, precision_rounding=ml. product_uom_id.rounding) > 0 and float_compare(ml.qty_done, 0.0, precision_rounding=ml.product_uom_id.rounding ) == 0) if move_line_ids: res = self.picking_ids[0]._pre_put_in_pack_hook(move_line_ids) if not res: res = self.picking_ids[0]._put_in_pack( move_line_ids, False) return res else: raise UserError( _("Please add 'Done' quantities to the batch picking to create a new pack." ))
def _create_or_update_picking(self): for line in self: if line.product_id and line.product_id.type in ('product', 'consu'): # Prevent decreasing below received quantity if float_compare(line.product_qty, line.qty_received, line.product_uom.rounding) < 0: raise UserError( _('You cannot decrease the ordered quantity below the received quantity.\n' 'Create a return first.')) if float_compare(line.product_qty, line.qty_invoiced, line.product_uom.rounding) == -1: # If the quantity is now below the invoiced quantity, create an activity on the vendor bill # inviting the user to create a refund. line.invoice_lines[0].move_id.activity_schedule( 'mail.mail_activity_data_warning', note= _('The quantities on your purchase order indicate less than billed. You should ask for a refund.' )) # If the user increased quantity of existing line or created a new line pickings = line.order_id.picking_ids.filtered( lambda x: x.state not in ('done', 'cancel') and x.location_dest_id.usage in ('internal', 'transit', 'customer')) picking = pickings and pickings[0] or False if not picking: res = line.order_id._prepare_picking() picking = self.env['stock.picking'].create(res) moves = line._create_stock_moves(picking) moves._action_confirm()._action_assign()
def _paypal_form_get_invalid_parameters(self, data): invalid_parameters = [] _logger.info('Received a notification from Paypal with IPN version %s', data.get('notify_version')) if data.get('test_ipn'): _logger.warning( 'Received a notification from Paypal using sandbox' ), # TODO: txn_id: shoudl be false at draft, set afterwards, and verified with txn details if self.acquirer_reference and data.get('txn_id') != self.acquirer_reference: invalid_parameters.append(('txn_id', data.get('txn_id'), self.acquirer_reference)) # check what is buyed if float_compare(float(data.get('mc_gross', '0.0')), (self.amount + self.fees), 2) != 0: invalid_parameters.append(('mc_gross', data.get('mc_gross'), '%.2f' % self.amount)) # mc_gross is amount + fees if data.get('mc_currency') != self.currency_id.name: invalid_parameters.append(('mc_currency', data.get('mc_currency'), self.currency_id.name)) if 'handling_amount' in data and float_compare(float(data.get('handling_amount')), self.fees, 2) != 0: invalid_parameters.append(('handling_amount', data.get('handling_amount'), self.fees)) # check buyer if self.payment_token_id and data.get('payer_id') != self.payment_token_id.acquirer_ref: invalid_parameters.append(('payer_id', data.get('payer_id'), self.payment_token_id.acquirer_ref)) # check seller if data.get('receiver_id') and self.acquirer_id.paypal_seller_account and data['receiver_id'] != self.acquirer_id.paypal_seller_account: invalid_parameters.append(('receiver_id', data.get('receiver_id'), self.acquirer_id.paypal_seller_account)) if not data.get('receiver_id') or not self.acquirer_id.paypal_seller_account: # Check receiver_email only if receiver_id was not checked. # In Paypal, this is possible to configure as receiver_email a different email than the business email (the login email) # In Flectra, there is only one field for the Paypal email: the business email. This isn't possible to set a receiver_email # different than the business email. Therefore, if you want such a configuration in your Paypal, you are then obliged to fill # the Merchant ID in the Paypal payment acquirer in Flectra, so the check is performed on this variable instead of the receiver_email. # At least one of the two checks must be done, to avoid fraudsters. if data.get('receiver_email') != self.acquirer_id.paypal_email_account: invalid_parameters.append(('receiver_email', data.get('receiver_email'), self.acquirer_id.paypal_email_account)) return invalid_parameters
def _has_components_to_record(self): """ Returns true if the move has still some tracked components to record. """ self.ensure_one() if not self.is_subcontract: return False rounding = self.product_uom.rounding production = self.move_orig_ids.production_id[-1:] return self._has_tracked_subcontract_components() and\ float_compare(production.qty_produced, production.product_uom_qty, precision_rounding=rounding) < 0 and\ float_compare(self.quantity_done, self.product_uom_qty, precision_rounding=rounding) < 0
def _prepare_stock_moves(self, picking): """ Prepare the stock moves data for one order line. This function returns a list of dictionary ready to be used in stock.move's create() """ self.ensure_one() res = [] if self.product_id.type not in ['product', 'consu']: return res price_unit = self._get_stock_move_price_unit() qty = self._get_qty_procurement() move_dests = self.move_dest_ids if not move_dests: move_dests = self.move_ids.move_dest_ids.filtered( lambda m: m.state != 'cancel' and not m.location_dest_id.usage == 'supplier') if not move_dests: qty_to_attach = 0 qty_to_push = self.product_qty - qty else: move_dests_initial_demand = self.product_id.uom_id._compute_quantity( sum( move_dests.filtered(lambda m: m.state != 'cancel' and not m .location_dest_id.usage == 'supplier'). mapped('product_qty')), self.product_uom, rounding_method='HALF-UP') qty_to_attach = min(self.product_qty, move_dests_initial_demand) - qty qty_to_push = self.product_qty - move_dests_initial_demand if float_compare(qty_to_attach, 0.0, precision_rounding=self.product_uom.rounding) > 0: product_uom_qty, product_uom = self.product_uom._adjust_uom_quantities( qty_to_attach, self.product_id.uom_id) res.append( self._prepare_stock_move_vals(picking, price_unit, product_uom_qty, product_uom)) if float_compare(qty_to_push, 0.0, precision_rounding=self.product_uom.rounding) > 0: product_uom_qty, product_uom = self.product_uom._adjust_uom_quantities( qty_to_push, self.product_id.uom_id) extra_move_vals = self._prepare_stock_move_vals( picking, price_unit, product_uom_qty, product_uom) extra_move_vals['move_dest_ids'] = False # don't attach res.append(extra_move_vals) return res
def process(self): pickings_to_do = self.env['stock.picking'] pickings_not_to_do = self.env['stock.picking'] for line in self.backorder_confirmation_line_ids: if line.to_backorder is True: pickings_to_do |= line.picking_id else: pickings_not_to_do |= line.picking_id for pick_id in pickings_not_to_do: moves_to_log = {} for move in pick_id.move_lines: if float_compare( move.product_uom_qty, move.quantity_done, precision_rounding=move.product_uom.rounding) > 0: moves_to_log[move] = (move.quantity_done, move.product_uom_qty) pick_id._log_less_quantities_than_expected(moves_to_log) pickings_to_validate = self.env.context.get( 'button_validate_picking_ids') if pickings_to_validate: pickings_to_validate = self.env['stock.picking'].browse( pickings_to_validate).with_context(skip_backorder=True) if pickings_not_to_do: pickings_to_validate = pickings_to_validate.with_context( picking_ids_not_to_backorder=pickings_not_to_do.ids) return pickings_to_validate.button_validate() return True
def _put_in_pack(self): package = False for pick in self: operations = pick.move_line_ids.filtered(lambda o: o.qty_done > 0 and not o.result_package_id) operation_ids = self.env['stock.move.line'] if operations: package = self.env['stock.quant.package'].create({}) for operation in operations: if float_compare(operation.qty_done, operation.product_uom_qty, precision_rounding=operation.product_uom_id.rounding) >= 0: operation_ids |= operation else: quantity_left_todo = float_round( operation.product_uom_qty - operation.qty_done, precision_rounding=operation.product_uom_id.rounding, rounding_method='UP') done_to_keep = operation.qty_done new_operation = operation.copy( default={'product_uom_qty': 0, 'qty_done': operation.qty_done}) operation.write({'product_uom_qty': quantity_left_todo, 'qty_done': 0.0}) new_operation.write({'product_uom_qty': done_to_keep}) operation_ids |= new_operation operation_ids.write({'result_package_id': package.id}) else: raise UserError(_('Please process some quantities to put in the pack first!')) return package
def _prepare_invoice_line_from_po_line(self, line): if line.product_id.purchase_method == 'purchase': qty = line.product_qty - line.qty_invoiced else: qty = line.qty_received - line.qty_invoiced if float_compare(qty, 0.0, precision_rounding=line.product_uom.rounding) <= 0: qty = 0.0 taxes = line.taxes_id invoice_line_tax_ids = line.order_id.fiscal_position_id.map_tax(taxes) invoice_line = self.env['account.invoice.line'] data = { 'purchase_line_id': line.id, 'name': line.order_id.name+': '+line.name, 'origin': line.order_id.origin, 'uom_id': line.product_uom.id, 'product_id': line.product_id.id, 'account_id': invoice_line.with_context({'journal_id': self.journal_id.id, 'type': 'in_invoice'})._default_account(), 'price_unit': line.order_id.currency_id.with_context(date=self.date_invoice).compute(line.price_unit, self.currency_id, round=False), 'quantity': qty, 'discount': 0.0, 'account_analytic_id': line.account_analytic_id.id, 'analytic_tag_ids': line.analytic_tag_ids.ids, 'invoice_line_tax_ids': invoice_line_tax_ids.ids } account = invoice_line.get_invoice_line_account('in_invoice', line.product_id, line.order_id.fiscal_position_id, self.env.user.company_id) if account: data['account_id'] = account.id return data
def _onchange_location_or_product_id(self): vals = {} # Once the new line is complete, fetch the new theoretical values. if self.product_id and self.location_id: # Sanity check if a lot has been set. if self.lot_id: if self.tracking == 'none' or self.product_id != self.lot_id.product_id: vals['lot_id'] = None quants = self._gather(self.product_id, self.location_id, lot_id=self.lot_id, package_id=self.package_id, owner_id=self.owner_id, strict=True) reserved_quantity = sum(quants.mapped('reserved_quantity')) quantity = sum(quants.mapped('quantity')) vals['reserved_quantity'] = reserved_quantity # Update `quantity` only if the user manually updated `inventory_quantity`. if float_compare( self.quantity, self.inventory_quantity, precision_rounding=self.product_uom_id.rounding) == 0: vals['quantity'] = quantity # Special case: directly set the quantity to one for serial numbers, # it'll trigger `inventory_quantity` compute. if self.lot_id and self.tracking == 'serial': vals['quantity'] = 1 if vals: self.update(vals)
def _get_overprocessed_stock_moves(self): self.ensure_one() return self.move_lines.filtered( lambda move: move.product_uom_qty != 0 and float_compare( move.quantity_done, move.product_uom_qty, precision_rounding=move.product_uom.rounding) == 1)
def add_payment(self, data): statement_id = super(PosOrder, self).add_payment(data) statement_lines = self.env['account.bank.statement.line'].search([('statement_id', '=', statement_id), ('pos_statement_id', '=', self.id), ('journal_id', '=', data['journal'])]) statement_lines = statement_lines.filtered(lambda line: float_compare(line.amount, data['amount'], precision_rounding=line.journal_currency_id.rounding) == 0) # we can get multiple statement_lines when there are >1 credit # card payments with the same amount. In that case it doesn't # matter which statement line we pick, just pick one that # isn't already used. for line in statement_lines: if not line.mercury_card_brand: line.mercury_card_brand = data.get('card_brand') line.mercury_card_number = data.get('card_number') line.mercury_card_owner_name = data.get('card_owner_name') line.mercury_ref_no = data.get('ref_no') line.mercury_record_no = data.get('record_no') line.mercury_invoice_no = data.get('invoice_no') break return statement_id
def _set_inventory_quantity(self): """ Inverse method to create stock move when `inventory_quantity` is set (`inventory_quantity` is only accessible in inventory mode). """ if not self._is_inventory_mode(): return for quant in self: # Get the quantity to create a move for. rounding = quant.product_id.uom_id.rounding diff = float_round(quant.inventory_quantity - quant.quantity, precision_rounding=rounding) diff_float_compared = float_compare(diff, 0, precision_rounding=rounding) # Create and vaidate a move so that the quant matches its `inventory_quantity`. if diff_float_compared == 0: continue elif diff_float_compared > 0: move_vals = quant._get_inventory_move_values( diff, quant.product_id.with_company( quant.company_id).property_stock_inventory, quant.location_id) else: move_vals = quant._get_inventory_move_values( -diff, quant.location_id, quant.product_id.with_company( quant.company_id).property_stock_inventory, out=True) move = quant.env['stock.move'].with_context( inventory_mode=False).create(move_vals) move._action_done()
def _get_first_available_slot(self, start_datetime, duration): """Get the first available interval for the workcenter in `self`. The available interval is disjoinct with all other workorders planned on this workcenter, but can overlap the time-off of the related calendar (inverse of the working hours). Return the first available interval (start datetime, end datetime) or, if there is none before 700 days, a tuple error (False, 'error message'). :param start_datetime: begin the search at this datetime :param duration: minutes needed to make the workorder (float) :rtype: tuple """ self.ensure_one() start_datetime, revert = make_aware(start_datetime) get_available_intervals = partial( self.resource_calendar_id._work_intervals, domain=[('time_type', 'in', ['other', 'leave'])], resource=self.resource_id, tz=timezone(self.resource_calendar_id.tz)) get_workorder_intervals = partial( self.resource_calendar_id._leave_intervals, domain=[('time_type', '=', 'other')], resource=self.resource_id, tz=timezone(self.resource_calendar_id.tz)) remaining = duration start_interval = start_datetime delta = timedelta(days=14) for n in range(50): # 50 * 14 = 700 days in advance (hardcoded) dt = start_datetime + delta * n available_intervals = get_available_intervals(dt, dt + delta) workorder_intervals = get_workorder_intervals(dt, dt + delta) for start, stop, dummy in available_intervals: # Shouldn't loop more than 2 times because the available_intervals contains the workorder_intervals # And remaining == duration can only occur at the first loop and at the interval intersection (cannot happen several time because available_intervals > workorder_intervals for i in range(2): interval_minutes = (stop - start).total_seconds() / 60 # If the remaining minutes has never decrease update start_interval if remaining == duration: start_interval = start # If there is a overlap between the possible available interval and a others WO if Intervals( [(start_interval, start + timedelta(minutes=min(remaining, interval_minutes)), dummy)]) & workorder_intervals: remaining = duration elif float_compare(interval_minutes, remaining, precision_digits=3) >= 0: return revert(start_interval), revert( start + timedelta(minutes=remaining)) else: # Decrease a part of the remaining duration remaining -= interval_minutes # Go to the next available interval because the possible current interval duration has been used break return False, 'Not available slot 700 days after the planned start'
def _compute_show_check_availability(self): for picking in self: has_moves_to_reserve = any( move.state in ('waiting', 'confirmed', 'partially_available') and float_compare(move.product_uom_qty, 0, precision_rounding=move.product_uom.rounding) for move in picking.move_lines ) picking.show_check_availability = picking.is_locked and picking.state in ('confirmed', 'waiting', 'assigned') and has_moves_to_reserve
def _free_reservation(self, product_id, location_id, quantity, lot_id=None, package_id=None, owner_id=None, ml_to_ignore=None): """ When editing a done move line or validating one with some forced quantities, it is possible to impact quants that were not reserved. It is therefore necessary to edit or unlink the move lines that reserved a quantity now unavailable. :param ml_to_ignore: recordset of `stock.move.line` that should NOT be unreserved """ self.ensure_one() if ml_to_ignore is None: ml_to_ignore = self.env['stock.move.line'] ml_to_ignore |= self # Check the available quantity, with the `strict` kw set to `True`. If the available # quantity is greather than the quantity now unavailable, there is nothing to do. available_quantity = self.env['stock.quant']._get_available_quantity( product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=True ) if quantity > available_quantity: # We now have to find the move lines that reserved our now unavailable quantity. We # take care to exclude ourselves and the move lines were work had already been done. oudated_move_lines_domain = [ ('move_id.state', 'not in', ['done', 'cancel']), ('product_id', '=', product_id.id), ('lot_id', '=', lot_id.id if lot_id else False), ('location_id', '=', location_id.id), ('owner_id', '=', owner_id.id if owner_id else False), ('package_id', '=', package_id.id if package_id else False), ('product_qty', '>', 0.0), ('id', 'not in', ml_to_ignore.ids), ] oudated_candidates = self.env['stock.move.line'].search(oudated_move_lines_domain) # As the move's state is not computed over the move lines, we'll have to manually # recompute the moves which we adapted their lines. move_to_recompute_state = self.env['stock.move'] rounding = self.product_uom_id.rounding for candidate in oudated_candidates: if float_compare(candidate.product_qty, quantity, precision_rounding=rounding) <= 0: quantity -= candidate.product_qty move_to_recompute_state |= candidate.move_id if candidate.qty_done: candidate.product_uom_qty = 0.0 else: candidate.unlink() else: # split this move line and assign the new part to our extra move quantity_split = float_round( candidate.product_qty - quantity, precision_rounding=self.product_uom_id.rounding, rounding_method='UP') candidate.product_uom_qty = self.product_id.uom_id._compute_quantity(quantity_split, candidate.product_uom_id, rounding_method='HALF-UP') quantity -= quantity_split move_to_recompute_state |= candidate.move_id if quantity == 0.0: break move_to_recompute_state._recompute_state()
def _transfer_form_get_invalid_parameters(self, data): invalid_parameters = [] if float_compare(float(data.get('amount', '0.0')), self.amount, 2) != 0: invalid_parameters.append(('amount', data.get('amount'), '%.2f' % self.amount)) if data.get('currency') != self.currency_id.name: invalid_parameters.append(('currency', data.get('currency'), self.currency_id.name)) return invalid_parameters
def _authorize_form_get_invalid_parameters(self, data): invalid_parameters = [] if self.acquirer_reference and data.get('x_trans_id') != self.acquirer_reference: invalid_parameters.append(('Transaction Id', data.get('x_trans_id'), self.acquirer_reference)) # check what is buyed if float_compare(float(data.get('x_amount', '0.0')), self.amount, 2) != 0: invalid_parameters.append(('Amount', data.get('x_amount'), '%.2f' % self.amount)) return invalid_parameters
def check_quantity(self): for quant in self: if float_compare( quant.quantity, 1, precision_rounding=quant.product_uom_id.rounding ) > 0 and quant.lot_id and quant.product_id.tracking == 'serial': raise ValidationError( _('A serial number should only be linked to a single product.' ))
def check_quantity(self): for quant in self: if float_compare( quant.quantity, 1, precision_rounding=quant.product_uom_id.rounding ) > 0 and quant.lot_id and quant.product_id.tracking == 'serial': raise ValidationError( _('The serial number has already been assigned: \n Product: %s, Serial Number: %s' ) % (quant.product_id.display_name, quant.lot_id.name))
def _onchange_qty_done(self): """ When the user is encoding a move line for a tracked product, we apply some logic to help him. This onchange will warn him if he set `qty_done` to a non-supported value. """ res = {} if self.qty_done and self.product_id.tracking == 'serial': if float_compare(self.qty_done, 1.0, precision_rounding=self.move_id.product_id.uom_id.rounding) != 0: message = _('You can only process 1.0 %s for products with unique serial number.') % self.product_id.uom_id.name res['warning'] = {'title': _('Warning'), 'message': message} return res
def _onchange_secondary_uom(self): if not self.secondary_uom_id: return factor = self.secondary_uom_id.factor * self.product_id.uom_id.factor qty = float_round(self.secondary_uom_qty * factor, precision_rounding=self.product_id.uom_id.rounding) if float_compare( self.units_included, qty, precision_rounding=self.product_id.uom_id.rounding) != 0: self.units_included = qty
def _buckaroo_form_get_invalid_parameters(self, data): invalid_parameters = [] data = normalize_keys_upper(data) if self.acquirer_reference and data.get('BRQ_TRANSACTIONS') != self.acquirer_reference: invalid_parameters.append(('Transaction Id', data.get('BRQ_TRANSACTIONS'), self.acquirer_reference)) # check what is buyed if float_compare(float(data.get('BRQ_AMOUNT', '0.0')), self.amount, 2) != 0: invalid_parameters.append(('Amount', data.get('BRQ_AMOUNT'), '%.2f' % self.amount)) if data.get('BRQ_CURRENCY') != self.currency_id.name: invalid_parameters.append(('Currency', data.get('BRQ_CURRENCY'), self.currency_id.name)) return invalid_parameters
def _onchange_units_included_purchase_order_secondary_unit(self): if not self.secondary_uom_id: return factor = self.secondary_uom_id.factor * self.product_id.uom_id.factor qty = float_round( self.units_included / (factor or 1.0), precision_rounding=self.secondary_uom_id.uom_id.rounding) if float_compare( self.secondary_uom_qty, qty, precision_rounding=self.secondary_uom_id.uom_id.rounding) != 0: self.secondary_uom_qty = qty
def _alipay_form_get_invalid_parameters(self, data): invalid_parameters = [] if float_compare(float(data.get('total_fee', '0.0')), (self.amount + self.fees), 2) != 0: invalid_parameters.append(('total_fee', data.get('total_fee'), '%.2f' % (self.amount + self.fees))) # mc_gross is amount + fees if self.acquirer_id.alipay_payment_method == 'standard_checkout': if data.get('currency') != self.currency_id.name: invalid_parameters.append(('currency', data.get('currency'), self.currency_id.name)) else: if data.get('seller_email') != self.acquirer_id.alipay_seller_email: invalid_parameters.append(('seller_email', data.get('seller_email'), self.acquirer_id.alipay_seller_email)) return invalid_parameters
def action_show_details(self): """ Open the produce wizard in order to register tracked components for subcontracted product. Otherwise use standard behavior. """ self.ensure_one() if self.is_subcontract: rounding = self.product_uom.rounding production = self.move_orig_ids.production_id[-1:] if self._has_tracked_subcontract_components() and\ float_compare(production.qty_produced, production.product_uom_qty, precision_rounding=rounding) < 0 and\ float_compare(self.quantity_done, self.product_uom_qty, precision_rounding=rounding) < 0: return self._action_record_components() action = super(StockMove, self).action_show_details() if self.is_subcontract and self._has_tracked_subcontract_components(): action['views'] = [ (self.env.ref('stock.view_stock_move_operations').id, 'form') ] action['context'].update({ 'show_lots_m2o': self.has_tracking != 'none', 'show_lots_text': False, }) return action
def _sips_form_get_invalid_parameters(self, data): invalid_parameters = [] data = self._sips_data_to_object(data.get('Data')) # TODO: txn_id: should be false at draft, set afterwards, and verified with txn details if self.acquirer_reference and data.get('transactionReference') != self.acquirer_reference: invalid_parameters.append(('transactionReference', data.get('transactionReference'), self.acquirer_reference)) # check what is bought if float_compare(float(data.get('amount', '0.0')) / 100, self.amount, 2) != 0: invalid_parameters.append(('amount', data.get('amount'), '%.2f' % self.amount)) return invalid_parameters
def _schedule_hours(self, hours, day_dt, compute_leaves=False, resource_id=None): """ Schedule hours of work, using a calendar and an optional resource to compute working and leave days. This method can be used backwards, i.e. scheduling days before a deadline. For compute_leaves, resource_id: see _get_day_work_intervals. This method does not use rrule because rrule does not allow backwards computation. :param int hours: number of hours to schedule. Use a negative number to compute a backwards scheduling. :param datetime day_dt: reference date to compute working days. If days is > 0 date is the starting date. If days is < 0 date is the ending date. :return list intervals: list of time intervals in naive UTC """ self.ensure_one() backwards = (hours < 0) intervals = [] remaining_hours, iterations = abs(hours * 1.0), 0 day_dt_tz = to_naive_user_tz(day_dt, self.env.user) current_datetime = day_dt_tz call_args = dict(compute_leaves=compute_leaves, resource_id=resource_id) while float_compare(remaining_hours, 0.0, precision_digits=2) in (1, 0) and iterations < 1000: if backwards: call_args['end_time'] = current_datetime.time() else: call_args['start_time'] = current_datetime.time() working_intervals = self._get_day_work_intervals(current_datetime.date(), **call_args) if working_intervals: new_working_intervals = self._interval_schedule_hours(working_intervals, remaining_hours, backwards=backwards) res = timedelta() for interval in working_intervals: res += interval[1] - interval[0] remaining_hours -= res.total_seconds() / 3600.0 intervals = intervals + new_working_intervals if not backwards else new_working_intervals + intervals # get next day if backwards: current_datetime = datetime.datetime.combine(self._get_previous_work_day(current_datetime), datetime.time(23, 59, 59)) else: current_datetime = datetime.datetime.combine(self._get_next_work_day(current_datetime), datetime.time()) # avoid infinite loops iterations += 1 return intervals
def _sips_form_get_invalid_parameters(self, data): invalid_parameters = [] data = self._sips_data_to_object(data.get('Data')) # amounts should match # get currency decimals from const sips_currency = SIPS_SUPPORTED_CURRENCIES.get(self.currency_id.name) # convert from int to float using decimals from currency amount_converted = float(data.get('amount', '0.0')) / (10 ** sips_currency.decimal) if float_compare(amount_converted, self.amount, sips_currency.decimal) != 0: invalid_parameters.append(('amount', data.get('amount'), '%.2f' % self.amount)) return invalid_parameters