diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index f209832ddfff20fceeafeb84164149b16673553d..188f2479cf73888226f21d928d38e74d6301c680 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -600,12 +600,19 @@ class SellingController(StockController): if self.doctype in ["Sales Order", "Quotation"]: for item in self.items: item.gross_profit = flt( - ((item.base_rate - item.valuation_rate) * item.stock_qty), self.precision("amount", item) + ((item.base_rate - (item.get("unit_cost_price") or item.valuation_rate)) * item.stock_qty), + self.precision("amount", item), ) def set_profit_margins(self): if self.doctype == "Quotation": + total_gross_profit = 0.0 + total_cost = 0.0 + total_selling_amount = 0.0 for item in self.items: + total_gross_profit += item.gross_profit or 0.0 + total_cost += (item.unit_cost_price or 0.0) * (item.stock_qty * 0.0) + total_selling_amount += item.base_amount or 0.0 if item.unit_cost_price: item.gross_profit_percentage = flt( item.gross_profit / (item.unit_cost_price * item.stock_qty) * 100.0, @@ -618,6 +625,13 @@ class SellingController(StockController): self.precision("amount", item), ) + self.gross_profit_percentage = total_gross_profit / total_cost * 100.0 if total_cost else 0.0 + self.markup_percentage = ( + (total_selling_amount - total_cost) / total_selling_amount * 100.0 + if total_selling_amount + else 0.0 + ) + def set_customer_address(self): address_dict = { "customer_address": "address_display", diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json index 315d9a55fce1af0308b10f14240c0d4704c56d1d..305b1a6057fddd976ca208eba30fa7fe609449bc 100644 --- a/erpnext/selling/doctype/quotation/quotation.json +++ b/erpnext/selling/doctype/quotation/quotation.json @@ -131,6 +131,10 @@ "opportunity", "supplier_quotation", "enq_det", + "margin_section", + "markup_percentage", + "column_break_htdb", + "gross_profit_percentage", "connections_tab" ], "fields": [ @@ -1073,13 +1077,34 @@ "fieldtype": "Link", "label": "Recurrence Period", "options": "Recurrence Period" + }, + { + "fieldname": "markup_percentage", + "fieldtype": "Percent", + "label": "Markup Percentage", + "read_only": 1 + }, + { + "fieldname": "gross_profit_percentage", + "fieldtype": "Percent", + "label": "Gross Profit Percentage", + "read_only": 1 + }, + { + "fieldname": "margin_section", + "fieldtype": "Section Break", + "label": "Margin / Markup" + }, + { + "fieldname": "column_break_htdb", + "fieldtype": "Column Break" } ], "icon": "fa fa-shopping-cart", "idx": 82, "is_submittable": 1, "links": [], - "modified": "2024-01-05 15:56:54.965566", + "modified": "2024-01-19 21:04:30.248635", "modified_by": "Administrator", "module": "Selling", "name": "Quotation", diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index 74651af1ae12519b378215d547ef67391150c8ec..4454009852ddd09eadf1bbeb8ccc9aa3738d33b7 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -12,6 +12,112 @@ form_grid_templates = {"items": "templates/form_grid/item_grid.html"} class Quotation(SellingController): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + from erpnext.accounts.doctype.payment_schedule.payment_schedule import PaymentSchedule + from erpnext.accounts.doctype.pricing_rule_detail.pricing_rule_detail import PricingRuleDetail + from erpnext.accounts.doctype.sales_taxes_and_charges.sales_taxes_and_charges import ( + SalesTaxesandCharges, + ) + from erpnext.crm.doctype.competitor_detail.competitor_detail import CompetitorDetail + from erpnext.selling.doctype.quotation_item.quotation_item import QuotationItem + from erpnext.setup.doctype.quotation_lost_reason_detail.quotation_lost_reason_detail import ( + QuotationLostReasonDetail, + ) + from erpnext.stock.doctype.packed_item.packed_item import PackedItem + + additional_discount_percentage: DF.Float + address_display: DF.SmallText | None + amended_from: DF.Link | None + apply_discount_on: DF.Literal["", "Grand Total", "Net Total"] + auto_repeat: DF.Link | None + base_discount_amount: DF.Currency + base_grand_total: DF.Currency + base_in_words: DF.Data | None + base_net_total: DF.Currency + base_rounded_total: DF.Currency + base_rounding_adjustment: DF.Currency + base_total: DF.Currency + base_total_taxes_and_charges: DF.Currency + campaign: DF.Link | None + company: DF.Link + company_address: DF.Link | None + company_address_display: DF.SmallText | None + competitors: DF.TableMultiSelect[CompetitorDetail] + contact_display: DF.SmallText | None + contact_email: DF.Data | None + contact_mobile: DF.SmallText | None + contact_person: DF.Link | None + conversion_rate: DF.Float + coupon_code: DF.Link | None + currency: DF.Link + customer_address: DF.Link | None + customer_group: DF.Link | None + customer_name: DF.Data | None + discount_amount: DF.Currency + enq_det: DF.Text | None + grand_total: DF.Currency + gross_profit_percentage: DF.Percent + group_same_items: DF.Check + ignore_pricing_rule: DF.Check + in_words: DF.Data | None + incoterm: DF.Link | None + items: DF.Table[QuotationItem] + language: DF.Data | None + letter_head: DF.Link | None + lost_reasons: DF.TableMultiSelect[QuotationLostReasonDetail] + markup_percentage: DF.Percent + named_place: DF.Data | None + naming_series: DF.Literal["SAL-QTN-.YYYY.-"] + net_total: DF.Currency + opportunity: DF.Link | None + order_lost_reason: DF.SmallText | None + order_type: DF.Literal["", "Sales", "Maintenance", "Shopping Cart"] + other_charges_calculation: DF.LongText | None + packed_items: DF.Table[PackedItem] + party_name: DF.DynamicLink | None + payment_schedule: DF.Table[PaymentSchedule] + payment_terms_template: DF.Link | None + plc_conversion_rate: DF.Float + price_list_currency: DF.Link + pricing_rules: DF.Table[PricingRuleDetail] + quotation_to: DF.Link + recurrence_period: DF.Link | None + referral_sales_partner: DF.Link | None + rounded_total: DF.Currency + rounding_adjustment: DF.Currency + scan_barcode: DF.Data | None + select_print_heading: DF.Link | None + selling_price_list: DF.Link + shipping_address: DF.SmallText | None + shipping_address_name: DF.Link | None + shipping_rule: DF.Link | None + source: DF.Link | None + status: DF.Literal[ + "Draft", "Open", "Replied", "Partially Ordered", "Ordered", "Lost", "Cancelled", "Expired" + ] + supplier_quotation: DF.Link | None + tax_category: DF.Link | None + taxes: DF.Table[SalesTaxesandCharges] + taxes_and_charges: DF.Link | None + tc_name: DF.Link | None + terms: DF.TextEditor | None + territory: DF.Link | None + title: DF.Data | None + total: DF.Currency + total_net_weight: DF.Float + total_qty: DF.Float + total_taxes_and_charges: DF.Currency + transaction_date: DF.Date + valid_till: DF.Date | None + # end: auto-generated types + def set_indicator(self): if self.docstatus == 1: self.indicator_color = "blue"