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 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 _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 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 _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 _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 _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 _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 _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 _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 _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 default_get(self, fields): res = super(MrpProductProduce, self).default_get(fields) if self._context and self._context.get('active_id'): production = self.env['mrp.production'].browse(self._context['active_id']) serial_finished = (production.product_id.tracking == 'serial') if serial_finished: todo_quantity = 1.0 else: main_product_moves = production.move_finished_ids.filtered(lambda x: x.product_id.id == production.product_id.id) todo_quantity = production.product_qty - sum(main_product_moves.mapped('quantity_done')) todo_quantity = todo_quantity if (todo_quantity > 0) else 0 if 'production_id' in fields: res['production_id'] = production.id if 'product_id' in fields: res['product_id'] = production.product_id.id if 'product_uom_id' in fields: res['product_uom_id'] = production.product_uom_id.id if 'serial' in fields: res['serial'] = bool(serial_finished) if 'product_qty' in fields: res['product_qty'] = todo_quantity if 'produce_line_ids' in fields: lines = [] for move in production.move_raw_ids.filtered(lambda x: (x.product_id.tracking != 'none') and x.state not in ('done', 'cancel') and x.bom_line_id): qty_to_consume = float_round(todo_quantity / move.bom_line_id.bom_id.product_qty * move.bom_line_id.product_qty, precision_rounding=move.product_uom.rounding, rounding_method="UP") for move_line in move.move_line_ids: if float_compare(qty_to_consume, 0.0, precision_rounding=move.product_uom.rounding) <= 0: break if move_line.lot_produced_id or float_compare(move_line.product_uom_qty, move_line.qty_done, precision_rounding=move.product_uom.rounding) <= 0: continue to_consume_in_line = min(qty_to_consume, move_line.product_uom_qty) lines.append({ 'move_id': move.id, 'qty_to_consume': to_consume_in_line, 'qty_done': 0.0, 'lot_id': move_line.lot_id.id, 'product_uom_id': move.product_uom.id, 'product_id': move.product_id.id, }) qty_to_consume -= to_consume_in_line if float_compare(qty_to_consume, 0.0, precision_rounding=move.product_uom.rounding) > 0: if move.product_id.tracking == 'serial': while float_compare(qty_to_consume, 0.0, precision_rounding=move.product_uom.rounding) > 0: lines.append({ 'move_id': move.id, 'qty_to_consume': 1, 'qty_done': 0.0, 'product_uom_id': move.product_uom.id, 'product_id': move.product_id.id, }) qty_to_consume -= 1 else: lines.append({ 'move_id': move.id, 'qty_to_consume': qty_to_consume, 'qty_done': 0.0, 'product_uom_id': move.product_uom.id, 'product_id': move.product_id.id, }) res['produce_line_ids'] = [(0, 0, x) for x in lines] return res
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_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()
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 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 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 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 _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 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