def _onchange_product_id_check_availability(self): if not self.product_id or not self.product_uom_qty or not self.product_uom: self.product_packaging = False return {} if self.product_id.type == 'product': precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') product = self.product_id.with_context( warehouse=self.order_id.warehouse_id.id, lang=self.order_id.partner_id.lang or self.env.user.lang or 'en_US' ) product_qty = self.product_uom._compute_quantity(self.product_uom_qty, self.product_id.uom_id) if float_compare(product.virtual_available, product_qty, precision_digits=precision) == -1: is_available = self._check_routing() if not is_available: message = _('You plan to sell %s %s but you only have %s %s available in %s warehouse.') % \ (self.product_uom_qty, self.product_uom.name, product.virtual_available, product.uom_id.name, self.order_id.warehouse_id.name) # We check if some products are available in other warehouses. if float_compare(product.virtual_available, self.product_id.virtual_available, precision_digits=precision) == -1: message += _('\nThere are %s %s available accross all warehouses.') % \ (self.product_id.virtual_available, product.uom_id.name) warning_mess = { 'title': _('Not enough inventory!'), 'message' : message } return {'warning': warning_mess} return {}
def _check_holidays(self): for holiday in self: if holiday.holiday_type != 'employee' or holiday.type != 'remove' or not holiday.employee_id or holiday.holiday_status_id.limit: continue leave_days = holiday.holiday_status_id.get_days( holiday.employee_id.id)[holiday.holiday_status_id.id] if float_compare(leave_days['remaining_leaves'], 0, precision_digits=2) == -1 or \ float_compare(leave_days['virtual_remaining_leaves'], 0, precision_digits=2) == -1: raise ValidationError( _('The number of remaining leaves is not sufficient for this leave type.\n' 'Please verify also the leaves waiting for validation.'))
def test_rounding_invalid(self): """ verify that invalid parameters are forbidden """ with self.assertRaises(AssertionError): float_is_zero(0.01, precision_digits=3, precision_rounding=0.01) with self.assertRaises(AssertionError): float_compare(0.01, 0.02, precision_digits=3, precision_rounding=0.01) with self.assertRaises(AssertionError): float_round(0.01, precision_digits=3, precision_rounding=0.01)
def _compute_consumed_less_than_planned(self): for order in self: order.consumed_less_than_planned = any(order.move_raw_ids.filtered( lambda move: float_compare(move.quantity_done, move.product_uom_qty, precision_rounding=move.product_uom.rounding) == -1) )
def action_validate(self): self.ensure_one() precision = self.env['decimal.precision'].precision_get( 'Product Unit of Measure') available_qty = self.env['stock.quant']._get_available_quantity( self.product_id, self.location_id, self.lot_id, strict=True) if float_compare(available_qty, self.product_qty, precision_digits=precision) >= 0: return self.action_unbuild() else: return { 'name': _('Insufficient Quantity'), 'view_type': 'form', 'view_mode': 'form', 'res_model': 'stock.warn.insufficient.qty.unbuild', 'view_id': self.env.ref( 'mrp.stock_warn_insufficient_qty_unbuild_form_view').id, 'type': 'ir.actions.act_window', 'context': { 'default_product_id': self.product_id.id, 'default_location_id': self.location_id.id, 'default_unbuild_id': self.id }, 'target': 'new' }
def action_validate(self): self.ensure_one() if self.product_id.type != 'product': return self.do_scrap() precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') available_qty = sum(self.env['stock.quant']._gather(self.product_id, self.location_id, self.lot_id, self.package_id, self.owner_id, strict=True).mapped('quantity')) if float_compare(available_qty, self.scrap_qty, precision_digits=precision) >= 0: return self.do_scrap() else: return { 'name': _('Insufficient Quantity'), 'view_type': 'form', 'view_mode': 'form', 'res_model': 'stock.warn.insufficient.qty.scrap', 'view_id': self.env.ref('stock.stock_warn_insufficient_qty_scrap_form_view').id, 'type': 'ir.actions.act_window', 'context': { 'default_product_id': self.product_id.id, 'default_location_id': self.location_id.id, 'default_scrap_id': self.id }, 'target': 'new' }
def create_move(self, post_move=True): created_moves = self.env['account.move'] prec = self.env['decimal.precision'].precision_get('Account') for line in self: if line.move_id: raise UserError(_('This depreciation is already linked to a journal entry! Please post or delete it.')) category_id = line.asset_id.category_id depreciation_date = self.env.context.get('depreciation_date') or line.depreciation_date or fields.Date.context_today(self) company_currency = line.asset_id.company_id.currency_id current_currency = line.asset_id.currency_id amount = current_currency.with_context(date=depreciation_date).compute(line.amount, company_currency) asset_name = line.asset_id.name + ' (%s/%s)' % (line.sequence, len(line.asset_id.depreciation_line_ids)) move_line_1 = { 'name': asset_name, 'account_id': category_id.account_depreciation_id.id, 'debit': 0.0 if float_compare(amount, 0.0, precision_digits=prec) > 0 else -amount, 'credit': amount if float_compare(amount, 0.0, precision_digits=prec) > 0 else 0.0, 'journal_id': category_id.journal_id.id, 'partner_id': line.asset_id.partner_id.id, 'analytic_account_id': category_id.account_analytic_id.id if category_id.type == 'sale' else False, 'currency_id': company_currency != current_currency and current_currency.id or False, 'amount_currency': company_currency != current_currency and - 1.0 * line.amount or 0.0, } move_line_2 = { 'name': asset_name, 'account_id': category_id.account_depreciation_expense_id.id, 'credit': 0.0 if float_compare(amount, 0.0, precision_digits=prec) > 0 else -amount, 'debit': amount if float_compare(amount, 0.0, precision_digits=prec) > 0 else 0.0, 'journal_id': category_id.journal_id.id, 'partner_id': line.asset_id.partner_id.id, 'analytic_account_id': category_id.account_analytic_id.id if category_id.type == 'purchase' else False, 'currency_id': company_currency != current_currency and current_currency.id or False, 'amount_currency': company_currency != current_currency and line.amount or 0.0, } move_vals = { 'ref': line.asset_id.code, 'date': depreciation_date or False, 'journal_id': category_id.journal_id.id, 'line_ids': [(0, 0, move_line_1), (0, 0, move_line_2)], } move = self.env['account.move'].create(move_vals) line.write({'move_id': move.id, 'move_check': True}) created_moves |= move if post_move and created_moves: created_moves.filtered(lambda m: any(m.asset_depreciation_ids.mapped('asset_id.category_id.open_asset'))).post() return [x.id for x in created_moves]
def _check_main_currency_rounding(self): if any(precision.name == 'Account' and tools.float_compare( self.env.user.company_id.currency_id.rounding, 10**-precision.digits, precision_digits=6) == -1 for precision in self): raise ValidationError( _("You cannot define the decimal precision of 'Account' as greater than the rounding factor of the company's main currency" )) return True
def reconcile(self, writeoff_acc_id=False, writeoff_journal_id=False): res = super(AccountMoveLine, self).reconcile(writeoff_acc_id=writeoff_acc_id, writeoff_journal_id=writeoff_journal_id) account_move_ids = [l.move_id.id for l in self if float_compare(l.move_id.matched_percentage, 1, precision_digits=5) == 0] if account_move_ids: expense_sheets = self.env['hr.expense.sheet'].search([ ('account_move_id', 'in', account_move_ids), ('state', '!=', 'done') ]) expense_sheets.set_to_paid() return res
def _update_line_quantity(self, values): precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') if self.mapped('qty_delivered') and float_compare(values['product_uom_qty'], max(self.mapped('qty_delivered')), precision_digits=precision) == -1: raise UserError('You cannot decrease the ordered quantity below the delivered quantity.\n' 'Create a return first.') for line in self: pickings = line.order_id.picking_ids.filtered(lambda p: p.state not in ('done', 'cancel')) for picking in pickings: picking.message_post("The quantity of %s has been updated from %d to %d in %s" % (line.product_id.display_name, line.product_uom_qty, values['product_uom_qty'], line.order_id.name)) super(SaleOrderLine, self)._update_line_quantity(values)
def write(self, values): lines = self.env['sale.order.line'] if 'product_uom_qty' in values: precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') lines = self.filtered( lambda r: r.state == 'sale' and float_compare(r.product_uom_qty, values['product_uom_qty'], precision_digits=precision) == -1) previous_product_uom_qty = {line.id: line.product_uom_qty for line in lines} res = super(SaleOrderLine, self).write(values) if lines: lines.with_context(previous_product_uom_qty=previous_product_uom_qty)._action_launch_procurement_rule() return res
def _action_launch_procurement_rule(self): """ Launch procurement group run method with required/custom fields genrated by a sale order line. procurement group will launch '_run_move', '_run_buy' or '_run_manufacture' depending on the sale order line product rule. """ precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') errors = [] for line in self: if line.state != 'sale' or not line.product_id.type in ('consu','product'): continue qty = line._get_qty_procurement() if float_compare(qty, line.product_uom_qty, precision_digits=precision) >= 0: continue group_id = line.order_id.procurement_group_id if not group_id: group_id = self.env['procurement.group'].create({ 'name': line.order_id.name, 'move_type': line.order_id.picking_policy, 'sale_id': line.order_id.id, 'partner_id': line.order_id.partner_shipping_id.id, }) line.order_id.procurement_group_id = group_id else: # In case the procurement group is already created and the order was # cancelled, we need to update certain values of the group. updated_vals = {} if group_id.partner_id != line.order_id.partner_shipping_id: updated_vals.update({'partner_id': line.order_id.partner_shipping_id.id}) if group_id.move_type != line.order_id.picking_policy: updated_vals.update({'move_type': line.order_id.picking_policy}) if updated_vals: group_id.write(updated_vals) values = line._prepare_procurement_values(group_id=group_id) product_qty = line.product_uom_qty - qty procurement_uom = line.product_uom quant_uom = line.product_id.uom_id get_param = self.env['ir.config_parameter'].sudo().get_param if procurement_uom.id != quant_uom.id and get_param('stock.propagate_uom') != '1': product_qty = line.product_uom._compute_quantity(product_qty, quant_uom, rounding_method='HALF-UP') procurement_uom = quant_uom try: self.env['procurement.group'].run(line.product_id, product_qty, procurement_uom, line.order_id.partner_shipping_id.property_stock_customer, line.name, line.order_id.name, values) except UserError as error: errors.append(error.name) if errors: raise UserError('\n'.join(errors)) return True
def _confirm_invoice(self): """ Check tx state, confirm and pay potential invoice """ self.ensure_one() # check tx state, confirm the potential SO if self.account_invoice_id.state != 'open': _logger.warning( '<%s> transaction STATE INCORRECT for invoice %s (ID %s, state %s)', self.acquirer_id.provider, self.account_invoice_id.number, self.account_invoice_id.id, self.account_invoice_id.state) return 'pay_invoice_invalid_doc_state' if not float_compare(self.amount, self.account_invoice_id.amount_total, 2) == 0: _logger.warning( '<%s> transaction AMOUNT MISMATCH for invoice %s (ID %s): expected %r, got %r', self.acquirer_id.provider, self.account_invoice_id.number, self.account_invoice_id.id, self.account_invoice_id.amount_total, self.amount, ) self.account_invoice_id.message_post( subject=_("Amount Mismatch (%s)") % self.acquirer_id.provider, body= _("The invoice was not confirmed despite response from the acquirer (%s): invoice amount is %r but acquirer replied with %r." ) % ( self.acquirer_id.provider, self.account_invoice_id.amount_total, self.amount, )) return 'pay_invoice_tx_amount' if self.state == 'authorized' and self.acquirer_id.capture_manually: _logger.info( '<%s> transaction authorized, nothing to do with invoice %s (ID %s)', self.acquirer_id.provider, self.account_invoice_id.number, self.account_invoice_id.id) elif self.state == 'done': _logger.info( '<%s> transaction completed, paying invoice %s (ID %s)', self.acquirer_id.provider, self.account_invoice_id.number, self.account_invoice_id.id) self._pay_invoice() else: _logger.warning('<%s> transaction MISMATCH for invoice %s (ID %s)', self.acquirer_id.provider, self.account_invoice_id.number, self.account_invoice_id.id) return 'pay_invoice_tx_state' return True
def _onchange_qty_done(self): """ When the user is encoding a produce 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.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 _check_sum(self): """ Check if each cost line its valuation lines sum to the correct amount and if the overall total amount is correct also """ prec_digits = self.env['decimal.precision'].precision_get('Account') for landed_cost in self: total_amount = sum( landed_cost.valuation_adjustment_lines.mapped( 'additional_landed_cost')) if not tools.float_compare(total_amount, landed_cost.amount_total, precision_digits=prec_digits) == 0: return False val_to_cost_lines = defaultdict(lambda: 0.0) for val_line in landed_cost.valuation_adjustment_lines: val_to_cost_lines[ val_line.cost_line_id] += val_line.additional_landed_cost if any( tools.float_compare(cost_line.price_unit, val_amount, precision_digits=prec_digits) != 0 for cost_line, val_amount in val_to_cost_lines.items()): return False return True
def do_produce(self): # Nothing to do for lots since values are created using default data (stock.move.lots) quantity = self.product_qty if float_compare(quantity, 0, precision_rounding=self.product_uom_id.rounding) <= 0: raise UserError( _("The production order for '%s' has no quantity specified") % self.product_id.display_name) for move in self.production_id.move_raw_ids: # TODO currently not possible to guess if the user updated quantity by hand or automatically by the produce wizard. if move.product_id.tracking == 'none' and move.state not in ( 'done', 'cancel') and move.unit_factor: rounding = move.product_uom.rounding if self.product_id.tracking != 'none': qty_to_add = float_round(quantity * move.unit_factor, precision_rounding=rounding) move._generate_consumed_move_line(qty_to_add, self.lot_id) elif len(move._get_move_lines()) < 2: move.quantity_done += float_round( quantity * move.unit_factor, precision_rounding=rounding) else: move._set_quantity_done(quantity * move.unit_factor) for move in self.production_id.move_finished_ids: if move.product_id.tracking == 'none' and move.state not in ( 'done', 'cancel'): rounding = move.product_uom.rounding if move.product_id.id == self.production_id.product_id.id: move.quantity_done += float_round( quantity, precision_rounding=rounding) elif move.unit_factor: # byproducts handling move.quantity_done += float_round( quantity * move.unit_factor, precision_rounding=rounding) self.check_finished_move_lots() if self.production_id.state == 'confirmed': self.production_id.write({ 'state': 'progress', 'date_start': datetime.now(), }) return {'type': 'ir.actions.act_window_close'}
def _onchange_qty_producing(self): """ Update stock.move.lot records, according to the new qty currently produced. """ moves = self.move_raw_ids.filtered(lambda move: move.state not in ('done', 'cancel') and move.product_id.tracking != 'none' and move.product_id.id != self.production_id.product_id.id) for move in moves: move_lots = self.active_move_line_ids.filtered(lambda move_lot: move_lot.move_id == move) if not move_lots: continue rounding = move.product_uom.rounding new_qty = float_round(move.unit_factor * self.qty_producing, precision_rounding=rounding) if move.product_id.tracking == 'lot': move_lots[0].product_qty = new_qty move_lots[0].qty_done = new_qty elif move.product_id.tracking == 'serial': # Create extra pseudo record qty_todo = float_round(new_qty - sum(move_lots.mapped('qty_done')), precision_rounding=rounding) if float_compare(qty_todo, 0.0, precision_rounding=rounding) > 0: while float_compare(qty_todo, 0.0, precision_rounding=rounding) > 0: self.active_move_line_ids += self.env['stock.move.line'].new({ 'move_id': move.id, 'product_id': move.product_id.id, 'lot_id': False, 'product_uom_qty': 0.0, 'product_uom_id': move.product_uom.id, 'qty_done': min(1.0, qty_todo), 'workorder_id': self.id, 'done_wo': False, 'location_id': move.location_id.id, 'location_dest_id': move.location_dest_id.id, 'date': move.date, }) qty_todo -= 1 elif float_compare(qty_todo, 0.0, precision_rounding=rounding) < 0: qty_todo = abs(qty_todo) for move_lot in move_lots: if float_compare(qty_todo, 0, precision_rounding=rounding) <= 0: break if not move_lot.lot_id and float_compare(qty_todo, move_lot.qty_done, precision_rounding=rounding) >= 0: qty_todo = float_round(qty_todo - move_lot.qty_done, precision_rounding=rounding) self.active_move_line_ids -= move_lot # Difference operator else: #move_lot.product_qty = move_lot.product_qty - qty_todo if float_compare(move_lot.qty_done - qty_todo, 0, precision_rounding=rounding) == 1: move_lot.qty_done = move_lot.qty_done - qty_todo else: move_lot.qty_done = 0 qty_todo = 0
def compare_amounts(self, amount1, amount2): """Compare ``amount1`` and ``amount2`` after rounding them according to the given currency's precision.. An amount is considered lower/greater than another amount if their rounded value is different. This is not the same as having a non-zero difference! For example 1.432 and 1.431 are equal at 2 digits precision, so this method would return 0. However 0.006 and 0.002 are considered different (returns 1) because they respectively round to 0.01 and 0.0, even though 0.006-0.002 = 0.004 which would be considered zero at 2 digits precision. :param float amount1: first amount to compare :param float amount2: second amount to compare :return: (resp.) -1, 0 or 1, if ``amount1`` is (resp.) lower than, equal to, or greater than ``amount2``, according to ``currency``'s rounding. With the new API, call it like: ``currency.compare_amounts(amount1, amount2)``. """ return tools.float_compare(amount1, amount2, precision_rounding=self.rounding)
def _select_seller(self, partner_id=False, quantity=0.0, date=None, uom_id=False): self.ensure_one() if date is None: date = fields.Date.context_today(self) precision = self.env['decimal.precision'].precision_get( 'Product Unit of Measure') res = self.env['product.supplierinfo'] for seller in self.seller_ids: # Set quantity in UoM of seller quantity_uom_seller = quantity if quantity_uom_seller and uom_id and uom_id != seller.product_uom: quantity_uom_seller = uom_id._compute_quantity( quantity_uom_seller, seller.product_uom) if seller.date_start and seller.date_start > date: continue if seller.date_end and seller.date_end < date: continue if partner_id and seller.name not in [ partner_id, partner_id.parent_id ]: continue if float_compare(quantity_uom_seller, seller.min_qty, precision_digits=precision) == -1: continue if seller.product_id and seller.product_id != self: continue res |= seller break return res
def _confirm_so(self): """ Check tx state, confirm the potential SO """ self.ensure_one() if self.sale_order_id.state not in ['draft', 'sent', 'sale']: _logger.warning('<%s> transaction STATE INCORRECT for order %s (ID %s, state %s)', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id, self.sale_order_id.state) return 'pay_sale_invalid_doc_state' if not float_compare(self.amount, self.sale_order_id.amount_total, 2) == 0: _logger.warning( '<%s> transaction AMOUNT MISMATCH for order %s (ID %s): expected %r, got %r', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id, self.sale_order_id.amount_total, self.amount, ) self.sale_order_id.message_post( subject=_("Amount Mismatch (%s)") % self.acquirer_id.provider, body=_("The sale order was not confirmed despite response from the acquirer (%s): SO amount is %r but acquirer replied with %r.") % ( self.acquirer_id.provider, self.sale_order_id.amount_total, self.amount, ) ) return 'pay_sale_tx_amount' if self.state == 'authorized' and self.acquirer_id.capture_manually: _logger.info('<%s> transaction authorized, auto-confirming order %s (ID %s)', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id) if self.sale_order_id.state in ('draft', 'sent'): self.sale_order_id.with_context(send_email=True).action_confirm() elif self.state == 'done': _logger.info('<%s> transaction completed, auto-confirming order %s (ID %s)', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id) if self.sale_order_id.state in ('draft', 'sent'): self.sale_order_id.with_context(send_email=True).action_confirm() elif self.state not in ['cancel', 'error'] and self.sale_order_id.state == 'draft': _logger.info('<%s> transaction pending/to confirm manually, sending quote email for order %s (ID %s)', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id) self.sale_order_id.force_quotation_send() else: _logger.warning('<%s> transaction MISMATCH for order %s (ID %s)', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id) return 'pay_sale_tx_state' return True
def _generate_lot_ids(self): """ Generate stock move lines """ self.ensure_one() MoveLine = self.env['stock.move.line'] tracked_moves = self.move_raw_ids.filtered( lambda move: move.state not in ('done', 'cancel') and move.product_id.tracking != 'none' and move.product_id != self.production_id.product_id and move.bom_line_id) for move in tracked_moves: qty = move.unit_factor * self.qty_producing if move.product_id.tracking == 'serial': while float_compare(qty, 0.0, precision_rounding=move.product_uom.rounding) > 0: MoveLine.create({ 'move_id': move.id, 'product_uom_qty': 0, 'product_uom_id': move.product_uom.id, 'qty_done': min(1, qty), 'production_id': self.production_id.id, 'workorder_id': self.id, 'product_id': move.product_id.id, 'done_wo': False, 'location_id': move.location_id.id, 'location_dest_id': move.location_dest_id.id, }) qty -= 1 else: MoveLine.create({ 'move_id': move.id, 'product_uom_qty': 0, 'product_uom_id': move.product_uom.id, 'qty_done': qty, 'product_id': move.product_id.id, 'production_id': self.production_id.id, 'workorder_id': self.id, 'done_wo': False, 'location_id': move.location_id.id, 'location_dest_id': move.location_dest_id.id, })
def record_production(self): self.ensure_one() if self.qty_producing <= 0: raise UserError(_('Please set the quantity you are currently producing. It should be different from zero.')) if (self.production_id.product_id.tracking != 'none') and not self.final_lot_id and self.move_raw_ids: raise UserError(_('You should provide a lot/serial number for the final product')) # Update quantities done on each raw material line # For each untracked component without any 'temporary' move lines, # (the new workorder tablet view allows registering consumed quantities for untracked components) # we assume that only the theoretical quantity was used for move in self.move_raw_ids: if move.has_tracking == 'none' and (move.state not in ('done', 'cancel')) and move.bom_line_id\ and move.unit_factor and not move.move_line_ids.filtered(lambda ml: not ml.done_wo): rounding = move.product_uom.rounding if self.product_id.tracking != 'none': qty_to_add = float_round(self.qty_producing * move.unit_factor, precision_rounding=rounding) move._generate_consumed_move_line(qty_to_add, self.final_lot_id) elif len(move._get_move_lines()) < 2: move.quantity_done += float_round(self.qty_producing * move.unit_factor, precision_rounding=rounding) else: move._set_quantity_done(move.quantity_done + float_round(self.qty_producing * move.unit_factor, precision_rounding=rounding)) # Transfer quantities from temporary to final move lots or make them final for move_line in self.active_move_line_ids: # Check if move_line already exists if move_line.qty_done <= 0: # rounding... move_line.sudo().unlink() continue if move_line.product_id.tracking != 'none' and not move_line.lot_id: raise UserError(_('You should provide a lot/serial number for a component')) # Search other move_line where it could be added: lots = self.move_line_ids.filtered(lambda x: (x.lot_id.id == move_line.lot_id.id) and (not x.lot_produced_id) and (not x.done_move) and (x.product_id == move_line.product_id)) if lots: lots[0].qty_done += move_line.qty_done lots[0].lot_produced_id = self.final_lot_id.id move_line.sudo().unlink() else: move_line.lot_produced_id = self.final_lot_id.id move_line.done_wo = True # One a piece is produced, you can launch the next work order if self.next_work_order_id.state == 'pending': self.next_work_order_id.state = 'ready' self.move_line_ids.filtered( lambda move_line: not move_line.done_move and not move_line.lot_produced_id and move_line.qty_done > 0 ).write({ 'lot_produced_id': self.final_lot_id.id, 'lot_produced_qty': self.qty_producing }) # If last work order, then post lots used # TODO: should be same as checking if for every workorder something has been done? if not self.next_work_order_id: production_move = self.production_id.move_finished_ids.filtered( lambda x: (x.product_id.id == self.production_id.product_id.id) and (x.state not in ('done', 'cancel'))) if production_move.product_id.tracking != 'none': move_line = production_move.move_line_ids.filtered(lambda x: x.lot_id.id == self.final_lot_id.id) if move_line: move_line.product_uom_qty += self.qty_producing move_line.qty_done += self.qty_producing else: move_line.create({'move_id': production_move.id, 'product_id': production_move.product_id.id, 'lot_id': self.final_lot_id.id, 'product_uom_qty': self.qty_producing, 'product_uom_id': production_move.product_uom.id, 'qty_done': self.qty_producing, 'workorder_id': self.id, 'location_id': production_move.location_id.id, 'location_dest_id': production_move.location_dest_id.id, }) else: production_move.quantity_done += self.qty_producing if not self.next_work_order_id: for by_product_move in self.production_id.move_finished_ids.filtered(lambda x: (x.product_id.id != self.production_id.product_id.id) and (x.state not in ('done', 'cancel'))): if by_product_move.has_tracking != 'serial': values = self._get_byproduct_move_line(by_product_move, self.qty_producing * by_product_move.unit_factor) self.env['stock.move.line'].create(values) elif by_product_move.has_tracking == 'serial': qty_todo = by_product_move.product_uom._compute_quantity(self.qty_producing * by_product_move.unit_factor, by_product_move.product_id.uom_id) for i in range(0, int(float_round(qty_todo, precision_digits=0))): values = self._get_byproduct_move_line(by_product_move, 1) self.env['stock.move.line'].create(values) # Update workorder quantity produced self.qty_produced += self.qty_producing if self.final_lot_id: self.final_lot_id.use_next_on_work_order_id = self.next_work_order_id self.final_lot_id = False # Set a qty producing rounding = self.production_id.product_uom_id.rounding if float_compare(self.qty_produced, self.production_id.product_qty, precision_rounding=rounding) >= 0: self.qty_producing = 0 elif self.production_id.product_id.tracking == 'serial': self._assign_default_final_lot_id() self.qty_producing = 1.0 self._generate_lot_ids() else: self.qty_producing = float_round(self.production_id.product_qty - self.qty_produced, precision_rounding=rounding) self._generate_lot_ids() if self.next_work_order_id and self.production_id.product_id.tracking != 'none': self.next_work_order_id._assign_default_final_lot_id() if float_compare(self.qty_produced, self.production_id.product_qty, precision_rounding=rounding) >= 0: self.button_finish() return True
def _generate_consumed_move_line(self, qty_to_add, final_lot, lot=False): if lot: move_lines = self.move_line_ids.filtered( lambda ml: ml.lot_id == lot and not ml.lot_produced_id) else: move_lines = self.move_line_ids.filtered( lambda ml: not ml.lot_id and not ml.lot_produced_id) # Sanity check: if the product is a serial number and `lot` is already present in the other # consumed move lines, raise. if lot and self.product_id.tracking == 'serial' and lot in self.move_line_ids.filtered( lambda ml: ml.qty_done).mapped('lot_id'): raise UserError( _('You cannot consume the same serial number twice. Please correct the serial numbers encoded.' )) for ml in move_lines: rounding = ml.product_uom_id.rounding if float_compare(qty_to_add, 0, precision_rounding=rounding) <= 0: break quantity_to_process = min(qty_to_add, ml.product_uom_qty - ml.qty_done) qty_to_add -= quantity_to_process new_quantity_done = (ml.qty_done + quantity_to_process) if float_compare(new_quantity_done, ml.product_uom_qty, precision_rounding=rounding) >= 0: ml.write({ 'qty_done': new_quantity_done, 'lot_produced_id': final_lot.id }) else: new_qty_reserved = ml.product_uom_qty - new_quantity_done default = { 'product_uom_qty': new_quantity_done, 'qty_done': new_quantity_done, 'lot_produced_id': final_lot.id } ml.copy(default=default) ml.with_context(bypass_reservation_update=True).write({ 'product_uom_qty': new_qty_reserved, 'qty_done': 0 }) if float_compare(qty_to_add, 0, precision_rounding=self.product_uom.rounding) > 0: # Search for a sub-location where the product is available. This might not be perfectly # correct if the quantity available is spread in several sub-locations, but at least # we should be closer to the reality. Anyway, no reservation is made, so it is still # possible to change it afterwards. quants = self.env['stock.quant']._gather(self.product_id, self.location_id, lot_id=lot, strict=False) available_quantity = self.product_id.uom_id._compute_quantity( self.env['stock.quant']._get_available_quantity( self.product_id, self.location_id, lot_id=lot, strict=False), self.product_uom) location_id = False if float_compare(qty_to_add, available_quantity, precision_rounding=self.product_uom.rounding) < 0: location_id = quants.filtered( lambda r: r.quantity > 0)[-1:].location_id vals = { 'move_id': self.id, 'product_id': self.product_id.id, 'location_id': location_id.id if location_id else self.location_id.id, 'location_dest_id': self.location_dest_id.id, 'product_uom_qty': 0, 'product_uom_id': self.product_uom.id, 'qty_done': qty_to_add, 'lot_produced_id': final_lot.id, } if lot: vals.update({'lot_id': lot.id}) self.env['stock.move.line'].create(vals)
def _compute_is_produced(self): rounding = self.production_id.product_uom_id.rounding self.is_produced = float_compare(self.qty_produced, self.production_id.product_qty, precision_rounding=rounding) >= 0
def action_payslip_done(self): res = super(HrPayslip, self).action_payslip_done() precision = self.env['decimal.precision'].precision_get('Payroll') for slip in self: line_ids = [] debit_sum = 0.0 credit_sum = 0.0 date = slip.date or slip.date_to name = _('Payslip of %s') % (slip.employee_id.name) move_dict = { 'narration': name, 'ref': slip.number, 'journal_id': slip.journal_id.id, 'date': date, } for line in slip.details_by_salary_rule_category: amount = slip.credit_note and -line.total or line.total if float_is_zero(amount, precision_digits=precision): continue debit_account_id = line.salary_rule_id.account_debit.id credit_account_id = line.salary_rule_id.account_credit.id if debit_account_id: debit_line = (0, 0, { 'name': line.name, 'partner_id': line._get_partner_id(credit_account=False), 'account_id': debit_account_id, 'journal_id': slip.journal_id.id, 'date': date, 'debit': amount > 0.0 and amount or 0.0, 'credit': amount < 0.0 and -amount or 0.0, 'analytic_account_id': line.salary_rule_id.analytic_account_id.id, 'tax_line_id': line.salary_rule_id.account_tax_id.id, }) line_ids.append(debit_line) debit_sum += debit_line[2]['debit'] - debit_line[2][ 'credit'] if credit_account_id: credit_line = (0, 0, { 'name': line.name, 'partner_id': line._get_partner_id(credit_account=True), 'account_id': credit_account_id, 'journal_id': slip.journal_id.id, 'date': date, 'debit': amount < 0.0 and -amount or 0.0, 'credit': amount > 0.0 and amount or 0.0, 'analytic_account_id': line.salary_rule_id.analytic_account_id.id, 'tax_line_id': line.salary_rule_id.account_tax_id.id, }) line_ids.append(credit_line) credit_sum += credit_line[2]['credit'] - credit_line[2][ 'debit'] if float_compare(credit_sum, debit_sum, precision_digits=precision) == -1: acc_id = slip.journal_id.default_credit_account_id.id if not acc_id: raise UserError( _('The Expense Journal "%s" has not properly configured the Credit Account!' ) % (slip.journal_id.name)) adjust_credit = (0, 0, { 'name': _('Adjustment Entry'), 'partner_id': False, 'account_id': acc_id, 'journal_id': slip.journal_id.id, 'date': date, 'debit': 0.0, 'credit': debit_sum - credit_sum, }) line_ids.append(adjust_credit) elif float_compare(debit_sum, credit_sum, precision_digits=precision) == -1: acc_id = slip.journal_id.default_debit_account_id.id if not acc_id: raise UserError( _('The Expense Journal "%s" has not properly configured the Debit Account!' ) % (slip.journal_id.name)) adjust_debit = (0, 0, { 'name': _('Adjustment Entry'), 'partner_id': False, 'account_id': acc_id, 'journal_id': slip.journal_id.id, 'date': date, 'debit': credit_sum - debit_sum, 'credit': 0.0, }) line_ids.append(adjust_debit) move_dict['line_ids'] = line_ids move = self.env['account.move'].create(move_dict) slip.write({'move_id': move.id, 'date': date}) move.post() return res
def test_order_to_invoice(self): # I create a new PoS order with 2 units of PC1 at 450 EUR (Tax Incl) and 3 units of PCSC349 at 300 EUR. (Tax Excl) self.pos_order_pos1 = self.PosOrder.create({ 'company_id': self.company_id, 'partner_id': self.partner1.id, 'pricelist_id': self.partner1.property_product_pricelist.id, 'lines': [(0, 0, { 'name': "OL/0001", 'product_id': self.product3.id, 'price_unit': 450, 'discount': 5.0, 'qty': 2.0, 'tax_ids': [(6, 0, self.product3.taxes_id.ids)], }), (0, 0, { 'name': "OL/0002", 'product_id': self.product4.id, 'price_unit': 300, 'discount': 5.0, 'qty': 3.0, 'tax_ids': [(6, 0, self.product4.taxes_id.ids)], })] }) # I click on the "Make Payment" wizard to pay the PoS order context_make_payment = {"active_ids": [self.pos_order_pos1.id], "active_id": self.pos_order_pos1.id} self.pos_make_payment = self.PosMakePayment.with_context(context_make_payment).create({ 'amount': (450 * 2 + 300 * 3 * 1.05) * 0.95, }) # I click on the validate button to register the payment. context_payment = {'active_id': self.pos_order_pos1.id} self.pos_make_payment.with_context(context_payment).check() # I check that the order is marked as paid and there is no invoice # attached to it self.assertEqual(self.pos_order_pos1.state, 'paid', "Order should be in paid state.") self.assertFalse(self.pos_order_pos1.invoice_id, 'Invoice should not be attached to order.') # I generate an invoice from the order res = self.pos_order_pos1.action_pos_order_invoice() self.assertIn('res_id', res, "No invoice created") # I test that the total of the attached invoice is correct invoice = self.env['account.invoice'].browse(res['res_id']) self.assertEqual( float_compare(invoice.amount_total, 1752.75, precision_digits=2), 0, "Invoice not correct") """In order to test the reports on Bank Statement defined in point_of_sale module, I create a bank statement line, confirm it and print the reports""" # I select the period and journal for the bank statement context_journal = {'journal_type': 'bank'} self.assertTrue(self.AccountBankStatement.with_context( context_journal)._default_journal(), 'Journal has not been selected') journal = self.env['account.journal'].create({ 'name': 'Bank Test', 'code': 'BNKT', 'type': 'bank', 'company_id': self.company_id, }) # I create a bank statement with Opening and Closing balance 0. account_statement = self.AccountBankStatement.create({ 'balance_start': 0.0, 'balance_end_real': 0.0, 'date': time.strftime('%Y-%m-%d'), 'journal_id': journal.id, 'company_id': self.company_id, 'name': 'pos session test', }) # I create bank statement line account_statement_line = self.AccountBankStatementLine.create({ 'amount': 1000, 'partner_id': self.partner4.id, 'statement_id': account_statement.id, 'name': 'EXT001' }) # I modify the bank statement and set the Closing Balance. account_statement.write({ 'balance_end_real': 1000.0, }) # I reconcile the bank statement. new_aml_dicts = [{ 'account_id': self.partner4.property_account_receivable_id.id, 'name': "EXT001", 'credit': 1000.0, 'debit': 0.0, }] account_statement_line.process_reconciliations([{'new_aml_dicts': new_aml_dicts}]) # I confirm the bank statement using Confirm button self.AccountBankStatement.button_confirm_bank()
def _procure_orderpoint_confirm(self, use_new_cursor=False, company_id=False): """ Create procurements based on orderpoints. :param bool use_new_cursor: if set, use a dedicated cursor and auto-commit after processing 1000 orderpoints. This is appropriate for batch jobs only. """ if company_id and self.env.user.company_id.id != company_id: # To ensure that the company_id is taken into account for # all the processes triggered by this method # i.e. If a PO is generated by the run of the procurements the # sequence to use is the one for the specified company not the # one of the user's company self = self.with_context(company_id=company_id, force_company=company_id) OrderPoint = self.env['stock.warehouse.orderpoint'] domain = self._get_orderpoint_domain(company_id=company_id) orderpoints_noprefetch = OrderPoint.with_context( prefetch_fields=False).search( domain, order=self._procurement_from_orderpoint_get_order()).ids while orderpoints_noprefetch: if use_new_cursor: cr = registry(self._cr.dbname).cursor() self = self.with_env(self.env(cr=cr)) OrderPoint = self.env['stock.warehouse.orderpoint'] orderpoints = OrderPoint.browse(orderpoints_noprefetch[:1000]) orderpoints_noprefetch = orderpoints_noprefetch[1000:] # Calculate groups that can be executed together location_data = OrderedDict() def makedefault(): return { 'products': self.env['product.product'], 'orderpoints': self.env['stock.warehouse.orderpoint'], 'groups': [] } for orderpoint in orderpoints: key = self._procurement_from_orderpoint_get_grouping_key( [orderpoint.id]) if not location_data.get(key): location_data[key] = makedefault() location_data[key]['products'] += orderpoint.product_id location_data[key]['orderpoints'] += orderpoint location_data[key][ 'groups'] = self._procurement_from_orderpoint_get_groups( [orderpoint.id]) for location_id, location_data in location_data.items(): location_orderpoints = location_data['orderpoints'] product_context = dict( self._context, location=location_orderpoints[0].location_id.id) substract_quantity = location_orderpoints._quantity_in_progress( ) for group in location_data['groups']: if group.get('from_date'): product_context['from_date'] = group[ 'from_date'].strftime( DEFAULT_SERVER_DATETIME_FORMAT) if group['to_date']: product_context['to_date'] = group['to_date'].strftime( DEFAULT_SERVER_DATETIME_FORMAT) product_quantity = location_data['products'].with_context( product_context)._product_available() for orderpoint in location_orderpoints: try: op_product_virtual = product_quantity[ orderpoint.product_id.id]['virtual_available'] if op_product_virtual is None: continue if float_compare(op_product_virtual, orderpoint.product_min_qty, precision_rounding=orderpoint. product_uom.rounding) <= 0: qty = max(orderpoint.product_min_qty, orderpoint.product_max_qty ) - op_product_virtual remainder = orderpoint.qty_multiple > 0 and qty % orderpoint.qty_multiple or 0.0 if float_compare(remainder, 0.0, precision_rounding=orderpoint. product_uom.rounding) > 0: qty += orderpoint.qty_multiple - remainder if float_compare(qty, 0.0, precision_rounding=orderpoint. product_uom.rounding) < 0: continue qty -= substract_quantity[orderpoint.id] qty_rounded = float_round( qty, precision_rounding=orderpoint.product_uom. rounding) if qty_rounded > 0: values = orderpoint._prepare_procurement_values( qty_rounded, **group['procurement_values']) try: with self._cr.savepoint(): self.env['procurement.group'].run( orderpoint.product_id, qty_rounded, orderpoint.product_uom, orderpoint.location_id, orderpoint.name, orderpoint.name, values) except UserError as error: self.env[ 'procurement.rule']._log_next_activity( orderpoint.product_id, error.name) self._procurement_from_orderpoint_post_process( [orderpoint.id]) if use_new_cursor: cr.commit() except OperationalError: if use_new_cursor: orderpoints_noprefetch += [orderpoint.id] cr.rollback() continue else: raise try: if use_new_cursor: cr.commit() except OperationalError: if use_new_cursor: cr.rollback() continue else: raise if use_new_cursor: cr.commit() cr.close() return {}
def test_10_calculation_price_of_products_pricelist(self): """Test calculation of product price based on pricelist""" # I check sale price of iPad Retina Display context = {} context.update({ 'pricelist': self.customer_pricelist.id, 'quantity': 1 }) ipad_retina_display = self.ipad_retina_display.with_context(context) msg = "Wrong sale price: iPad Retina Display. should be %s instead of %s" % ( ipad_retina_display.price, (ipad_retina_display.lst_price - ipad_retina_display.lst_price * (0.10))) self.assertEqual( float_compare(ipad_retina_display.price, (ipad_retina_display.lst_price - ipad_retina_display.lst_price * (0.10)), precision_digits=2), 0, msg) # I check sale price of Laptop. laptop_E5023 = self.laptop_E5023.with_context(context) msg = "Wrong sale price: Laptop. should be %s instead of %s" % ( laptop_E5023.price, (laptop_E5023.lst_price + 1)) self.assertEqual( float_compare(laptop_E5023.price, laptop_E5023.lst_price + 1, precision_digits=2), 0, msg) # I check sale price of IT component. apple_headphones = self.apple_in_ear_headphones.with_context(context) msg = "Wrong sale price: IT component. should be %s instead of %s" % ( apple_headphones.price, apple_headphones.lst_price) self.assertEqual( float_compare(apple_headphones.price, apple_headphones.lst_price, precision_digits=2), 0, msg) # I check sale price of IT component if more than 3 Unit. context.update({'quantity': 5}) laptop_S3450 = self.laptop_S3450.with_context(context) msg = "Wrong sale price: IT component if more than 3 Unit. should be %s instead of %s" % ( laptop_S3450.price, (laptop_S3450.lst_price - laptop_S3450.lst_price * (0.05))) self.assertEqual( float_compare(laptop_S3450.price, laptop_S3450.lst_price - laptop_S3450.lst_price * (0.05), precision_digits=2), 0, msg) # I check sale price of LCD Monitor. context.update({'quantity': 1}) ipad_mini = self.ipad_mini.with_context(context) msg = "Wrong sale price: LCD Monitor. should be %s instead of %s" % ( ipad_mini.price, ipad_mini.lst_price) self.assertEqual( float_compare(ipad_mini.price, ipad_mini.lst_price, precision_digits=2), 0, msg) # I check sale price of LCD Monitor on end of year. context.update({'quantity': 1, 'date': '2011-12-31'}) ipad_mini = self.ipad_mini.with_context(context) msg = "Wrong sale price: LCD Monitor on end of year. should be %s instead of %s" % ( ipad_mini.price, ipad_mini.lst_price - ipad_mini.lst_price * (0.30)) self.assertEqual( float_compare(ipad_mini.price, ipad_mini.lst_price - ipad_mini.lst_price * (0.30), precision_digits=2), 0, msg) # I check cost price of LCD Monitor. context.update({ 'quantity': 1, 'date': False, 'partner_id': self.res_partner_4.id }) ipad_mini = self.ipad_mini.with_context(context) partner = self.res_partner_4.with_context(context) msg = "Wrong cost price: LCD Monitor. should be 790 instead of %s" % ipad_mini._select_seller( partner_id=partner, quantity=1.0).price self.assertEqual( float_compare(ipad_mini._select_seller(partner_id=partner, quantity=1.0).price, 790, precision_digits=2), 0, msg) # I check cost price of LCD Monitor if more than 3 Unit. context.update({'quantity': 3}) ipad_mini = self.ipad_mini.with_context(context) partner = self.res_partner_4.with_context(context) msg = "Wrong cost price: LCD Monitor if more than 3 Unit.should be 785 instead of %s" % ipad_mini._select_seller( partner_id=partner, quantity=3.0).price self.assertEqual( float_compare(ipad_mini._select_seller(partner_id=partner, quantity=3.0).price, 785, precision_digits=2), 0, msg) # I print the sale prices report. ctx = { 'active_model': 'product.product', 'date': '2011-12-30', 'active_ids': [ self.computer_SC234.id, self.ipad_retina_display.id, self.custom_computer_kit.id, self.ipad_mini.id ] } data_dict = { 'qty1': 1, 'qty2': 5, 'qty3': 10, 'qty4': 15, 'qty5': 30, 'price_list': self.customer_pricelist.id, } test_reports.try_report_action(self.cr, self.uid, 'action_product_price_list', wiz_data=data_dict, context=ctx, our_module='product')
def action_repair_done(self): """ Creates stock move for operation and stock move for final product of repair order. @return: Move ids of final products """ if self.filtered(lambda repair: not repair.repaired): raise UserError( _("Repair must be repaired in order to make the product moves." )) res = {} precision = self.env['decimal.precision'].precision_get( 'Product Unit of Measure') Move = self.env['stock.move'] for repair in self: # Try to create move with the appropriate owner owner_id = False available_qty_owner = self.env[ 'stock.quant']._get_available_quantity( repair.product_id, repair.location_id, repair.lot_id, owner_id=repair.partner_id, strict=True) if float_compare(available_qty_owner, repair.product_qty, precision_digits=precision) >= 0: owner_id = repair.partner_id.id moves = self.env['stock.move'] for operation in repair.operations: move = Move.create({ 'name': repair.name, 'product_id': operation.product_id.id, 'product_uom_qty': operation.product_uom_qty, 'product_uom': operation.product_uom.id, 'partner_id': repair.address_id.id, 'location_id': operation.location_id.id, 'location_dest_id': operation.location_dest_id.id, 'move_line_ids': [( 0, 0, { 'product_id': operation.product_id.id, 'lot_id': operation.lot_id.id, 'product_uom_qty': 0, # bypass reservation here 'product_uom_id': operation.product_uom.id, 'qty_done': operation.product_uom_qty, 'package_id': False, 'result_package_id': False, 'owner_id': owner_id, 'location_id': operation.location_id.id, #TODO: owner stuff 'location_dest_id': operation.location_dest_id.id, })], 'repair_id': repair.id, 'origin': repair.name, }) moves |= move operation.write({'move_id': move.id, 'state': 'done'}) move = Move.create({ 'name': repair.name, 'product_id': repair.product_id.id, 'product_uom': repair.product_uom.id or repair.product_id.uom_id.id, 'product_uom_qty': repair.product_qty, 'partner_id': repair.address_id.id, 'location_id': repair.location_id.id, 'location_dest_id': repair.location_dest_id.id, 'move_line_ids': [( 0, 0, { 'product_id': repair.product_id.id, 'lot_id': repair.lot_id.id, 'product_uom_qty': 0, # bypass reservation here 'product_uom_id': repair.product_uom.id or repair.product_id.uom_id.id, 'qty_done': repair.product_qty, 'package_id': False, 'result_package_id': False, 'owner_id': owner_id, 'location_id': repair.location_id.id, #TODO: owner stuff 'location_dest_id': repair.location_dest_id.id, })], 'repair_id': repair.id, 'origin': repair.name, }) consumed_lines = moves.mapped('move_line_ids') produced_lines = move.move_line_ids moves |= move moves._action_done() produced_lines.write( {'consume_line_ids': [(6, 0, consumed_lines.ids)]}) res[repair.id] = move.id return res
def test_00_delivery_cost(self): # In order to test Carrier Cost # Create sales order with Normal Delivery Charges self.sale_normal_delivery_charges = self.SaleOrder.create({ 'partner_id': self.partner_18.id, 'partner_invoice_id': self.partner_18.id, 'partner_shipping_id': self.partner_18.id, 'pricelist_id': self.pricelist.id, 'order_line': [(0, 0, { 'name': 'PC Assamble + 2GB RAM', 'product_id': self.product_4.id, 'product_uom_qty': 1, 'product_uom': self.product_uom_unit.id, 'price_unit': 750.00, })], 'carrier_id': self.normal_delivery.id }) # I add delivery cost in Sales order self.a_sale = self.AccountAccount.create({ 'code': 'X2020', 'name': 'Product Sales - (test)', 'user_type_id': self.account_data.id, 'tag_ids': [(6, 0, { self.account_tag_operating.id })] }) self.product_consultant = self.Product.create({ 'sale_ok': True, 'list_price': 75.0, 'standard_price': 30.0, 'uom_id': self.product_uom_hour.id, 'uom_po_id': self.product_uom_hour.id, 'name': 'Service', 'categ_id': self.product_category.id, 'type': 'service' }) # I add delivery cost in Sales order self.sale_normal_delivery_charges.get_delivery_price() self.sale_normal_delivery_charges.set_delivery_line() # I check sales order after added delivery cost line = self.SaleOrderLine.search([('order_id', '=', self.sale_normal_delivery_charges.id), ('product_id', '=', self.sale_normal_delivery_charges.carrier_id.product_id.id)]) self.assertEqual(len(line), 1, "Delivery cost is not Added") self.assertEqual(float_compare(line.price_subtotal, 10.0, precision_digits=2), 0, "Delivery cost is not correspond.") # I confirm the sales order self.sale_normal_delivery_charges.action_confirm() # Create one more sales order with Free Delivery Charges self.delivery_sale_order_cost = self.SaleOrder.create({ 'partner_id': self.partner_4.id, 'partner_invoice_id': self.partner_address_13.id, 'partner_shipping_id': self.partner_address_13.id, 'pricelist_id': self.pricelist.id, 'order_line': [(0, 0, { 'name': 'Service on demand', 'product_id': self.product_consultant.id, 'product_uom_qty': 24, 'product_uom': self.product_uom_hour.id, 'price_unit': 75.00, }), (0, 0, { 'name': 'On Site Assistance', 'product_id': self.product_2.id, 'product_uom_qty': 30, 'product_uom': self.product_uom_hour.id, 'price_unit': 38.25, })], 'carrier_id': self.free_delivery.id }) # I add free delivery cost in Sales order self.delivery_sale_order_cost.get_delivery_price() self.delivery_sale_order_cost.set_delivery_line() # I check sales order after adding delivery cost line = self.SaleOrderLine.search([('order_id', '=', self.delivery_sale_order_cost.id), ('product_id', '=', self.delivery_sale_order_cost.carrier_id.product_id.id)]) self.assertEqual(len(line), 1, "Delivery cost is not Added") self.assertEqual(float_compare(line.price_subtotal, 0, precision_digits=2), 0, "Delivery cost is not correspond.") # I set default delivery policy self.default_delivery_policy = self.SaleConfigSetting.create({}) self.default_delivery_policy.execute()