From f44b3e860588a39826dbf8acefb54e3dbc7f62fa Mon Sep 17 00:00:00 2001 From: Jeremy Rangel Date: Thu, 19 Jun 2025 23:40:03 -0700 Subject: [PATCH] Added Number Cards for Opportunities --- .../fixtures/item_custom_fields.json | 12 ++++++ .../fixtures/print_format_rd_quotation_1.json | 4 +- rangeldigital/fixtures/property_setter.json | 1 + .../quotation_item_custom_fields.json | 14 +++++++ .../fixtures/sales_stage_custom_fields.json | 13 ++++++ rangeldigital/public/css/rangeldigital.css | 24 +++++++++++- .../opportunities_closing_this_month.json | 21 ++++++++++ .../overdue_opportunities.cpython-310.pyc | Bin 0 -> 718 bytes .../overdue_opportunities.json | 19 +++++++++ .../overdue_opportunities.py | 23 +++++++++++ .../pipeline_total/pipeline_total.json | 22 +++++++++++ .../pipeline_total_(this_month).json | 22 +++++++++++ .../pipeline_value.cpython-310.pyc | Bin 0 -> 1126 bytes .../pipeline_value/pipeline_value.json | 22 +++++++++++ .../pipeline_value/pipeline_value.py | 36 +++++++++++++++++ ...ipeline_value_(this_month).cpython-310.pyc | Bin 0 -> 1193 bytes .../pipeline_value_(this_month).json | 22 +++++++++++ .../pipeline_value_(this_month).py | 37 ++++++++++++++++++ .../__pycache__/quotation.cpython-310.pyc | Bin 0 -> 1025 bytes 19 files changed, 288 insertions(+), 4 deletions(-) create mode 100644 rangeldigital/fixtures/item_custom_fields.json create mode 100644 rangeldigital/fixtures/quotation_item_custom_fields.json create mode 100644 rangeldigital/fixtures/sales_stage_custom_fields.json create mode 100644 rangeldigital/rangel_digital/number_card/opportunities_closing_this_month/opportunities_closing_this_month.json create mode 100644 rangeldigital/rangel_digital/number_card/overdue_opportunities/__pycache__/overdue_opportunities.cpython-310.pyc create mode 100644 rangeldigital/rangel_digital/number_card/overdue_opportunities/overdue_opportunities.json create mode 100644 rangeldigital/rangel_digital/number_card/overdue_opportunities/overdue_opportunities.py create mode 100644 rangeldigital/rangel_digital/number_card/pipeline_total/pipeline_total.json create mode 100644 rangeldigital/rangel_digital/number_card/pipeline_total_(this_month)/pipeline_total_(this_month).json create mode 100644 rangeldigital/rangel_digital/number_card/pipeline_value/__pycache__/pipeline_value.cpython-310.pyc create mode 100644 rangeldigital/rangel_digital/number_card/pipeline_value/pipeline_value.json create mode 100644 rangeldigital/rangel_digital/number_card/pipeline_value/pipeline_value.py create mode 100644 rangeldigital/rangel_digital/number_card/pipeline_value_(this_month)/__pycache__/pipeline_value_(this_month).cpython-310.pyc create mode 100644 rangeldigital/rangel_digital/number_card/pipeline_value_(this_month)/pipeline_value_(this_month).json create mode 100644 rangeldigital/rangel_digital/number_card/pipeline_value_(this_month)/pipeline_value_(this_month).py create mode 100644 rangeldigital/rangel_digital/overrides/__pycache__/quotation.cpython-310.pyc diff --git a/rangeldigital/fixtures/item_custom_fields.json b/rangeldigital/fixtures/item_custom_fields.json new file mode 100644 index 0000000..487bc5f --- /dev/null +++ b/rangeldigital/fixtures/item_custom_fields.json @@ -0,0 +1,12 @@ +[ + { + + "doctype": "Custom Field", + "dt": "Item", + "fieldname": "manuf_item_code", + "fieldtype": "Data", + "insert_after": "stock_uom", + "label": "Manufacturer Item Code", + "name": "item_manuf_item_code" + } + ] \ No newline at end of file diff --git a/rangeldigital/fixtures/print_format_rd_quotation_1.json b/rangeldigital/fixtures/print_format_rd_quotation_1.json index 97f005e..f9c8ade 100644 --- a/rangeldigital/fixtures/print_format_rd_quotation_1.json +++ b/rangeldigital/fixtures/print_format_rd_quotation_1.json @@ -7,8 +7,8 @@ "doc_type": "Quotation", "docstatus": 0, "doctype": "Print Format", - "html": "

Rangel Digital LLC

sales@rangeldigital.com

541-808-9102

QUOTE

Quote #

Quote Date

{% if doc.valid_till %}

Expires

{% endif %}

{{ doc.name }}

{{ frappe.utils.formatdate(doc.transaction_date, \"MMMM d, YYYY\") }}

{% if doc.valid_till %}

{{ frappe.utils.formatdate(doc.valid_till, \"MMMM d, yyyy\") }}

{% endif %}

Bill To

{{doc.party_name }}

{% if doc.address_display%}{{ doc.address_display}}{% endif %}

{% if doc.shipping_address%}

Deliver To

{{doc.party_name }}

{{ doc.shipping_address}}

{% endif %}
{% for item in doc.items %}{% if item.description and item.description != item.item_name %}{% endif %}{% endfor %}
ItemQuantityPriceTotal
{{ item.item_name }}{{ item.qty }}${{ \"{:,.2f}\".format(item.rate) }}${{ \"{:,.2f}\".format(item.amount) }}
{{ item.description }}
Sub-Total: ${{ \"{:,.2f}\".format(doc.base_total) }}
Taxes: ${{ \"{:,.2f}\".format(doc.total_taxes_and_charges) }}
Discount:${{ \"{:,.2f}\".format(doc.discount_amount) }}
Grand Total:${{ \"{:,.2f}\".format(doc.grand_total) }}
{% if doc.terms %}

Terms and Conditions

{{doc.terms}}

{% endif %}", - "css": ".bold {font-weight:bold} .font-size-1 {font-size:14px!important;} .font-size-2 {font-size:16px!important} .font-size-4 {font-size:24px!important;} .font-color-3 {color:#008AFC!important} .text-left {text-align:left!important;} .text-center {text-align:center!important;} .text-right {text-align:right!important} table {border-spacing: 0 1px;border-collapse:separate} p,td {font-size:14px;} h2 {margin:0!important;} h2.quote-heading {font-size:20px;} .header {margin-bottom:40px;} .print-format td.details {padding-left:20px!important} .addresses {margin-bottom:40px;} .items-table *{line-height:16px} .items-table .row:before {display:none!important;} .items-table th, .items-table td {font-size:14px;} .items-table .header-row {background:#02204B} .items-table .header-row td {font-weight:bold} .items-table .header-row th {color:white;font-weight:bold} .items-table .row:not(.description-row) td {padding-top:5px!important;padding-bottom:0!important;} .items-table .description-row td {padding-top:0!important;padding-bottom:0!important;} .description-row p {padding-left:5px!important} .totals {margin-top:50px;font-size:14px} .totals td {padding-top:3px!important;padding-right:10px!important;padding-bottom:3px!important;} .totals .grand-total {background:#f1f1f1;} .totals .grand-total td{padding-top:7px!important;padding-bottom:7px!important;} .terms-section {margin-top:40px;} .terms-section h3 {margin-bottom:5px;}", +"html": "

Rangel Digital LLC

sales@rangeldigital.com

541-808-9102

QUOTE

Quote #

Quote Date

{% if doc.valid_till %}

Expires

{% endif %}

{{ doc.name }}

{{ frappe.utils.formatdate(doc.transaction_date, \"MMMM d, YYYY\") }}

{% if doc.valid_till %}

{{ frappe.utils.formatdate(doc.valid_till, \"MMMM d, yyyy\") }}

{% endif %}

Bill To

{{doc.party_name }}

{% if doc.address_display%}{{ doc.address_display}}{% endif %}

{% if doc.shipping_address%}

Deliver To

{{doc.party_name }}

{{ doc.shipping_address}}

{% endif %}
{% for item in doc.items %}{% if item.description and item.description != item.item_name %}{% endif %}{% endfor %}
ItemQuantityPriceTotal
{{ item.item_name }}{% if item.manuf_item_code %} ({{ item.manuf_item_code }}){% endif %}{{ item.qty }}${{ \"{:,.2f}\".format(item.rate) }}${{ \"{:,.2f}\".format(item.amount) }}
{{ item.description }}
Sub-Total:${{ \"{:,.2f}\".format(doc.base_total) }}
Taxes: ${{ \"{:,.2f}\".format(doc.total_taxes_and_charges) }}
Discount:${{ \"{:,.2f}\".format(doc.discount_amount) }}
Grand Total:${{ \"{:,.2f}\".format(doc.grand_total) }}
{% if doc.terms %}

Terms and Conditions

{{doc.terms}}

{% endif %}" +, "css": ".bold {font-weight:bold} .font-size-1 {font-size:14px!important;} .font-size-2 {font-size:16px!important} .font-size-4 {font-size:24px!important;} .font-color-3 {color:#008AFC!important} .text-left {text-align:left!important;} .text-center {text-align:center!important;} .text-right {text-align:right!important} table {border-spacing: 0 1px;border-collapse:separate} p,td {font-size:14px;} h2 {margin:0!important;} h2.quote-heading {font-size:20px;} .header {margin-bottom:40px;} .print-format td.details {padding-left:20px!important} .addresses {margin-bottom:40px;} .items-table *{line-height:16px} .items-table .row:before {display:none!important;} .items-table th, .items-table td {font-size:14px;} .items-table .header-row {background:#02204B} .items-table .header-row td {font-weight:bold} .items-table .header-row th {color:white;font-weight:bold} .items-table .row:not(.description-row) td {padding-top:5px!important;padding-bottom:0!important;} .items-table .description-row td {padding-top:0!important;padding-bottom:0!important;} .description-row p {padding-left:5px!important} .totals {margin-top:50px;font-size:14px} .totals td {padding-top:3px!important;padding-right:10px!important;padding-bottom:3px!important;} .totals .grand-total {background:#f1f1f1;} .totals .grand-total td{padding-top:7px!important;padding-bottom:7px!important;} .terms-section {margin-top:40px;} .terms-section h3 {margin-bottom:5px;}", "idx": 1, "line_breaks": 0, "is_default": 1, diff --git a/rangeldigital/fixtures/property_setter.json b/rangeldigital/fixtures/property_setter.json index ed1da2d..77a8f2d 100644 --- a/rangeldigital/fixtures/property_setter.json +++ b/rangeldigital/fixtures/property_setter.json @@ -1,4 +1,5 @@ [ + { "doctype": "Property Setter", "name": "Lead-status_options", diff --git a/rangeldigital/fixtures/quotation_item_custom_fields.json b/rangeldigital/fixtures/quotation_item_custom_fields.json new file mode 100644 index 0000000..11c4615 --- /dev/null +++ b/rangeldigital/fixtures/quotation_item_custom_fields.json @@ -0,0 +1,14 @@ +[ + { + + "doctype": "Custom Field", + "dt": "Quotation Item", + "fetch_from": "item_code.manuf_item_code", + "fieldname": "manuf_item_code", + "fieldtype": "Data", + "insert_after": "item_code", + "label": "Manufacturer Item Code", + "name": "quotation_item_manuf_item_code", + "readonly": 1 + } + ] \ No newline at end of file diff --git a/rangeldigital/fixtures/sales_stage_custom_fields.json b/rangeldigital/fixtures/sales_stage_custom_fields.json new file mode 100644 index 0000000..5f91cee --- /dev/null +++ b/rangeldigital/fixtures/sales_stage_custom_fields.json @@ -0,0 +1,13 @@ +[ + { + + "doctype": "Custom Field", + "dt": "Sales Stage", + "fieldname": "opportunity_probability", + "fieldtype": "Percent", + "insert_after": "", + "label": "Probability", + "name": "sales_stage_opportunity_probability", + "readonly": 0 + } + ] \ No newline at end of file diff --git a/rangeldigital/public/css/rangeldigital.css b/rangeldigital/public/css/rangeldigital.css index 0239895..fe64118 100644 --- a/rangeldigital/public/css/rangeldigital.css +++ b/rangeldigital/public/css/rangeldigital.css @@ -1,9 +1,15 @@ /* VARIABLES */ :root { + --primary: white; --text-color: white; + --border-color: none; + --darker-border-color: none; --fg-color: #1D1F37; /* Dark Bluish Purple */ --gray-900: white; + --gray-700: #cccccc; --control-bg: #008AFC; /* Light Blue */ + --icon-stroke: #008AFC; + } @@ -29,11 +35,25 @@ h2 {font-size:3rem} /* Button */ button {background:#008AFC} +/* Form Elements */ +input.form-control { + background-color: var(--fg-color)!important; + border-bottom: 1px solid gray; + border-radius:0; +} + /* ------- FRAPPE ELEMENTS ------- */ /* Text */ -body .text-muted {color:#cccccc} - +body .text-muted {color:#cccccc!important} +/* Forms */ +.web-form-header,form.web-form {border:none}; +.form-group .ql-container.ql-snow {border:none;background:var(--fg-color)} +.form-group .ql-container.ql-snow svg path {stroke:#cccccc} +/* Account Info Box */ +@media only screen and (max-width:767px){ + .row.account-info {padding:15px} +} /* Web Sidebar */ .web-sidebar {margin-top:60px} diff --git a/rangeldigital/rangel_digital/number_card/opportunities_closing_this_month/opportunities_closing_this_month.json b/rangeldigital/rangel_digital/number_card/opportunities_closing_this_month/opportunities_closing_this_month.json new file mode 100644 index 0000000..0192f91 --- /dev/null +++ b/rangeldigital/rangel_digital/number_card/opportunities_closing_this_month/opportunities_closing_this_month.json @@ -0,0 +1,21 @@ +{ + "creation": "2025-06-18 20:11:15.922486", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Opportunity", + "dynamic_filters_json": "[[\"Opportunity\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Opportunity\",\"expected_closing\",\"Timespan\",\"this month\",false],[\"Opportunity\",\"status\",\"not in\",[\"Closed\",\"Lost\"],false]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Opportunities Closing This Month", + "modified": "2025-06-19 12:15:53.088837", + "modified_by": "Administrator", + "module": "CRM", + "name": "Opportunities Closing This Month", + "owner": "Administrator", + "show_percentage_stats": 0, + "stats_time_interval": "", + "type": "Document Type" + } \ No newline at end of file diff --git a/rangeldigital/rangel_digital/number_card/overdue_opportunities/__pycache__/overdue_opportunities.cpython-310.pyc b/rangeldigital/rangel_digital/number_card/overdue_opportunities/__pycache__/overdue_opportunities.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..14d950cc466716c4cd4b93c9e5dd97d3cdc3e8d1 GIT binary patch literal 718 zcmZuvy>8nu5GE;$mK`T;(WG-04;ox8-5L}DG8HJ2A;?fKf~=!u8q^O+DyfZRO4m+Z zGuUIFqVK`AQ(vJ#gC66i^^gPZ|BiR}N%ih-pCElF7qhpFkRRUJrU0E|Wb+0E5TJ^T zy=ID(bWEwY1I0=>mWLfCYx zKgAE6g}APHg<>z@0i&GtY3H8bvmV-%|JFVghx!Gohif<3^sZ)jk&=jwFL*e5OB`U# YMMq@~trG7jddB~}5gE_{j@YAr06a3rjQ{`u literal 0 HcmV?d00001 diff --git a/rangeldigital/rangel_digital/number_card/overdue_opportunities/overdue_opportunities.json b/rangeldigital/rangel_digital/number_card/overdue_opportunities/overdue_opportunities.json new file mode 100644 index 0000000..3189ce8 --- /dev/null +++ b/rangeldigital/rangel_digital/number_card/overdue_opportunities/overdue_opportunities.json @@ -0,0 +1,19 @@ +{ + "creation": "2025-06-18 20:11:15.922486", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Opportunity", + "method": "rangeldigital.rangel_digital.number_card.overdue_opportunities.overdue_opportunities.get_number_card_data", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Overdue Opportunities", + "modified": "2025-06-19 12:15:53.088837", + "modified_by": "Administrator", + "module": "CRM", + "name": "Overdue Opportunities", + "owner": "Administrator", + "show_percentage_stats": 0, + "stats_time_interval": "", + "type": "Custom" + } \ No newline at end of file diff --git a/rangeldigital/rangel_digital/number_card/overdue_opportunities/overdue_opportunities.py b/rangeldigital/rangel_digital/number_card/overdue_opportunities/overdue_opportunities.py new file mode 100644 index 0000000..ce3a652 --- /dev/null +++ b/rangeldigital/rangel_digital/number_card/overdue_opportunities/overdue_opportunities.py @@ -0,0 +1,23 @@ +import frappe +from frappe import _ +from datetime import datetime + +@frappe.whitelist() +def get_number_card_data(): + # Get today's date in YYYY-MM-DD format + today = datetime.today().strftime('%Y-%m-%d') + + # Fetch count of open opportunities closing before today + count = frappe.db.count( + "Opportunity", + filters={ + "expected_closing": ["<", today], + "status": ["not in", ["Closed", "Lost"]] + } + ) + + return { + "value": count, + "fieldtype": "Int", + "label": _("Overdue Opportunities") + } diff --git a/rangeldigital/rangel_digital/number_card/pipeline_total/pipeline_total.json b/rangeldigital/rangel_digital/number_card/pipeline_total/pipeline_total.json new file mode 100644 index 0000000..3130da1 --- /dev/null +++ b/rangeldigital/rangel_digital/number_card/pipeline_total/pipeline_total.json @@ -0,0 +1,22 @@ +{ + "aggregate_function_based_on": "base_opportunity_amount", + "creation": "2020-07-20 20:17:15.922486", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Opportunity", + "dynamic_filters_json": "[[\"Opportunity\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Opportunity\",\"status\",\"not in\",[\"Closed\",\"Lost\"],false]]", + "function": "Sum", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Pipeline Total", + "modified": "2025-06-19 12:15:53.088837", + "modified_by": "Administrator", + "module": "CRM", + "name": "Pipeline Total", + "owner": "Administrator", + "show_percentage_stats": 0, + "stats_time_interval": "", + "type": "Document Type" + } \ No newline at end of file diff --git a/rangeldigital/rangel_digital/number_card/pipeline_total_(this_month)/pipeline_total_(this_month).json b/rangeldigital/rangel_digital/number_card/pipeline_total_(this_month)/pipeline_total_(this_month).json new file mode 100644 index 0000000..b5ff3b4 --- /dev/null +++ b/rangeldigital/rangel_digital/number_card/pipeline_total_(this_month)/pipeline_total_(this_month).json @@ -0,0 +1,22 @@ +{ + "aggregate_function_based_on": "base_opportunity_amount", + "creation": "2020-07-20 20:17:15.922486", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Opportunity", + "dynamic_filters_json": "[[\"Opportunity\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", +"filters_json": "[[\"Opportunity\",\"expected_closing\",\"Timespan\",\"this month\",false],[\"Opportunity\",\"status\",\"not in\",[\"Closed\",\"Lost\"],false]]", + "function": "Sum", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Pipeline Total (This Month)", + "modified": "2025-06-19 12:15:53.088837", + "modified_by": "Administrator", + "module": "CRM", + "name": "Pipeline Total (This Month)", + "owner": "Administrator", + "show_percentage_stats": 0, + "stats_time_interval": "", + "type": "Document Type" + } \ No newline at end of file diff --git a/rangeldigital/rangel_digital/number_card/pipeline_value/__pycache__/pipeline_value.cpython-310.pyc b/rangeldigital/rangel_digital/number_card/pipeline_value/__pycache__/pipeline_value.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..11f70c015b86d050e0e20a9d23055a29dbec8e77 GIT binary patch literal 1126 zcmZuw&2AGh5VpO$&ZbF9i>ek0338y&9NHWK2@q0ygmP#>A`)6TwUfB={$M+xM9GE1 zkvHJbATB%vFXAhwJ^~;F<1AGpz>>%Q?3wY*eB->)SVAzqd^;Sx2@(40!Qu!2JcMcX zKybvdLLI*ng;LzX*zW^HQqm#weW-#o?1X95i7*oAl+F;3IDLywaHqoY0cuswz!TbU zVTThCF=tj-nTi&1jlQr+PikWm&PJ~8p<*RE@>b^~h7E$M1t_z^l`666~QD*QXKe=_ERi^<8gw zTIEX~@!BjX?)^@g!1}Tw1%dYNjRz8M$_=+EnAy}ym3PKvaRz=Cz!8%2q5#- z{c*hce0ww9-sGk>69qansiH7{R_QADc zy*^jDXqNcAQC#C9Hz|zUu(@R_u@^a%PF(y;7&1e zYrZ547Vu3h^B=)%frx`;yo_l;aGj7}G@w7iI3^*ccpYCROE`ubis?^%6O^HZS74c! zvm~irrn57&QkgkdB}3KM1ISiM;LRJ(3$h?K;mpPN(wq;Kc;H*jc)DU#$924dDOtw9 E0pzksBme*a literal 0 HcmV?d00001 diff --git a/rangeldigital/rangel_digital/number_card/pipeline_value/pipeline_value.json b/rangeldigital/rangel_digital/number_card/pipeline_value/pipeline_value.json new file mode 100644 index 0000000..49407a7 --- /dev/null +++ b/rangeldigital/rangel_digital/number_card/pipeline_value/pipeline_value.json @@ -0,0 +1,22 @@ +{ + "creation": "2020-07-20 20:17:15.922486", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Opportunity", + "dynamic_filters_json": "[[\"Opportunity\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Opportunity\",\"status\",\"not in\",[\"Closed\",\"Lost\"],false]]", + "function": "Sum", + "method": "rangeldigital.rangel_digital.number_card.pipeline_value.pipeline_value.get_number_card_data", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Pipeline Value", + "modified": "2025-06-19 12:15:53.088837", + "modified_by": "Administrator", + "module": "CRM", + "name": "Pipeline Value", + "owner": "Administrator", + "show_percentage_stats": 0, + "stats_time_interval": "", + "type": "Custom" + } \ No newline at end of file diff --git a/rangeldigital/rangel_digital/number_card/pipeline_value/pipeline_value.py b/rangeldigital/rangel_digital/number_card/pipeline_value/pipeline_value.py new file mode 100644 index 0000000..df7bcbd --- /dev/null +++ b/rangeldigital/rangel_digital/number_card/pipeline_value/pipeline_value.py @@ -0,0 +1,36 @@ +import frappe +from frappe import _ +from datetime import datetime +from frappe.utils import get_first_day, get_last_day + +@frappe.whitelist() +def get_number_card_data(): + # Get the first and last day of the current month + today = datetime.today() + first_day = get_first_day(today).strftime('%Y-%m-%d') + last_day = get_last_day(today).strftime('%Y-%m-%d') + + # Fetch open opportunities closing this month + opportunities = frappe.db.get_all( + "Opportunity", + filters={ + "status": ["not in", ["Closed", "Lost"]] + }, + fields=["name", "sales_stage", "base_opportunity_amount"] + ) + + total_theoretical_value = 0 + + for opp in opportunities: + probability = 0 + if opp.sales_stage: + probability = frappe.db.get_value("Sales Stage", opp.sales_stage, "opportunity_probability") or 0 + + theoretical_value = (opp.base_opportunity_amount or 0) * (probability / 100) + total_theoretical_value += theoretical_value + + return { + "value": round(total_theoretical_value, 2), + "fieldtype": "Currency", + "label": _("Theoretical Value (This Month)") + } diff --git a/rangeldigital/rangel_digital/number_card/pipeline_value_(this_month)/__pycache__/pipeline_value_(this_month).cpython-310.pyc b/rangeldigital/rangel_digital/number_card/pipeline_value_(this_month)/__pycache__/pipeline_value_(this_month).cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..87eeda8da66bb9115946535f26a996d198214159 GIT binary patch literal 1193 zcmaJ=&2JPp6t_Jyp4m?VLXA)*SOJk()Ud~13aT28d>j&x7zvHMYwxZj&qr-<=|;1s zaP5D9)TB2q{2BZazV?)VArgq^SwdE#sz-kI^ZWk2H)XHaMzC(4AIv|72>t7s%@qLi zD{S)^2uB<%l=_n>l;ad*e-9MNNlKb~sDeC9!#qkOj08HNr-(SlZEVKb(sezn*qWPotBaMug~Hy0Z9V|8=mejl6|$s; zNGAcur+9^JxWN${9n!;?6CTvKCNubW=yPjs@jEg@JoyLn)?dLY;O$jd2anO~TdN4} zw2lrF{m6SbY4MKdc=t4@9)6XztKWFLro8tfx{g+H9oF&BXchkclkL#di7D zG3Hm_1%Sb|XGh7-tKFS^cZa*q^QtPfT@=zTT|5@{jS$5VElN9(g`<0_G=jVENog!# zv?czm#Ka1oO+X-vnWM%syD-DRg$2un>lmhl$>4P+-1e9mk(HMbGM1N%!h4FRQdyym zqf;pqHyQ(wN1c6dJ=piw>Gq~!rOPoJO9g43aZo)YKwPr7&{`Ce<CT$ zB~uGAxOXs@X7IEu?0g9MMSu8L*J|R~WhGps*jT7xPy0D@ku9O`8s-rE8mp&%E_CEs zI@PQK1H#8H_H)6M(mgL}d9HD+`;8L~^>OW!8zy?YQTaAgX>u`7u6J3R6eiuarDZC! zmo==Nx&ZWCXQTM^>UT=-k-iOq&i@|0Fo5XjP_tqt)M#H?VMgoJzaG0|0Bb&iV$5i* z#pO&w=>O!h3t_Ry$3kZlrunFnl~A$}>jblV7C_GO2IT*fJ*bxMx(92+7yM_I`9I0r z2a*JR+{ZK^xJ}wP{7gf79<~w^Vv2vj*WfJy8O8LIz6+<`Zj{F0Y^p~NFUhNcT7L0#O%TbPnQ{sIA#UE2Tv literal 0 HcmV?d00001 diff --git a/rangeldigital/rangel_digital/number_card/pipeline_value_(this_month)/pipeline_value_(this_month).json b/rangeldigital/rangel_digital/number_card/pipeline_value_(this_month)/pipeline_value_(this_month).json new file mode 100644 index 0000000..797b2db --- /dev/null +++ b/rangeldigital/rangel_digital/number_card/pipeline_value_(this_month)/pipeline_value_(this_month).json @@ -0,0 +1,22 @@ +{ + "creation": "2020-07-20 20:17:15.922486", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Opportunity", + "dynamic_filters_json": "[[\"Opportunity\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Opportunity\",\"status\",\"not in\",[\"Closed\",\"Lost\"],false]]", + "function": "Sum", + "method": "rangeldigital.rangel_digital.number_card.pipeline_value_(this_month).pipeline_value_(this_month).get_number_card_data", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Pipeline Value (This Month)", + "modified": "2025-06-19 12:15:53.088837", + "modified_by": "Administrator", + "module": "CRM", + "name": "Pipeline Value (This Month)", + "owner": "Administrator", + "show_percentage_stats": 0, + "stats_time_interval": "", + "type": "Custom" + } \ No newline at end of file diff --git a/rangeldigital/rangel_digital/number_card/pipeline_value_(this_month)/pipeline_value_(this_month).py b/rangeldigital/rangel_digital/number_card/pipeline_value_(this_month)/pipeline_value_(this_month).py new file mode 100644 index 0000000..55b9e09 --- /dev/null +++ b/rangeldigital/rangel_digital/number_card/pipeline_value_(this_month)/pipeline_value_(this_month).py @@ -0,0 +1,37 @@ +import frappe +from frappe import _ +from datetime import datetime +from frappe.utils import get_first_day, get_last_day + +@frappe.whitelist() +def get_number_card_data(): + # Get the first and last day of the current month + today = datetime.today() + first_day = get_first_day(today).strftime('%Y-%m-%d') + last_day = get_last_day(today).strftime('%Y-%m-%d') + + # Fetch open opportunities closing this month + opportunities = frappe.db.get_all( + "Opportunity", + filters={ + "expected_closing": ["between", [first_day, last_day]], + "status": ["not in", ["Closed", "Lost"]] + }, + fields=["name", "sales_stage", "base_opportunity_amount"] + ) + + total_theoretical_value = 0 + + for opp in opportunities: + probability = 0 + if opp.sales_stage: + probability = frappe.db.get_value("Sales Stage", opp.sales_stage, "opportunity_probability") or 0 + + theoretical_value = (opp.base_opportunity_amount or 0) * (probability / 100) + total_theoretical_value += theoretical_value + + return { + "value": round(total_theoretical_value, 2), + "fieldtype": "Currency", + "label": _("Theoretical Value (This Month)") + } diff --git a/rangeldigital/rangel_digital/overrides/__pycache__/quotation.cpython-310.pyc b/rangeldigital/rangel_digital/overrides/__pycache__/quotation.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..34256e921f681582c3cb09fd76ce218e7ae15f63 GIT binary patch literal 1025 zcmb7Dy>1gh5Z=H0v17*x2@;{Eu~5zu6f_Y;P#_v36wzFBx_H;lTIajV?iwZNQYGRg z0@AtUQFsB0ZK-$#3dF204vs{_o_2osX6KukExO${g7xkDvy-O`p&w3JUtmLru=N2D zjyTRx+;ALY&`CySG^W_uXvSt<>|^vAamKwX#696((%9qp2=)D+5CIMQ*zT06(jv*z znan4N7D@^Y=GMD;VNxTDeB@59F9OOTY`qIaAa`?2IOgOE#gtQs&J+dL2m@Iy4+OVg z!PcLFRA`FIZG&q95?$b$alF7&ul6}vU{3eY1vWv2FYyJbNF8#v0LipWyWZd)_ZI{_ zv?A94ucFuJ3%y_lPdjzj#jDtYs5k#}T0f~+g_2E<06;Lb-8&VrxUC!^32B>d(M`NXw=mG(ZT9ir?YKp&ABKHq!^91yND|vl zl3Bs$nUgz7ayn15#-koW;3c>9_>DsypWA}DtTFC)6?X!IA|t+b3AZ@*Pgr##JWp{yn3imzN&Hauu>Ja^-y-FpvF Jh&%A@l0W?}7Nh_G literal 0 HcmV?d00001