diff --git a/custom_ui/api/db/estimates.py b/custom_ui/api/db/estimates.py index 8aadf5a..678200b 100644 --- a/custom_ui/api/db/estimates.py +++ b/custom_ui/api/db/estimates.py @@ -4,7 +4,7 @@ from custom_ui.api.db.general import get_doc_history from custom_ui.db_utils import DbUtils, process_query_conditions, build_datatable_dict, get_count_or_filters, build_success_response, build_error_response from werkzeug.wrappers import Response from custom_ui.api.db.clients import check_if_customer, convert_lead_to_customer -from custom_ui.services import DbService, ClientService, AddressService, ContactService, EstimateService, ItemService +from custom_ui.services import DbService, ClientService, AddressService, ContactService # =============================================================================== # ESTIMATES & INVOICES API METHODS @@ -86,25 +86,11 @@ def get_estimate_table_data(filters={}, sortings=[], page=1, page_size=10): @frappe.whitelist() -def get_quotation_items(project_template:str = None): +def get_quotation_items(): """Get all available quotation items.""" try: - filters = EstimateService.map_project_template_to_filter(project_template) - items = frappe.get_all("Item", fields=["item_code", "item_group"], filters=filters) - grouped_item_dicts = {} - for item in items: - item_dict = ItemService.get_full_dict(item.item_code) - if item_dict["bom"]: - if "Packages" not in grouped_item_dicts: - grouped_item_dicts["Packages"] = {} - if item.item_group not in grouped_item_dicts["Packages"]: - grouped_item_dicts["Packages"][item.item_group] = [] - grouped_item_dicts["Packages"][item.item_group].append(item_dict) - else: - if item.item_group not in grouped_item_dicts: - grouped_item_dicts[item.item_group] = [] - grouped_item_dicts[item.item_group].append(item_dict) - return build_success_response(grouped_item_dicts) + items = frappe.get_all("Item", fields=["*"], filters={"item_group": "SNW-S"}) + return build_success_response(items) except Exception as e: return build_error_response(str(e), 500) diff --git a/custom_ui/api/public/payments.py b/custom_ui/api/public/payments.py index 9f4a88a..b369087 100644 --- a/custom_ui/api/public/payments.py +++ b/custom_ui/api/public/payments.py @@ -1,8 +1,7 @@ import frappe import json from frappe.utils.data import flt -from custom_ui.services import DbService, StripeService, PaymentService -from custom_ui.models import PaymentData +from custom_ui.services import DbService, StripeService @frappe.whitelist(allow_guest=True) def half_down_stripe_payment(sales_order): @@ -32,36 +31,37 @@ def stripe_webhook(): sig_header = frappe.request.headers.get('Stripe-Signature') session, metadata = StripeService.get_session_and_metadata(payload, sig_header) - required_keys = ["order_num", "company", "payment_type"] - for key in required_keys: - if not metadata.get(key): - raise frappe.ValidationError(f"Missing required metadata key: {key}") - if DbService.exists("Payment Entry", {"reference_no": session.id}): raise frappe.ValidationError("Payment Entry already exists for this session.") - - reference_doctype = "Sales Invoice" + if metadata.get("payment_type") == "advance": reference_doctype = "Sales Order" elif metadata.get("payment_type") != "full": raise frappe.ValidationError("Invalid payment type in metadata.") amount_paid = flt(session.amount_total) / 100 + currency = session.currency.upper() + reference_doc = frappe.get_doc(reference_doctype, metadata.get("order_num")) - # stripe_settings = StripeService.get_stripe_settings(metadata.get("company")) + pe = frappe.get_doc({ + "doctype": "Payment Entry", + "payment_type": "Receive", + "party_type": "Customer", + "mode_of_payment": "Stripe", + "party": reference_doc.customer, + "party_name": reference_doc.customer, + "paid_to": metadata.get("company"), + "reference_no": session.id, + "reference_date": frappe.utils.nowdate(), + "reference_doctype": reference_doctype, + "reference_name": reference_doc.name, + "paid_amount": amount_paid, + "paid_currency": currency, + }) - pe = PaymentService.create_payment_entry( - data=PaymentData( - mode_of_payment="Stripe", - reference_no=session.id, - reference_date=session.created, - received_amount=amount_paid, - company=metadata.get("company"), - reference_doc_name=metadata.get("order_num") - ) - ) + pe.insert() pe.submit() return "Payment Entry created and submitted successfully." diff --git a/custom_ui/install.py b/custom_ui/install.py index 450e82d..c4a42ae 100644 --- a/custom_ui/install.py +++ b/custom_ui/install.py @@ -24,8 +24,6 @@ def after_install(): create_task_types() # create_tasks() create_bid_meeting_note_form_templates() - create_accounts() - init_stripe_accounts() build_frontend() def after_migrate(): @@ -44,8 +42,6 @@ def after_migrate(): create_task_types() # create_tasks() create_bid_meeting_note_form_templates() - create_accounts() - init_stripe_accounts() # update_address_fields() # build_frontend() @@ -1382,58 +1378,3 @@ def create_bid_meeting_note_form_templates(): ) doc.insert(ignore_permissions=True) - -def create_accounts(): - """Create necessary accounts if they do not exist.""" - print("\nšŸ”§ Checking for necessary accounts...") - - accounts = [ - { - "Sprinklers Northwest": [ - { - "account_name": "Stripe Clearing - Sprinklers Northwest", - "account_type": "Bank", - "parent_account": "Bank Accounts - S", - "company": "Sprinklers Northwest" - } - ] - } - ] - - for company_accounts in accounts: - for company, account_list in company_accounts.items(): - for account in account_list: - # Idempotency check - if frappe.db.exists("Account", {"account_name": account["account_name"], "company": account["company"]}): - continue - doc = frappe.get_doc({ - "doctype": "Account", - "account_name": account["account_name"], - "account_type": account["account_type"], - "company": account["company"], - "parent_account": account["parent_account"], - "is_group": 0 - }) - doc.insert(ignore_permissions=True) - - frappe.db.commit() - -def init_stripe_accounts(): - """Initializes the bare configurations for each Stripe Settings doctypes.""" - print("\nšŸ”§ Initializing Stripe Settings for companies...") - - companies = ["Sprinklers Northwest"] - - for company in companies: - if not frappe.db.exists("Stripe Settings", {"company": company}): - doc = frappe.get_doc({ - "doctype": "Stripe Settings", - "company": company, - "api_key": "", - "publishable_key": "", - "webhook_secret": "", - "account": f"Stripe Clearing - {company}" - }) - doc.insert(ignore_permissions=True) - - frappe.db.commit() diff --git a/custom_ui/models/__init__.py b/custom_ui/models/__init__.py deleted file mode 100644 index c0134af..0000000 --- a/custom_ui/models/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .payments import PaymentData \ No newline at end of file diff --git a/custom_ui/models/payments.py b/custom_ui/models/payments.py deleted file mode 100644 index dfd9bf4..0000000 --- a/custom_ui/models/payments.py +++ /dev/null @@ -1,10 +0,0 @@ -from dataclasses import dataclass - -@dataclass -class PaymentData: - mode_of_payment: str - reference_no: str - reference_date: str - received_amount: float - company: str = None - reference_doc_name: str = None \ No newline at end of file diff --git a/custom_ui/services/__init__.py b/custom_ui/services/__init__.py index d5fafc8..a1d4631 100644 --- a/custom_ui/services/__init__.py +++ b/custom_ui/services/__init__.py @@ -6,6 +6,4 @@ from .estimate_service import EstimateService from .onsite_meeting_service import OnSiteMeetingService from .task_service import TaskService from .service_appointment_service import ServiceAppointmentService -from .stripe_service import StripeService -from .payment_service import PaymentService -from .item_service import ItemService \ No newline at end of file +from .stripe_service import StripeService \ No newline at end of file diff --git a/custom_ui/services/estimate_service.py b/custom_ui/services/estimate_service.py index ea05a8a..d5e3cf2 100644 --- a/custom_ui/services/estimate_service.py +++ b/custom_ui/services/estimate_service.py @@ -1,5 +1,4 @@ import frappe -from .item_service import ItemService class EstimateService: @@ -94,18 +93,4 @@ class EstimateService: estimate_doc.customer = customer_name estimate_doc.save(ignore_permissions=True) print(f"DEBUG: Linked Quotation {estimate_doc.name} to {customer_type} {customer_name}") - - @staticmethod - def map_project_template_to_filter(project_template: str = None) -> dict | None: - """Map a project template to a filter.""" - print(f"DEBUG: Mapping project template {project_template} to quotation category") - if not project_template: - print("DEBUG: No project template provided, defaulting to 'General'") - return None - mapping = { - # SNW Install is both Irrigation and SNW-S categories - "SNW Install": ["in", ["Irrigation", "SNW-S", "Landscaping"]], - } - category = mapping.get(project_template, "General") - print(f"DEBUG: Mapped to quotation category: {category}") - return { "item_group": category } \ No newline at end of file + \ No newline at end of file diff --git a/custom_ui/services/item_service.py b/custom_ui/services/item_service.py deleted file mode 100644 index 4e4e3de..0000000 --- a/custom_ui/services/item_service.py +++ /dev/null @@ -1,38 +0,0 @@ -import frappe - -class ItemService: - - @staticmethod - def get_item_category(item_code: str) -> str: - """Retrieve the category of an Item document by item code.""" - print(f"DEBUG: Getting category for Item {item_code}") - category = frappe.db.get_value("Item", item_code, "item_group") - print(f"DEBUG: Retrieved category: {category}") - return category - - @staticmethod - def get_full_dict(item_code: str) -> frappe._dict: - """Retrieve the full Item document by item code.""" - print(f"DEBUG: Getting full document for Item {item_code}") - item_doc = frappe.get_doc("Item", item_code).as_dict() - item_doc["bom"] = ItemService.get_full_bom_dict(item_code) if item_doc.get("default_bom") else None - return item_doc - - @staticmethod - def get_full_bom_dict(item_code: str): - """Retrieve the Bill of Materials (BOM) associated with an Item.""" - print(f"DEBUG: Getting BOM for Item {item_code}") - bom_name = frappe.db.get_value("BOM", {"item": item_code, "is_active": 1}, "name") - bom_dict = frappe.get_doc("BOM", bom_name).as_dict() - for item in bom_dict.get('exploded_items', []): - item_bom_name = frappe.get_value("Item", item["item_name"], "default_bom") - item["bom"] = frappe.get_doc("BOM", item_bom_name).as_dict() if item_bom_name else None - return bom_dict - - @staticmethod - def exists(item_code: str) -> bool: - """Check if an Item document exists by item code.""" - print(f"DEBUG: Checking existence of Item {item_code}") - exists = frappe.db.exists("Item", item_code) is not None - print(f"DEBUG: Item {item_code} exists: {exists}") - return exists \ No newline at end of file diff --git a/custom_ui/services/payment_service.py b/custom_ui/services/payment_service.py index db84f18..ca447f1 100644 --- a/custom_ui/services/payment_service.py +++ b/custom_ui/services/payment_service.py @@ -1,51 +1,29 @@ import frappe -from custom_ui.services import DbService, StripeService -from dataclasses import dataclass -from custom_ui.models import PaymentData - - +from custom_ui.services import DbService class PaymentService: @staticmethod - def create_payment_entry(data: PaymentData) -> frappe._dict: + def create_payment_entry(reference_doctype: str, reference_doc_name: str, data: dict) -> frappe._dict: """Create a Payment Entry document based on the reference document.""" - print(f"DEBUG: Creating Payment Entry for {data.reference_doc_name} with data: {data}") - reference_doctype = PaymentService.determine_reference_doctype(data.reference_doc_name) - reference_doc = DbService.get_or_throw(reference_doctype, data.reference_doc_name) - account = StripeService.get_stripe_settings(data.company).account + print(f"DEBUG: Creating Payment Entry for {reference_doctype} {reference_doc_name} with data: {data}") + reference_doc = DbService.get_or_throw(reference_doctype, reference_doc_name) pe = frappe.get_doc({ "doctype": "Payment Entry", - "company": data.company, "payment_type": "Receive", "party_type": "Customer", - "mode_of_payment": data.mode_of_payment or "Stripe", + "mode_of_payment": data.get("mode_of_payment", "Stripe"), "party": reference_doc.customer, "party_name": reference_doc.customer, - "paid_to": account, - "reference_no": data.reference_no, - "reference_date": data.reference_date or frappe.utils.nowdate(), - "received_amount": data.received_amount, - "paid_currency": "USD", - "received_currency": "USD", - }).append("references", { - "reference_doctype": reference_doc.doctype, + "paid_to": data.get("paid_to"), + "reference_no": data.get("reference_no"), + "reference_date": data.get("reference_date", frappe.utils.nowdate()), + "reference_doctype": reference_doctype, "reference_name": reference_doc.name, - "reconcile_effect_on": reference_doc.doctype, - "allocated_amount": data.received_amount, + "paid_amount": data.get("paid_amount"), + "paid_currency": data.get("paid_currency"), }) pe.insert() print(f"DEBUG: Created Payment Entry with name: {pe.name}") - return pe - - @staticmethod - def determine_reference_doctype(reference_doc_name: str) -> str: - """Determine the reference doctype based on the document name pattern.""" - print(f"DEBUG: Determining reference doctype for document name: {reference_doc_name}") - if DbService.exists("Sales Order", reference_doc_name): - return "Sales Order" - elif DbService.exists("Sales Invoice", reference_doc_name): - return "Sales Invoice" - else: - frappe.throw("Unable to determine reference doctype from document name.") + return pe.as_dict() \ No newline at end of file diff --git a/custom_ui/services/sales_order_service.py b/custom_ui/services/sales_order_service.py index 0bdeca3..9730618 100644 --- a/custom_ui/services/sales_order_service.py +++ b/custom_ui/services/sales_order_service.py @@ -1,2 +1,7 @@ +import frappe - \ No newline at end of file +class SalesOrderService: + + @staticmethod + def apply_advance_payment(sales_order_name: str, payment_entry_doc): + pass \ No newline at end of file diff --git a/docker-compose.local.yaml b/docker-compose.local.yaml deleted file mode 100644 index b99b3de..0000000 --- a/docker-compose.local.yaml +++ /dev/null @@ -1,8 +0,0 @@ -services: - mailhog: - image: mailhog/mailhog:latest - container_name: mailhog - ports: - - "8025:8025" # MailHog web UI - - "1025:1025" # SMTP server - restart: unless-stopped \ No newline at end of file diff --git a/frontend/src/api.js b/frontend/src/api.js index a1da164..01e1baf 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -229,8 +229,8 @@ class Api { // ESTIMATE / QUOTATION METHODS // ============================================================================ - static async getQuotationItems(projectTemplate) { - return await this.request("custom_ui.api.db.estimates.get_quotation_items", { projectTemplate }); + static async getQuotationItems() { + return await this.request("custom_ui.api.db.estimates.get_quotation_items"); } static async getEstimateFromAddress(fullAddress) { diff --git a/frontend/src/components/common/ItemSelector.vue b/frontend/src/components/common/ItemSelector.vue deleted file mode 100644 index 46ee774..0000000 --- a/frontend/src/components/common/ItemSelector.vue +++ /dev/null @@ -1,145 +0,0 @@ - - - - - diff --git a/frontend/src/components/modals/AddItemModal.vue b/frontend/src/components/modals/AddItemModal.vue deleted file mode 100644 index ae70a8a..0000000 --- a/frontend/src/components/modals/AddItemModal.vue +++ /dev/null @@ -1,526 +0,0 @@ - - - - - diff --git a/frontend/src/components/modals/BidMeetingNoteForm.vue b/frontend/src/components/modals/BidMeetingNoteForm.vue index 113361a..8783a14 100644 --- a/frontend/src/components/modals/BidMeetingNoteForm.vue +++ b/frontend/src/components/modals/BidMeetingNoteForm.vue @@ -20,27 +20,24 @@