def build_row(self, buyer, price, grades, sale_type, adjustment, volume): if adjustment is None: adjustment = CV(self.report.season.default_adjustment, self.report.season.exchange_rate) else: adjustment = CV(adjustment, self.report.season.exchange_rate) if sale_type == 'FOT': fot_price = price if fot_price.as_local(self.report.season.exchange_rate) > Decimal(0): fob_price = fot_price + adjustment else: fob_price = CV_ZERO freight = adjustment * volume revenue = fob_price * volume elif sale_type == 'FOB': fob_price = price fot_price = fob_price - adjustment if fot_price.as_local(self.report.season.exchange_rate) < Decimal(0): fot_price = CV_ZERO freight = CV_ZERO revenue = fob_price * volume elif sale_type == 'LOC': fob_price = CV_ZERO fot_price = CV_ZERO price = price adjustment = CV_ZERO freight = CV_ZERO revenue = price * volume else: # pragma: no cover raise Exception("Invalid sale type: '%s' for sale to '%s" % (sale_type, buyer)) return SalesRow(buyer, grades, sale_type, volume, price, fot_price, fob_price, freight, revenue)
def test_cloning(self): val = CV(Decimal("1000")) val2 = CV(val) self.assertEquals(val.as_local(Decimal("500")), val2.as_local(Decimal("500"))) try: val2 = CV(val, Decimal("500")) self.assertFalse(True, "Should have thrown, invalid constructor") except: pass
def build_expense_row(self, entries, index): row = entries[index] children = [] for i in range(index+1, len(entries)): entry = entries[i] if entry['expense'].depth <= 1: break children.append(self.build_row(entry['expense'], CV(entry['value'], entry['exchange_rate']))) return self.build_row(row['expense'], CV(row['value'], row['exchange_rate']), children)
def test_conversion(self): val = CV(Decimal("1000")) self.assertEquals(Decimal("1000"), val.as_local(Decimal("500"))) self.assertEquals(Decimal("2"), val.as_usd(Decimal("500"))) self.assertEquals("1000 - []", str(val)) val = CV(Decimal("2"), Decimal("500")) self.assertEquals(Decimal("1000"), val.as_local(Decimal("500"))) self.assertEquals(Decimal("1200"), val.as_local(Decimal("600"))) self.assertEquals(Decimal("2"), val.as_usd(Decimal("500"))) self.assertEquals("None - [2 (500)]", str(val))
def test_division(self): val = CV(None) total = val / None self.assertIsNone(total) self.assertIsNone(total) total = None / val self.assertIsNone(total) self.assertIsNone(total) val = CV(Decimal("1000")) total = None / val self.assertIsNone(total) self.assertIsNone(total) total = val / None self.assertIsNone(total) self.assertIsNone(total) total = val / Decimal("2") self.assertEquals(Decimal("500"), total.as_local(Decimal("500"))) self.assertEquals(Decimal("1"), total.as_usd(Decimal("500"))) total = val total /= Decimal("2") self.assertEquals(Decimal("500"), total.as_local(Decimal("500"))) self.assertEquals(Decimal("1"), total.as_usd(Decimal("500"))) try: total = Decimal("2") / val self.assertTrue( False, "Should have raised exception as numerator must always be CurrencyValue" ) except Exception as e: pass val = CV(Decimal("4"), Decimal("500")) + CV(Decimal("1000")) total = val / Decimal("-2") self.assertEquals(Decimal("-1500"), total.as_local(Decimal("500"))) self.assertEquals(Decimal("-3"), total.as_usd(Decimal("500")))
def populate_calculated(self): # first calculate all our totals self.working_cap_utilized = CV_ZERO categories = self.expenses.get_categories() if categories: self.working_cap_utilized += categories[0].value self.working_cap_received = CV_ZERO if self.report.working_capital: self.working_cap_received = CV(self.report.working_capital) self.working_cap_repaid = CV_ZERO if self.report.working_capital_repaid: self.working_cap_repaid = CV(self.report.working_capital_repaid) cash_due = self.expenses.total_revenue # subtract all our category totals for category in categories[1:]: cash_due -= category.value # and our forex loss cash_due -= self.expenses.total_forex_loss # and our working capital received cash_due -= self.working_cap_received cash_due += self.working_cap_repaid self.cash_due = cash_due self.unused_working_capital = self.working_cap_received - self.working_cap_utilized - self.working_cap_repaid # calculate retained profit self.retained_profit = self.expenses.total_profit for use in self.uses: if use.total: self.retained_profit -= use.total if self.retained_profit.as_local(self.exchange) < 0: self.retained_profit = CV(0) # then set them on our sources of cash if appropriate self.set_calculated_source_value('WCAP', self.unused_working_capital) self.set_calculated_source_value('CDUE', self.cash_due) self.set_calculated_use_value('PROF', self.retained_profit)
def test_nones(self): val = CV(None) self.assertIsNone(val.as_local(Decimal("585"))) self.assertIsNone(val.as_usd(Decimal("585"))) val = CV(None, Decimal("585")) self.assertIsNone(val.as_local(Decimal("585"))) self.assertIsNone(val.as_usd(Decimal("585"))) try: val = CV(Decimal("5")) val.as_usd(None) self.assertFalse( True, "Should have raised exception due to missing exchange rate") except: pass
def build_sources(self): self.sources = [] for source in self.report.cash_sources_for_season(): total = None if source.entry: total = CV(source.entry.value) self.sources.append(SourceRow(source, total))
def build_uses(self): self.uses = [] for cashuse in self.report.cash_uses_for_season(): total = None if cashuse.entry: total = CV(cashuse.entry.value) self.uses.append(UseRow(cashuse, total))
def __init__(self, report, production_box, sales_box, currency): self.report = report self.production_box = production_box self.sales_box = sales_box self.currency = currency self.exchange = self.report.season.exchange_rate self.sales_revenue = sales_box.total_revenue self.total_revenue = sales_box.total_revenue self.misc_revenue = CV(None) # if we have misc revenue (TZ in some cases) then our total must take that into account if self.report.season.has_misc_revenue: self.misc_revenue = CV(self.report.miscellaneous_revenue) self.total_revenue += self.misc_revenue self.categories = self.build_category_rows() self.calculate_totals()
def calculate_totals(self): self.total = CV(Decimal("0")) self.total_advance = CV(Decimal("0")) self.total_expense = CV(Decimal("0")) # calculate our overall advance for category in self.categories: self.total += category.value self.total_advance += category.advance_value # total forex loss self.total_forex_loss = self.total_revenue.forex_loss(self.exchange) - self.total.forex_loss(self.exchange) # total profit self.total_profit = self.total_revenue - self.total - self.total_forex_loss # total expenses per kilo green total_expense = self.total - self.total_advance if self.production_box.green_total: self.production_cost = total_expense / self.production_box.green_total else: self.production_cost = CV(Decimal("0"))
def build_total_row(self, rows): total_volume = Decimal("0") total_revenue = CV(Decimal("0")) total_freight = CV(Decimal("0")) total_fob = CV(Decimal("0")) total_fot = CV(Decimal("0")) for row in rows: total_volume += row.volume total_revenue += row.revenue total_freight += row.freight total_fob += (row.volume * row.fob_price) total_fot += (row.volume * row.fot_price) fot_price = total_fot / total_volume fob_price = total_fob / total_volume price = total_revenue / total_volume return SalesRow("Total Sales", [], "", total_volume, price, fot_price, fob_price, total_freight, total_revenue, is_total=True)
def test_multiplication(self): val = CV(None) total = val * None self.assertIsNone(total) self.assertIsNone(total) total = None * val self.assertIsNone(total) self.assertIsNone(total) val = CV(Decimal("1000")) total = None * val self.assertIsNone(total) self.assertIsNone(total) total = val * None self.assertIsNone(total) self.assertIsNone(total) total = val * Decimal("2") self.assertEquals(Decimal("2000"), total.as_local(Decimal("500"))) self.assertEquals(Decimal("4"), total.as_usd(Decimal("500"))) total = Decimal("2") * val self.assertEquals(Decimal("2000"), total.as_local(Decimal("500"))) self.assertEquals(Decimal("4"), total.as_usd(Decimal("500"))) total = val total *= Decimal("2") self.assertEquals(Decimal("2000"), total.as_local(Decimal("500"))) self.assertEquals(Decimal("4"), total.as_usd(Decimal("500"))) val = CV(Decimal("4"), Decimal("500")) + CV(Decimal("1000")) total = Decimal("-2") * val self.assertEquals(Decimal("-6000"), total.as_local(Decimal("500"))) self.assertEquals(Decimal("-12"), total.as_usd(Decimal("500"))) # test invalid combinations val1 = CV(Decimal("10")) val2 = CV(Decimal("20")) try: total = val1 * val2 self.assertFalse(True, "Should have thrown, invalid operation") except: pass
def build_category_row(self, entries, index): category = entries[index] children = [] for i in range(index+1, len(entries)): entry = entries[i] # new category? all done if entry['expense'].depth == 0: break # depth 1? roll it up if entry['expense'].depth == 1: row = self.build_expense_row(entries, i) children.append(row) return self.build_row(category['expense'], CV(category['value'], category['exchange_rate']), children)
def test_addition(self): val = CV(None) val2 = CV(None) total = val + val2 self.assertIsNone(total.as_local(Decimal("585"))) self.assertIsNone(total.as_usd(Decimal("585"))) val = CV(Decimal("1000")) total = val + val2 self.assertEquals(Decimal("1000"), total.as_local(Decimal("500"))) self.assertEquals(Decimal("2"), total.as_usd(Decimal("500"))) total = val2 + val self.assertEquals(Decimal("1000"), total.as_local(Decimal("500"))) self.assertEquals(Decimal("2"), total.as_usd(Decimal("500"))) total = None + val self.assertEquals(Decimal("1000"), total.as_local(Decimal("500"))) self.assertEquals(Decimal("2"), total.as_usd(Decimal("500"))) total = val + None self.assertEquals(Decimal("1000"), total.as_local(Decimal("500"))) self.assertEquals(Decimal("2"), total.as_usd(Decimal("500"))) val = CV(Decimal("1"), Decimal("500")) val2 = CV(Decimal("2"), Decimal("250")) total = val + val2 self.assertEquals(Decimal("1500"), total.as_local(Decimal("500"))) self.assertEquals(Decimal("3"), total.as_usd(Decimal("500"))) total += CV(Decimal("1000")) self.assertEquals(Decimal("2500"), total.as_local(Decimal("500"))) self.assertEquals(Decimal("5"), total.as_usd(Decimal("500"))) total = total.negate() self.assertEquals(Decimal("-2500"), total.as_local(Decimal("500"))) self.assertEquals(Decimal("-5"), total.as_usd(Decimal("500")))
def test_subtraction(self): val = CV(None) val2 = CV(None) total = val - val2 self.assertIsNone(total.as_local(Decimal("500"))) self.assertIsNone(total.as_usd(Decimal("500"))) val = CV(Decimal("1000")) total = val - val2 self.assertEquals(Decimal("1000"), total.as_local(Decimal("500"))) self.assertEquals(Decimal("2"), total.as_usd(Decimal("500"))) total = val2 - val self.assertEquals(Decimal("-1000"), total.as_local(Decimal("500"))) self.assertEquals(Decimal("-2"), total.as_usd(Decimal("500"))) total = None - val self.assertEquals(Decimal("-1000"), total.as_local(Decimal("500"))) self.assertEquals(Decimal("-2"), total.as_usd(Decimal("500"))) total = val - None self.assertEquals(Decimal("1000"), total.as_local(Decimal("500"))) self.assertEquals(Decimal("2"), total.as_usd(Decimal("500"))) val = CV(Decimal("1"), Decimal("500")) val2 = CV(Decimal("2"), Decimal("250")) total = val - val2 self.assertEquals(Decimal("-500"), total.as_local(Decimal("500"))) self.assertEquals(Decimal("-1"), total.as_usd(Decimal("500"))) total -= CV(Decimal("1000")) self.assertEquals(Decimal("-1500"), total.as_local(Decimal("500"))) self.assertEquals(Decimal("-3"), total.as_usd(Decimal("500")))
def build_rows(self, sales): rows = [] combination_sales = [] # for each sale for sale in sales: volume = Decimal(0) grades = [] # whether all the grades in this sale are depth 2.. if so, then this sale # will be combined with other sales that are all depth2 if by the same buyer can_be_combined = sale.components.count() > 0 depth1_parent = -1 for component in sale.components.all(): grade = component.grade if not grade.parent or not grade.parent.parent: can_be_combined = False else: grade_depth1_parent = self.get_depth1_parent(grade) if depth1_parent == -1: depth1_parent = grade_depth1_parent if depth1_parent != grade_depth1_parent: can_be_combined = False volume += component.volume if not component.grade in grades: grades.append(component.grade) row = self.build_row(sale.buyer, CV(sale.price, sale.exchange_rate), grades, sale.sale_type, sale.adjustment, volume) if can_be_combined: combination_sales.append(row) else: rows.append(row) # now we try to combine all the depth2 sales depth2_buyers = dict() for sale in combination_sales: sale_key = "%s_%s_%s" % (sale.buyer, sale.sale_type, sale.get_depth1_grade().id) if sale_key in depth2_buyers: depth2_buyers[sale_key].append(sale) else: depth2_buyers[sale_key] = [sale] for sales in depth2_buyers.values(): total_volume = Decimal(0) total_fot_revenue = CV_ZERO total_fob_revenue = CV_ZERO total_freight = CV_ZERO depth1_grade = None fot_price = CV_ZERO fob_price = CV_ZERO for sale in sales: total_volume += sale.volume total_fot_revenue += (sale.volume * sale.fot_price) total_fob_revenue += (sale.volume * sale.fob_price) total_freight += sale.freight if depth1_grade is None: for grade in sale.grades: depth1_grade = self.get_depth1_parent(grade) break if total_volume > Decimal("0"): fot_price = total_fot_revenue / total_volume fob_price = total_fob_revenue / total_volume # we don't call build_row here because build_row side-effects our total forex loss and # those have already been accounted for above combined_row = SalesRow(sales[0].buyer, [depth1_grade], sales[0].sale_type, total_volume, fob_price, fot_price, fob_price, total_freight, total_fob_revenue) rows.append(combined_row) return rows
def test_forex_loss(self): val = CV(Decimal("50"), Decimal("500")) self.assertEquals( Decimal("500"), val.forex_loss(Decimal("510")).as_local(Decimal("500")))
def build_rows(self): self.rows = [] # build our advance payment self.rows.append( FarmerRow(_("Advance Payment"), 'advance_payment', 'ALL', self.expenses.total_advance)) for payment in self.report.farmer_payments_for_season(): total = None entry = payment.entry row_for = None if payment.applies_to == 'ALL': row_for = 'ALL' if entry: total = CV(self.times_cherry(entry.all_per_kilo)) self.rows.append( FarmerRow(payment.name, 'fp__%d' % payment.id, row_for, total)) if payment.applies_to == 'BOT' or payment.applies_to == 'MEM': row_for = 'MEM' if entry: total = CV(self.times_cherry(entry.member_per_kilo)) self.rows.append( FarmerRow(payment.name, 'fp__%d' % payment.id, row_for, total)) if payment.applies_to == 'BOT' or payment.applies_to == 'NON': row_for = 'NON' if entry is None: total = None else: total = CV(self.times_cherry(entry.non_member_per_kilo)) self.rows.append( FarmerRow(payment.name, 'fp__%d' % payment.id, row_for, total)) # calculate our totals label = _("Total Farmer Payment") # no members for this season means just having a simple total if not self.report.season.has_members: self.rows.append( self.build_total_row(self.rows, label, 'total', 'ALL')) self.total_paid = self.rows[-1].value self.total_paid_members = None self.total_paid_non_members = None self.price = self.per_weight_unit_green(self.rows[-1].value) self.member_price = None self.non_member_price = None else: self.price = None self.rows.append( self.build_total_row([ row for row in self.rows if row.row_for in ('ALL', 'MEM') ], label, 'total', 'MEM')) self.member_price = self.per_weight_unit_green(self.rows[-1].value) self.total_paid_members = self.rows[-1].value self.total_paid = self.rows[-1].value self.rows.append( self.build_total_row([ row for row in self.rows[:-1] if row.row_for in ('ALL', 'NON') ], label, 'total', 'NON')) self.non_member_price = self.per_weight_unit_green( self.rows[-1].value) self.total_paid_non_members = self.rows[-1].value