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 0000000..14d950c Binary files /dev/null and b/rangeldigital/rangel_digital/number_card/overdue_opportunities/__pycache__/overdue_opportunities.cpython-310.pyc differ 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 0000000..11f70c0 Binary files /dev/null and b/rangeldigital/rangel_digital/number_card/pipeline_value/__pycache__/pipeline_value.cpython-310.pyc differ 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 0000000..87eeda8 Binary files /dev/null and b/rangeldigital/rangel_digital/number_card/pipeline_value_(this_month)/__pycache__/pipeline_value_(this_month).cpython-310.pyc differ 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 0000000..34256e9 Binary files /dev/null and b/rangeldigital/rangel_digital/overrides/__pycache__/quotation.cpython-310.pyc differ