def _compute_invoice_status(self): """ Compute the invoice status of a SO line. Possible statuses: - no: if the SO is not in status 'sale' or 'done', we consider that there is nothing to invoice. This is also hte default value if the conditions of no other status is met. - to invoice: we refer to the quantity to invoice of the line. Refer to method `_get_to_invoice_qty()` for more information on how this quantity is calculated. - upselling: this is possible only for a product invoiced on ordered quantities for which we delivered more than expected. The could arise if, for example, a project took more time than expected but we decided not to invoice the extra cost to the client. This occurs onyl in state 'sale', so that when a SO is set to done, the upselling opportunity is removed from the list. - invoiced: the quantity invoiced is larger or equal to the quantity ordered. """ precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') for line in self: if line.state not in ('sale', 'done'): line.invoice_status = 'no' elif not float_is_zero(line.qty_to_invoice, precision_digits=precision): line.invoice_status = 'to invoice' elif line.state == 'sale' and line.product_id.invoice_policy == 'order' and\ float_compare(line.qty_delivered, line.product_uom_qty, precision_digits=precision) == 1: line.invoice_status = 'upselling' elif float_compare(line.qty_invoiced, line.product_uom_qty, precision_digits=precision) >= 0: line.invoice_status = 'invoiced' else: line.invoice_status = 'no'
def _check_sum(self, cr, uid, landed_cost, context=None): """ Will check if each cost line its valuation lines sum to the correct amount and if the overall total amount is correct also """ costcor = {} tot = 0 for valuation_line in landed_cost.valuation_adjustment_lines: if costcor.get(valuation_line.cost_line_id): costcor[valuation_line. cost_line_id] += valuation_line.additional_landed_cost else: costcor[valuation_line. cost_line_id] = valuation_line.additional_landed_cost tot += valuation_line.additional_landed_cost prec = self.pool['decimal.precision'].precision_get(cr, uid, 'Account') # float_compare returns 0 for equal amounts res = not bool( float_compare(tot, landed_cost.amount_total, precision_digits=prec)) for costl in costcor.keys(): if float_compare(costcor[costl], costl.price_unit, precision_digits=prec): res = False return res
def do_move_consume(self, cr, uid, ids, context=None): if context is None: context = {} move_obj = self.pool.get('stock.move') uom_obj = self.pool.get('product.uom') production_obj = self.pool.get('mrp.production') move_ids = context['active_ids'] move = move_obj.browse(cr, uid, move_ids[0], context=context) production_id = move.raw_material_production_id.id production = production_obj.browse(cr, uid, production_id, context=context) precision = self.pool['decimal.precision'].precision_get(cr, uid, 'Product Unit of Measure') for data in self.browse(cr, uid, ids, context=context): qty = uom_obj._compute_qty(cr, uid, data['product_uom'].id, data.product_qty, data.product_id.uom_id.id) remaining_qty = move.product_qty - qty #check for product quantity is less than previously planned if float_compare(remaining_qty, 0, precision_digits=precision) >= 0: move_obj.action_consume(cr, uid, move_ids, qty, data.location_id.id, restrict_lot_id=data.restrict_lot_id.id, context=context) else: consumed_qty = min(move.product_qty, qty) new_moves = move_obj.action_consume(cr, uid, move_ids, consumed_qty, data.location_id.id, restrict_lot_id=data.restrict_lot_id.id, context=context) #consumed more in wizard than previously planned extra_more_qty = qty - consumed_qty #create new line for a remaining qty of the product extra_move_id = production_obj._make_consume_line_from_data(cr, uid, production, data.product_id, data.product_id.uom_id.id, extra_more_qty, context=context) move_obj.write(cr, uid, [extra_move_id], {'restrict_lot_id': data.restrict_lot_id.id}, context=context) move_obj.action_done(cr, uid, [extra_move_id], context=context) return {'type': 'ir.actions.act_window_close'}
def _action_procurement_create(self): """ Create procurements based on quantity ordered. If the quantity is increased, new procurements are created. If the quantity is decreased, no automated action is taken. """ precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') new_procs = self.env['procurement.order'] #Empty recordset for line in self: if line.state != 'sale' or not line.product_id._need_procurement(): continue qty = 0.0 for proc in line.procurement_ids: qty += proc.product_qty if float_compare(qty, line.product_uom_qty, precision_digits=precision) >= 0: return False if not line.order_id.procurement_group_id: vals = line.order_id._prepare_procurement_group() line.order_id.procurement_group_id = self.env["procurement.group"].create(vals) vals = line._prepare_order_line_procurement(group_id=line.order_id.procurement_group_id.id) vals['product_qty'] = line.product_uom_qty - qty new_proc = self.env["procurement.order"].create(vals) new_procs += new_proc new_procs.run() return new_procs
def _get_delivered_qty(self): self.ensure_one() precision = self.env['decimal.precision'].precision_get( 'Product Unit of Measure') # In the case of a kit, we need to check if all components are shipped. We use a all or # nothing policy. A product can have several BoMs, we don't know which one was used when the # delivery was created. bom_delivered = {} for bom in self.product_id.product_tmpl_id.bom_ids: if bom.type != 'phantom': continue bom_delivered[bom.id] = False bom_exploded = self.env['mrp.bom']._bom_explode( bom, self.product_id, self.product_uom_qty)[0] for bom_line in bom_exploded: qty = 0.0 for move in self.procurement_ids.mapped('move_ids'): if move.state == 'done' and move.product_id.id == bom_line.get( 'product_id', False): qty += self.env['product.uom']._compute_qty_obj( move.product_uom, move.product_uom_qty, self.product_uom) if float_compare(qty, bom_line['product_qty'], precision_digits=precision) < 0: bom_delivered[bom.id] = False break else: bom_delivered[bom.id] = True if bom_delivered and any(bom_delivered.values()): return self.product_uom_qty elif bom_delivered: return 0.0 return super(SaleOrderLine, self)._get_delivered_qty()
def write(self, values): lines = False 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) result = super(SaleOrderLine, self).write(values) if lines: lines._action_procurement_create() return result
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_qty = self.env['product.uom']._compute_qty_obj( self.product_uom, self.product_uom_qty, self.product_id.uom_id) if float_compare(self.product_id.virtual_available, product_qty, precision_digits=precision) == -1: is_available = self._check_routing() if not is_available: warning_mess = { 'title': _('Not enough inventory!'), 'message' : _('You plan to sell %.2f %s but you only have %.2f %s available!\nThe stock on hand is %.2f %s.') % \ (self.product_uom_qty, self.product_uom.name, self.product_id.virtual_available, self.product_id.uom_id.name, self.product_id.qty_available, self.product_id.uom_id.name) } return {'warning': warning_mess} return {}
def process_sheet(self, cr, uid, ids, context=None): move_pool = self.pool.get('account.move') hr_payslip_line_pool = self.pool['hr.payslip.line'] precision = self.pool.get('decimal.precision').precision_get( cr, uid, 'Payroll') timenow = time.strftime('%Y-%m-%d') for slip in self.browse(cr, uid, ids, context=context): line_ids = [] debit_sum = 0.0 credit_sum = 0.0 date = timenow name = _('Payslip of %s') % (slip.employee_id.name) move = { 'narration': name, 'ref': slip.number, 'journal_id': slip.journal_id.id, 'date': date, } for line in slip.details_by_salary_rule_category: amt = slip.credit_note and -line.total or line.total if float_is_zero(amt, 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': hr_payslip_line_pool._get_partner_id( cr, uid, line, credit_account=False, context=context), 'account_id': debit_account_id, 'journal_id': slip.journal_id.id, 'date': date, 'debit': amt > 0.0 and amt or 0.0, 'credit': amt < 0.0 and -amt or 0.0, 'analytic_account_id': line.salary_rule_id.analytic_account_id and line.salary_rule_id.analytic_account_id.id or False, 'tax_line_id': line.salary_rule_id.account_tax_id and line.salary_rule_id.account_tax_id.id or False, }) 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': hr_payslip_line_pool._get_partner_id( cr, uid, line, credit_account=True, context=context), 'account_id': credit_account_id, 'journal_id': slip.journal_id.id, 'date': date, 'debit': amt < 0.0 and -amt or 0.0, 'credit': amt > 0.0 and amt or 0.0, 'analytic_account_id': line.salary_rule_id.analytic_account_id and line.salary_rule_id.analytic_account_id.id or False, 'tax_line_id': line.salary_rule_id.account_tax_id and line.salary_rule_id.account_tax_id.id or False, }) 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'), 'date': timenow, '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.update({'line_ids': line_ids}) move_id = move_pool.create(cr, uid, move, context=context) self.write(cr, uid, [slip.id], { 'move_id': move_id, 'date': date }, context=context) move_pool.post(cr, uid, [move_id], context=context) return super(hr_payslip, self).process_sheet(cr, uid, [slip.id], context=context)
def _procure_orderpoint_confirm(self, cr, uid, use_new_cursor=False, company_id=False, context=None): ''' Create procurement based on Orderpoint :param bool use_new_cursor: if set, use a dedicated cursor and auto-commit after processing each procurement. This is appropriate for batch jobs only. ''' if context is None: context = {} if use_new_cursor: cr = yuancloud.registry(cr.dbname).cursor() orderpoint_obj = self.pool.get('stock.warehouse.orderpoint') procurement_obj = self.pool.get('procurement.order') product_obj = self.pool.get('product.product') dom = company_id and [('company_id', '=', company_id)] or [] orderpoint_ids = orderpoint_obj.search( cr, uid, dom, order="location_id, purchase_calendar_id, calendar_id") prev_ids = [] tot_procs = [] while orderpoint_ids: ids = orderpoint_ids[:1000] del orderpoint_ids[:1000] dates_dict = {} product_dict = {} ops_dict = {} ops = orderpoint_obj.browse(cr, uid, ids, context=context) #Calculate groups that can be executed together for op in ops: key = (op.location_id.id, op.purchase_calendar_id.id, op.calendar_id.id) res_groups = [] if not dates_dict.get(key): date_groups = self._get_group(cr, uid, op, context=context) for date, group in date_groups: if op.calendar_id and op.calendar_id.attendance_ids: date1, date2 = self._get_next_dates( cr, uid, op, date, group, context=context) res_groups += [ (group, date1, date2, date) ] #date1/date2 as deliveries and date as purchase confirmation date else: res_groups += [(group, date, False, date)] dates_dict[key] = res_groups product_dict[key] = [op.product_id] ops_dict[key] = [op] else: product_dict[key] += [op.product_id] ops_dict[key] += [op] for key in product_dict.keys(): for res_group in dates_dict[key]: ctx = context.copy() ctx.update({'location': ops_dict[key][0].location_id.id}) if res_group[2]: ctx.update({ 'to_date': res_group[2].strftime( DEFAULT_SERVER_DATETIME_FORMAT) }) prod_qty = product_obj._product_available( cr, uid, [x.id for x in product_dict[key]], context=ctx) group = res_group[0] date = res_group[1] subtract_qty = orderpoint_obj.subtract_procurements_from_orderpoints( cr, uid, [x.id for x in ops_dict[key]], context=context) first_op = True ndelivery = date and self._convert_to_UTC( cr, uid, date, context=context) or False npurchase = res_group[3] and self._convert_to_UTC( cr, uid, res_group[3], context=context) or False for op in ops_dict[key]: try: prods = prod_qty[ op.product_id.id]['virtual_available'] if prods is None: continue if float_compare(prods, op.product_min_qty, precision_rounding=op.product_uom. rounding) < 0: qty = max(op.product_min_qty, op.product_max_qty) - prods reste = op.qty_multiple > 0 and qty % op.qty_multiple or 0.0 if float_compare(reste, 0.0, precision_rounding=op. product_uom.rounding) > 0: qty += op.qty_multiple - reste if float_compare(qty, 0.0, precision_rounding=op. product_uom.rounding) <= 0: continue qty -= subtract_qty[op.id] qty_rounded = float_round( qty, precision_rounding=op.product_uom.rounding) if qty_rounded > 0: proc_id = procurement_obj.create( cr, uid, self._prepare_orderpoint_procurement( cr, uid, op, qty_rounded, date=ndelivery, purchase_date=npurchase, group=group, context=context), context=context) tot_procs.append(proc_id) orderpoint_obj.write( cr, uid, [op.id], { 'last_execution_date': datetime.utcnow().strftime( DEFAULT_SERVER_DATETIME_FORMAT) }, context=context) if use_new_cursor: cr.commit() except OperationalError: if use_new_cursor: orderpoint_ids.append(op.id) cr.rollback() continue else: raise try: tot_procs.reverse() self.run(cr, uid, tot_procs, context=context) tot_procs = [] if use_new_cursor: cr.commit() except OperationalError: if use_new_cursor: cr.rollback() continue else: raise if use_new_cursor: cr.commit() if prev_ids == ids: break else: prev_ids = ids if use_new_cursor: cr.commit() cr.close() return {}
def action_consume(self, cr, uid, ids, product_qty, location_id=False, restrict_lot_id=False, restrict_partner_id=False, consumed_for=False, context=None): """ Consumed product with specific quantity from specific source location. @param product_qty: Consumed/produced product quantity (= in quantity of UoM of product) @param location_id: Source location @param restrict_lot_id: optionnal parameter that allows to restrict the choice of quants on this specific lot @param restrict_partner_id: optionnal parameter that allows to restrict the choice of quants to this specific partner @param consumed_for: optionnal parameter given to this function to make the link between raw material consumed and produced product, for a better traceability @return: New lines created if not everything was consumed for this line """ if context is None: context = {} res = [] production_obj = self.pool.get('mrp.production') if product_qty <= 0: raise UserError(_('Please provide proper quantity.')) #because of the action_confirm that can create extra moves in case of phantom bom, we need to make 2 loops ids2 = [] for move in self.browse(cr, uid, ids, context=context): if move.state == 'draft': ids2.extend( self.action_confirm(cr, uid, [move.id], context=context)) else: ids2.append(move.id) prod_orders = set() for move in self.browse(cr, uid, ids2, context=context): prod_orders.add(move.raw_material_production_id.id or move.production_id.id) move_qty = move.product_qty if move_qty <= 0: raise UserError( _('Cannot consume a move with negative or zero quantity.')) quantity_rest = move_qty - product_qty # Compare with numbers of move uom as we want to avoid a split with 0 qty quantity_rest_uom = move.product_uom_qty - self.pool.get( "product.uom")._compute_qty_obj( cr, uid, move.product_id.uom_id, product_qty, move.product_uom) if float_compare( quantity_rest_uom, 0, precision_rounding=move.product_uom.rounding) != 0: new_mov = self.split(cr, uid, move, quantity_rest, context=context) if move.production_id: self.write(cr, uid, [new_mov], {'production_id': move.production_id.id}, context=context) res.append(new_mov) vals = { 'restrict_lot_id': restrict_lot_id, 'restrict_partner_id': restrict_partner_id, 'consumed_for': consumed_for } if location_id: vals.update({'location_id': location_id}) self.write(cr, uid, [move.id], vals, context=context) # Original moves will be the quantities consumed, so they need to be done self.action_done(cr, uid, ids2, context=context) if res: self.action_assign(cr, uid, res, context=context) if prod_orders: production_obj.signal_workflow(cr, uid, list(prod_orders), 'button_produce') return res
def _procure_orderpoint_confirm(self, cr, uid, use_new_cursor=False, company_id=False, context=None): ''' Create procurement based on Orderpoint :param bool use_new_cursor: if set, use a dedicated cursor and auto-commit after processing each procurement. This is appropriate for batch jobs only. ''' if context is None: context = {} if use_new_cursor: cr = yuancloud.registry(cr.dbname).cursor() orderpoint_obj = self.pool.get('stock.warehouse.orderpoint') procurement_obj = self.pool.get('procurement.order') product_obj = self.pool.get('product.product') dom = company_id and [('company_id', '=', company_id)] or [] orderpoint_ids = orderpoint_obj.search(cr, uid, dom, order="location_id") prev_ids = [] tot_procs = [] while orderpoint_ids: ids = orderpoint_ids[:1000] del orderpoint_ids[:1000] product_dict = {} ops_dict = {} ops = orderpoint_obj.browse(cr, uid, ids, context=context) #Calculate groups that can be executed together for op in ops: key = (op.location_id.id,) if not product_dict.get(key): product_dict[key] = [op.product_id] ops_dict[key] = [op] else: product_dict[key] += [op.product_id] ops_dict[key] += [op] for key in product_dict.keys(): ctx = context.copy() ctx.update({'location': ops_dict[key][0].location_id.id}) prod_qty = product_obj._product_available(cr, uid, [x.id for x in product_dict[key]], context=ctx) subtract_qty = orderpoint_obj.subtract_procurements_from_orderpoints(cr, uid, [x.id for x in ops_dict[key]], context=context) for op in ops_dict[key]: try: prods = prod_qty[op.product_id.id]['virtual_available'] if prods is None: continue if float_compare(prods, op.product_min_qty, precision_rounding=op.product_uom.rounding) <= 0: qty = max(op.product_min_qty, op.product_max_qty) - prods reste = op.qty_multiple > 0 and qty % op.qty_multiple or 0.0 if float_compare(reste, 0.0, precision_rounding=op.product_uom.rounding) > 0: qty += op.qty_multiple - reste if float_compare(qty, 0.0, precision_rounding=op.product_uom.rounding) < 0: continue qty -= subtract_qty[op.id] qty_rounded = float_round(qty, precision_rounding=op.product_uom.rounding) if qty_rounded > 0: proc_id = procurement_obj.create(cr, uid, self._prepare_orderpoint_procurement(cr, uid, op, qty_rounded, context=context), context=context) tot_procs.append(proc_id) if use_new_cursor: cr.commit() except OperationalError: if use_new_cursor: orderpoint_ids.append(op.id) cr.rollback() continue else: raise try: tot_procs.reverse() self.run(cr, uid, tot_procs, context=context) tot_procs = [] if use_new_cursor: cr.commit() except OperationalError: if use_new_cursor: cr.rollback() continue else: raise if use_new_cursor: cr.commit() if prev_ids == ids: break else: prev_ids = ids if use_new_cursor: cr.commit() cr.close() return {}