def get_shipment_cost_line(self, cost, unit_price=None): pool = Pool() SaleLine = pool.get('sale.line') product = self.carrier.carrier_product sequence = None if self.lines: last_line = self.lines[-1] if last_line.sequence is not None: sequence = last_line.sequence + 1 shipment_cost = round_price(cost) cost_line = SaleLine( sale=self, sequence=sequence, type='line', product=product, quantity=1, # XXX unit=product.sale_uom, shipment_cost=shipment_cost, ) cost_line.on_change_product() if unit_price is not None: cost_line.unit_price = round_price(unit_price) else: cost_line.unit_price = round_price(cost) if not cost_line.unit_price: cost_line.quantity = 0 cost_line.amount = cost_line.on_change_with_amount() return cost_line
def allocate_cost_by_value(self): pool = Pool() Currency = pool.get('currency.currency') Move = pool.get('stock.move') if not self.cost_used: return cost = Currency.compute( self.cost_currency_used, self.cost_used, self.company.currency, round=False) moves = [m for m in self.incoming_moves if m.state not in ('done', 'cancelled')] sum_value = 0 unit_prices = {} for move in moves: unit_price = Currency.compute(move.currency, move.unit_price, self.company.currency, round=False) unit_prices[move.id] = unit_price sum_value += unit_price * Decimal(str(move.quantity)) costs = [] digit = Move.unit_price.digits[1] exp = Decimal(str(10.0 ** -digit)) difference = cost for move in moves: quantity = Decimal(str(move.quantity)) if not sum_value: move_cost = cost / Decimal(len(moves)) else: move_cost = cost * quantity * unit_prices[move.id] / sum_value unit_shipment_cost = round_price( move_cost / quantity, rounding=ROUND_DOWN) costs.append({ 'unit_shipment_cost': unit_shipment_cost, 'difference': move_cost - (unit_shipment_cost * quantity), 'move': move, }) difference -= unit_shipment_cost * quantity costs.sort(key=itemgetter('difference')) for cost in costs: move = cost['move'] quantity = Decimal(str(move.quantity)) if exp * quantity < difference: cost['unit_shipment_cost'] += exp difference -= exp * quantity if difference < exp: break for cost in costs: move = cost['move'] unit_shipment_cost = Currency.compute( self.company.currency, cost['unit_shipment_cost'], move.currency, round=False) unit_shipment_cost = round_price( unit_shipment_cost, rounding=ROUND_HALF_EVEN) move.unit_price += unit_shipment_cost move.unit_shipment_cost = unit_shipment_cost Move.save(moves)
def _allocate_cost(self, factors, sign=1): "Allocate cost on moves using factors" pool = Pool() Move = pool.get('stock.move') Currency = pool.get('currency.currency') assert sign in {1, -1} cost = self.cost currency = self.company.currency moves = [m for m in self.stock_moves() if m.quantity] costs = [] digit = Move.unit_price.digits[1] exp = Decimal(str(10.0**-digit)) difference = cost for move in moves: quantity = Decimal(str(move.quantity)) move_cost = cost * factors[str(move.id)] unit_landed_cost = round_price(move_cost / quantity, rounding=ROUND_DOWN) costs.append({ 'unit_landed_cost': unit_landed_cost, 'difference': move_cost - (unit_landed_cost * quantity), 'move': move, }) difference -= unit_landed_cost * quantity costs.sort(key=itemgetter('difference'), reverse=True) for cost in costs: move = cost['move'] quantity = Decimal(str(move.quantity)) if exp * quantity <= difference: cost['unit_landed_cost'] += exp difference -= exp * quantity if difference < exp: break for cost in costs: move = cost['move'] with Transaction().set_context(date=move.effective_date): unit_landed_cost = Currency.compute(currency, cost['unit_landed_cost'], move.currency, round=False) unit_landed_cost = round_price(unit_landed_cost, rounding=ROUND_HALF_EVEN) if move.unit_landed_cost is None: move.unit_landed_cost = 0 move.unit_price += unit_landed_cost * sign move.unit_landed_cost += unit_landed_cost * sign Move.save(moves)
def on_change_discount_rate(self): if self.base_price is not None and self.discount_rate is not None: self.unit_price = round_price(self.base_price * (1 - self.discount_rate)) self.discount_amount = self.on_change_with_discount_amount() self.discount = self.on_change_with_discount() self.amount = self.on_change_with_amount()
def get_cost_invoice_line(self, invoice): pool = Pool() Currency = pool.get('currency.currency') InvoiceLine = pool.get('account.invoice.line') if not self.cost_sale_used: return product = self.carrier.carrier_product invoice_line = InvoiceLine(invoice=invoice) invoice_line.on_change_invoice() invoice_line.type = 'line' invoice_line.quantity = 1 # XXX invoice_line.unit = product.sale_uom.id cost = self.cost_sale_used if invoice.currency != self.cost_sale_currency_used: with Transaction().set_context(date=invoice.currency_date): cost = Currency.compute( self.cost_sale_currency_used, cost, invoice.currency, round=False) invoice_line.unit_price = round_price(cost) invoice_line.product = product invoice_line.on_change_product() invoice_line.cost_shipments = [self] if not invoice_line.account: raise InvoiceShipmentCostError( gettext('sale_shipment_cost' '.msg_shipment_cost_invoice_missing_account_revenue', shipment=self.rec_name, product=product.rec_name)) return invoice_line
def recompute_cost_price(cls, products, start=None): pool = Pool() Move = pool.get('stock.move') costs = defaultdict(list) for product in products: if product.type == 'service': continue cost = getattr( product, 'recompute_cost_price_%s' % product.cost_price_method)(start) cost = round_price(cost) costs[cost].append(product) updated = [] for sub_products in grouped_slice(products): domain = [ ('unit_price_updated', '=', True), cls._domain_moves_cost(), ('product', 'in', [p.id for p in sub_products]), ] if start: domain.append(('effective_date', '>=', start)) updated += Move.search(domain, order=[]) if updated: Move.write(updated, {'unit_price_updated': False}) if costs: cls.update_cost_price(costs)
def get_cost(self, name): pool = Pool() Work = pool.get('production.work') Cycle = pool.get('production.work.cycle') table = self.__table__() work = Work.__table__() cycle = Cycle.__table__() cursor = Transaction().connection.cursor() cost = super(Production, self).get_cost(name) cursor.execute( *table.join(work, 'LEFT', condition=work.production == table.id ).join(cycle, 'LEFT', condition=cycle.work == work.id).select(Sum(cycle.cost), where=(cycle.state == 'done') & (table.id == self.id))) cycle_cost, = cursor.fetchone() if cycle_cost is not None: # SQLite uses float for SUM if not isinstance(cycle_cost, Decimal): cycle_cost = Decimal(cycle_cost) cost += cycle_cost return round_price(cost)
def get_cost_price(self, product_cost_price=None): pool = Pool() SaleLine = pool.get('sale.line') Sale = pool.get('sale.sale') # For return sale's move use the cost price of the original sale if (isinstance(self.origin, SaleLine) and self.origin.quantity < 0 and self.from_location.type != 'storage' and self.to_location.type == 'storage' and isinstance(self.origin.sale.origin, Sale)): sale = self.origin.sale.origin cost = Decimal(0) qty = Decimal(0) for move in sale.moves: if (move.state == 'done' and move.from_location.type == 'storage' and move.to_location.type == 'customer' and move.product == self.product): move_quantity = Decimal(str(move.internal_quantity)) cost_price = move.get_cost_price( product_cost_price=move.cost_price) qty += move_quantity cost += cost_price * move_quantity if qty: product_cost_price = round_price(cost / qty) return super().get_cost_price(product_cost_price=product_cost_price)
def set_cost(cls, shipments): pool = Pool() Move = pool.get('stock.move') UoM = pool.get('product.uom') to_save = [] for shipment in shipments: product_cost = defaultdict(int) s_product_qty = defaultdict(int) for s_move in shipment.supplier_moves: if s_move.state == 'cancelled': continue internal_quantity = Decimal(str(s_move.internal_quantity)) product_cost[s_move.product] += (s_move.get_cost_price() * internal_quantity) quantity = UoM.compute_qty(s_move.uom, s_move.quantity, s_move.product.default_uom, round=False) s_product_qty[s_move.product] += quantity for product, cost in product_cost.items(): qty = Decimal(str(s_product_qty[product])) if qty: product_cost[product] = round_price(cost / qty) for c_move in shipment.customer_moves: cost_price = product_cost[c_move.product] if cost_price != c_move.cost_price: c_move.cost_price = cost_price to_save.append(c_move) if to_save: Move.save(to_save)
def on_change_product(self): pool = Pool() User = pool.get('res.user') ModelData = pool.get('ir.model.data') Uom = pool.get('product.uom') Currency = pool.get('currency.currency') if not self.product: return if self.price_list_hour: hour_uom = Uom(ModelData.get_id('product', 'uom_hour')) self.list_price = Uom.compute_price(self.product.default_uom, self.product.list_price, hour_uom) else: self.list_price = self.product.list_price if self.company: user = User(Transaction().user) if user.company != self.company: if user.company.currency != self.company.currency: self.list_price = Currency.compute(user.company.currency, self.list_price, self.company.currency, round=False) self.list_price = round_price(self.list_price)
def on_change_service(self): pool = Pool() Product = pool.get('product.product') if not self.service: self.consumption_recurrence = None self.consumption_delay = None return party = None party_context = {} if self.subscription and self.subscription.party: party = self.subscription.party if party.lang: party_context['language'] = party.lang.code product = self.service.product category = product.sale_uom.category if not self.unit or self.unit.category != category: self.unit = product.sale_uom self.unit_digits = product.sale_uom.digits with Transaction().set_context(self._get_context_sale_price()): self.unit_price = Product.get_sale_price([product], self.quantity or 0)[product.id] if self.unit_price: self.unit_price = round_price(self.unit_price) self.consumption_recurrence = self.service.consumption_recurrence self.consumption_delay = self.service.consumption_delay
def test_round_price(self): for value, result in [ (Decimal('1'), Decimal('1.0000')), (Decimal('1.12345'), Decimal('1.1234')), (1, Decimal('1')), ]: with self.subTest(value=value): self.assertEqual(round_price(value), result)
def get_move(self, move_type): pool = Pool() Currency = pool.get('currency.currency') move = super().get_move(move_type) if move and self.purchase.customer: cost_price = Currency.compute(move.currency, move.unit_price, move.company.currency) move.cost_price = round_price(cost_price) return move
def get_cost(self, name): cost = Decimal(0) for input_ in self.inputs: if input_.cost_price is not None: cost_price = input_.cost_price else: cost_price = input_.product.cost_price cost += (Decimal(str(input_.internal_quantity)) * cost_price) return round_price(cost)
def compute_base_price(self): pool = Pool() Uom = pool.get('product.uom') if self.product and self.product.list_price is not None: price = self.product.list_price if self.unit: price = Uom.compute_price(self.product.default_uom, price, self.unit) return round_price(price)
def get_cost(self, name): pool = Pool() Currency = pool.get('currency.currency') cost = super(Production, self).get_cost(name) for line in self.purchase_lines: if line.purchase.state != 'cancelled': cost += Currency.compute( line.purchase.currency, line.amount, self.company.currency, round=False) return round_price(cost)
def compute_fifo_cost_price(quantity, date): fifo_moves = self.get_fifo_move(float(quantity), date=date) cost_price = Decimal(0) consumed_qty = 0 for move, move_qty in fifo_moves: consumed_qty += move_qty cost_price += move.get_cost_price() * Decimal(str(move_qty)) if consumed_qty: return round_price(cost_price / Decimal(str(consumed_qty)))
def _compute_costs(self): costs = { 'cost': None, 'cost_currency': None, } if self.carrier: with Transaction().set_context(self._get_carrier_context()): cost, currency_id = self.carrier.get_purchase_price() if cost is not None: costs['cost'] = round_price(cost) costs['cost_currency'] = currency_id return costs
def _get_supplier_invoice_line_consignment(self): pool = Pool() InvoiceLine = pool.get('account.invoice.line') Product = pool.get('product.product') ProductSupplier = pool.get('purchase.product_supplier') with Transaction().set_context( supplier=self.from_location.consignment_party.id): pattern = ProductSupplier.get_pattern() for product_supplier in self.product.product_suppliers_used(**pattern): currency = product_supplier.currency break else: currency = self.company.currency line = InvoiceLine() line.invoice_type = 'in' line.type = 'line' line.company = self.company line.party = self.from_location.consignment_party line.currency = currency line.product = self.product line.description = self.product.name line.quantity = self.quantity line.unit = self.uom line.account = self.product.account_expense_used line.stock_moves = [self] line.origin = self taxes = [] pattern = self._get_tax_rule_pattern() for tax in line.product.supplier_taxes_used: if line.party.supplier_tax_rule: tax_ids = line.party.supplier_tax_rule.apply(tax, pattern) if tax_ids: taxes.extend(tax_ids) continue taxes.append(tax.id) if line.party.supplier_tax_rule: tax_ids = line.party.supplier_tax_rule.apply(None, pattern) if tax_ids: taxes.extend(tax_ids) line.taxes = taxes with Transaction().set_context(currency=line.currency.id, supplier=line.party.id, uom=line.unit, taxes=[t.id for t in line.taxes]): line.unit_price = Product.get_purchase_price( [line.product], line.quantity)[line.product.id] if line.unit_price is not None: line.unit_price = round_price(line.unit_price) return line
def on_change_secondary_unit_price(self, name=None): pool = Pool() Uom = pool.get('product.uom') if (self.secondary_unit_price is not None and self.secondary_unit and self.unit and (self.secondary_uom_factor or self.secondary_uom_rate)): self.unit_price = Uom.compute_price(self.secondary_unit, self.secondary_unit_price, self.unit, factor=self.secondary_uom_rate, rate=self.secondary_uom_factor) self.unit_price = round_price(self.unit_price) self.amount = self.on_change_with_amount()
def _compute_costs(self): costs = super()._compute_costs() costs.update({ 'cost_sale': None, 'cost_sale_currency': None, }) if self.carrier and self.cost_method == 'shipment': with Transaction().set_context(self._get_carrier_context()): cost_sale, sale_currency_id = self.carrier.get_sale_price() if cost_sale is not None: costs['cost_sale'] = round_price(cost_sale) costs['cost_sale_currency'] = sale_currency_id return costs
def on_change_with_secondary_unit_price(self, name=None): pool = Pool() Uom = pool.get('product.uom') if (self.unit_price is not None and self.unit and self.secondary_unit and (self.secondary_uom_factor or self.secondary_uom_rate)): unit_price = Uom.compute_price(self.unit, self.unit_price, self.secondary_unit, factor=self.secondary_uom_factor, rate=self.secondary_uom_rate) return round_price(unit_price) else: return None
def _allocate_shipment_cost(self, cost, factors): moves = [] for move in self.shipment_cost_moves: ratio = factors[move.id] quantity = Decimal(str(move.internal_quantity)) if quantity and ratio: cost_price = round_price(cost * ratio / quantity) else: cost_price = Decimal(0) if move.shipment_out_cost_price != cost_price: move.shipment_out_cost_price = cost_price moves.append(move) return moves
def get_multivalue(self, name, **pattern): pool = Pool() Uom = pool.get('product.uom') value = super().get_multivalue(name, **pattern) if name == 'cost_price' and self.type == 'kit': value = Decimal(0) for component in self.components_used: cost_price = component.product.get_multivalue( 'cost_price', **pattern) cost_price = Uom.compute_price( component.product.default_uom, cost_price, component.unit) value += cost_price * Decimal(str(component.quantity)) value = round_price(value) return value
def _update_fifo_out_product_cost_price(self): ''' Update the product cost price of the given product on the move. Update fifo_quantity on the concerned incomming moves. Return the cost price for outputing the given product and quantity. ''' pool = Pool() Uom = pool.get('product.uom') total_qty = Uom.compute_qty(self.uom, self.quantity, self.product.default_uom, round=False) with Transaction().set_context(company=self.company.id): fifo_moves = self.product.get_fifo_move(total_qty) cost_price = Decimal("0.0") consumed_qty = 0.0 to_save = [] for move, move_qty in fifo_moves: consumed_qty += move_qty cost_price += move.get_cost_price() * Decimal(str(move_qty)) move_qty = Uom.compute_qty(self.product.default_uom, move_qty, move.uom, round=False) move.fifo_quantity = (move.fifo_quantity or 0.0) + move_qty # Due to float, the fifo quantity result can exceed the quantity. assert move.quantity >= move.fifo_quantity - move.uom.rounding move.fifo_quantity = min(move.fifo_quantity, move.quantity) to_save.append(move) if consumed_qty: cost_price = cost_price / Decimal(str(consumed_qty)) else: cost_price = self.product.get_multivalue( 'cost_price', **self._cost_price_pattern) # Compute average cost price average_cost_price = self._compute_product_cost_price( 'out', product_cost_price=cost_price) if cost_price: cost_price = round_price(cost_price) else: cost_price = average_cost_price return cost_price, average_cost_price, to_save
def _compute_costs(self): pool = Pool() Currency = pool.get('currency.currency') costs = { 'cost': None, } if self.carrier: with Transaction().set_context(self._get_carrier_context()): cost, currency_id = self.carrier.get_purchase_price() if cost is not None: cost = Currency.compute(Currency(currency_id), cost, self.company.currency, round=False) costs['cost'] = round_price(cost) return costs
def _compute_component_unit_price(self, unit_price): pool = Pool() Currency = pool.get('currency.currency') UoM = pool.get('product.uom') amount, quantity = 0, 0 for line in self.origin.line.invoice_lines: if line.invoice and line.invoice.state in {'posted', 'paid'}: with Transaction().set_context(date=self.effective_date): amount += Currency.compute(line.invoice.currency, line.amount, self.currency) quantity += UoM.compute_qty(line.unit, line.quantity, self.origin.line.unit) amount *= self.origin.price_ratio if quantity: unit_price = round_price(amount / Decimal(str(quantity))) unit_price = UoM.compute_price(self.origin.unit, unit_price, self.uom) return unit_price
def compute_purchase_line(cls, key, requests, purchase): pool = Pool() RequisitionLine = pool.get('purchase.requisition.line') Uom = pool.get('product.uom') line = super().compute_purchase_line(key, requests, purchase) key_values = dict(key) if (key_values.get('unit_price') is not None and any( isinstance(r.origin, RequisitionLine) for r in requests)): line.unit_price = round_price( Uom.compute_price( key_values.get('unit', line.unit), key_values['unit_price'], line.unit)) return line
def apply(self, sale): applied = False for line in sale.lines: if line.type != 'line': continue if not self.is_valid_sale_line(line): continue context = self.get_context_formula(line) new_price = self.get_unit_price(**context) if new_price is not None: if new_price < 0: new_price = Decimal(0) if line.unit_price >= new_price: line.unit_price = round_price(new_price) line.promotion = self applied = True if applied: sale.lines = sale.lines # Trigger the change
def get_cost_value(cls, products, name): cost_values = {p.id: None for p in products} context = {} trans_context = Transaction().context if trans_context.get('stock_date_end'): # Use the last cost_price of the day context['_datetime'] = datetime.datetime.combine( trans_context['stock_date_end'], datetime.time.max) # The date could be before the product creation products = [ p for p in products if p.create_date <= context['_datetime'] ] with Transaction().set_context(context): for product in cls.browse(products): # The product may not have a cost price if product.cost_price is not None: cost_values[product.id] = round_price( Decimal(str(product.quantity)) * product.cost_price) return cost_values