diff --git a/westech_r2/__pycache__/crm_import.cpython-312.pyc b/westech_r2/__pycache__/crm_import.cpython-312.pyc new file mode 100644 index 0000000..34b6c8f Binary files /dev/null and b/westech_r2/__pycache__/crm_import.cpython-312.pyc differ diff --git a/westech_r2/__pycache__/crm_import_fix.cpython-312.pyc b/westech_r2/__pycache__/crm_import_fix.cpython-312.pyc new file mode 100644 index 0000000..bafc85c Binary files /dev/null and b/westech_r2/__pycache__/crm_import_fix.cpython-312.pyc differ diff --git a/westech_r2/crm_import.py b/westech_r2/crm_import.py new file mode 100644 index 0000000..1625a3a --- /dev/null +++ b/westech_r2/crm_import.py @@ -0,0 +1,129 @@ +import frappe +import json + +def run(): + print("=== CRM Import Status Check ===") + print("Customer Record DocType exists:", bool(frappe.db.exists("DocType", "Customer Record"))) + + if not frappe.db.exists("DocType", "Customer Record"): + print("Customer Record DocType does not exist! Creating it...") + doctype_def = { + "name": "Customer Record", + "doctype": "DocType", + "module": "Westech R2", + "custom": 0, + "autoname": "field:record_number", + "naming_rule": "By fieldname", + "engine": "InnoDB", + "document_type": "Document", + "fields": [ + {"fieldname": "record_number", "fieldtype": "Data", "label": "Record Number", "reqd": 1, "unique": 1, "in_list_view": 1}, + {"fieldname": "company_name", "fieldtype": "Data", "label": "Company Name", "in_list_view": 1}, + {"fieldname": "supplier", "fieldtype": "Link", "label": "Supplier", "options": "Supplier", "in_list_view": 1}, + {"fieldname": "customer_number", "fieldtype": "Data", "label": "Customer Number", "in_list_view": 1}, + {"fieldname": "contact_persons", "fieldtype": "Text", "label": "Contact Persons"}, + {"fieldname": "email_address", "fieldtype": "Text", "label": "Email Addresses"}, + {"fieldname": "phone_numbers", "fieldtype": "Text", "label": "Phone Numbers"}, + {"fieldname": "customer_address", "fieldtype": "Data", "label": "Address"}, + {"fieldname": "city", "fieldtype": "Data", "label": "City"}, + {"fieldname": "state", "fieldtype": "Data", "label": "State"}, + {"fieldname": "zip", "fieldtype": "Data", "label": "Zip"}, + {"fieldname": "date_created", "fieldtype": "Date", "label": "Date Created"}, + {"fieldname": "contacted_date", "fieldtype": "Date", "label": "Contacted Date"}, + {"fieldname": "follow_up_date", "fieldtype": "Date", "label": "Follow Up Date"}, + {"fieldname": "last_pu_date", "fieldtype": "Date", "label": "Last PU Date"}, + {"fieldname": "notes", "fieldtype": "Text", "label": "Notes"}, + {"fieldname": "comments", "fieldtype": "Text", "label": "Comments"}, + {"fieldname": "hours_operation", "fieldtype": "Data", "label": "Hours of Operation"}, + ], + "permissions": [ + {"role": "System Manager", "read": 1, "write": 1, "create": 1, "delete": 1, "export": 1, "print": 1, "share": 1, "email": 1}, + {"role": "All", "read": 1, "write": 1, "create": 1, "delete": 0, "export": 0, "print": 1, "share": 0, "email": 0} + ], + "quick_entry": 0, + "track_changes": 1, + } + doc = frappe.get_doc(doctype_def) + doc.insert(ignore_permissions=True) + frappe.db.commit() + print("Customer Record DocType created!") + + # Check existing count + existing = frappe.db.count("Customer Record") + print(f"Records in Customer Record: {existing}") + + if existing > 0: + print("Data already imported. Skipping.") + return {"status": "already_done", "count": existing} + + # Load parsed data + with open("/tmp/crm_records.json") as f: + records = json.load(f) + + print(f"Parsed records from xlsx: {len(records)}") + + # Get supplier names + supplier_names = set(frappe.get_all("Supplier", fields=["name"], pluck="name")) + print(f"Total suppliers: {len(supplier_names)}") + + # Count matches + matched = sum(1 for r in records if r["record_number"] in supplier_names) + print(f"Records matching suppliers: {matched}") + + print(f"Importing {len(records)} records...") + created = 0 + errors = [] + + for rec in records: + try: + doc = frappe.new_doc("Customer Record") + doc.record_number = rec["record_number"] + doc.company_name = rec.get("company_name") or "" + + # Match supplier + if rec["record_number"] in supplier_names: + doc.supplier = rec["record_number"] + doc.customer_number = rec["record_number"] + + doc.contact_persons = "\n".join(rec.get("contact_persons", [])) + doc.email_address = "\n".join(rec.get("emails", [])) + doc.phone_numbers = "\n".join(rec.get("phone_numbers", [])) + doc.customer_address = rec.get("address") or "" + doc.city = rec.get("city") or "" + doc.state = rec.get("state") or "" + doc.zip = str(rec.get("zip") or "") + + if rec.get("date_created"): + doc.date_created = rec["date_created"] + if rec.get("contacted_date"): + doc.contacted_date = rec["contacted_date"] + if rec.get("follow_up_date"): + doc.follow_up_date = rec["follow_up_date"] + if rec.get("last_pu_date"): + doc.last_pu_date = rec["last_pu_date"] + + doc.notes = rec.get("notes") or "" + doc.comments = rec.get("comments") or "" + doc.hours_operation = rec.get("hours_operation") or "" + + doc.save(ignore_permissions=True) + created += 1 + + if created % 500 == 0: + print(f" ... {created} records imported") + frappe.db.commit() + + except Exception as e: + errors.append(f"{rec['record_number']}: {str(e)}") + if len(errors) > 10: + print(f"Too many errors, stopping. Last error: {e}") + break + + frappe.db.commit() + print(f"\nImport complete: {created} records created") + if errors: + print(f"Errors ({len(errors)}):") + for e in errors[:10]: + print(f" {e}") + + return {"status": "ok", "created": created, "errors": len(errors), "matched": matched} diff --git a/westech_r2/crm_import_fix.py b/westech_r2/crm_import_fix.py new file mode 100644 index 0000000..ebe7a2a --- /dev/null +++ b/westech_r2/crm_import_fix.py @@ -0,0 +1,142 @@ +import frappe +import json +from datetime import datetime + +def parse_date(val): + """Parse various date formats from the CRM spreadsheet""" + if not val: + return None + if isinstance(val, datetime): + return val.date() + if isinstance(val, str): + val = val.strip() + if not val or val.lower() in ['nan', 'none', 'null']: + return None + # Handle format like "9/8/0206" -> year 2006 + if '/' in val: + parts = val.split('/') + if len(parts) == 3: + month, day, year = parts + year = year.strip() + # Fix 4-digit years starting with 02xx -> 20xx + if len(year) == 4 and year.startswith('02'): + year = '20' + year[2:] + # Fix 2-digit years + elif len(year) == 2: + year_int = int(year) + if year_int < 50: + year = '20' + year + else: + year = '19' + year + try: + return datetime(int(year), int(month), int(day)).date() + except: + return None + return None + +def run(): + print("=== CRM Import Fix & Continue ===") + + # Load parsed data + with open("/tmp/crm_records.json") as f: + records = json.load(f) + + print(f"Total parsed records: {len(records)}") + + # Get already imported records + existing = frappe.get_all("Customer Record", fields=["name"], pluck="name") + existing_set = set(existing) + print(f"Already imported: {len(existing_set)}") + + # Get supplier names + supplier_names = set(frappe.get_all("Supplier", fields=["name"], pluck="name")) + + # Fix existing bad records + print("\nFixing existing records with bad dates...") + fixed = 0 + for rec_num in existing_set: + doc = frappe.get_doc("Customer Record", rec_num) + changed = False + + # Check and fix dates + date_fields = ['date_created', 'contacted_date', 'follow_up_date', 'last_pu_date'] + for df in date_fields: + val = doc.get(df) + if val and isinstance(val, str): + parsed = parse_date(val) + if parsed: + doc.set(df, parsed) + changed = True + else: + # Can't parse, clear it + doc.set(df, None) + changed = True + + if changed: + doc.save(ignore_permissions=True) + fixed += 1 + + if fixed: + frappe.db.commit() + print(f"Fixed {fixed} existing records") + + # Import remaining records + remaining = [r for r in records if r["record_number"] not in existing_set] + print(f"\nImporting remaining {len(remaining)} records...") + + created = 0 + errors = [] + + for rec in remaining: + try: + doc = frappe.new_doc("Customer Record") + doc.record_number = rec["record_number"] + doc.company_name = rec.get("company_name") or "" + + # Match supplier + if rec["record_number"] in supplier_names: + doc.supplier = rec["record_number"] + doc.customer_number = rec["record_number"] + + doc.contact_persons = "\n".join(rec.get("contact_persons", [])) + doc.email_address = "\n".join(rec.get("emails", [])) + doc.phone_numbers = "\n".join(rec.get("phone_numbers", [])) + doc.customer_address = rec.get("address") or "" + doc.city = rec.get("city") or "" + doc.state = rec.get("state") or "" + doc.zip = str(rec.get("zip") or "") + + # Parse dates properly + doc.date_created = parse_date(rec.get("date_created")) + doc.contacted_date = parse_date(rec.get("contacted_date")) + doc.follow_up_date = parse_date(rec.get("follow_up_date")) + doc.last_pu_date = parse_date(rec.get("last_pu_date")) + + doc.notes = rec.get("notes") or "" + doc.comments = rec.get("comments") or "" + doc.hours_operation = rec.get("hours_operation") or "" + + doc.save(ignore_permissions=True) + created += 1 + + if created % 500 == 0: + print(f" ... {created} remaining records imported") + frappe.db.commit() + + except Exception as e: + errors.append(f"{rec['record_number']}: {str(e)}") + if len(errors) > 20: + print(f"Too many errors ({len(errors)}), stopping. Last: {e}") + break + + frappe.db.commit() + print(f"\nImport complete: {created} additional records created") + if errors: + print(f"Errors ({len(errors)}):") + for e in errors[:10]: + print(f" {e}") + + total = frappe.db.count("Customer Record") + print(f"Total Customer Records now: {total}") + + return {"status": "ok", "created": created, "errors": len(errors), "total": total} diff --git a/westech_r2/westech_r2/doctype/__init__.py b/westech_r2/westech_r2/doctype/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/westech_r2/westech_r2/doctype/__pycache__/__init__.cpython-312.pyc b/westech_r2/westech_r2/doctype/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..d2f66bb Binary files /dev/null and b/westech_r2/westech_r2/doctype/__pycache__/__init__.cpython-312.pyc differ diff --git a/westech_r2/westech_r2/doctype/customer_record/__init__.py b/westech_r2/westech_r2/doctype/customer_record/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/westech_r2/westech_r2/doctype/customer_record/__pycache__/__init__.cpython-312.pyc b/westech_r2/westech_r2/doctype/customer_record/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..00f58a2 Binary files /dev/null and b/westech_r2/westech_r2/doctype/customer_record/__pycache__/__init__.cpython-312.pyc differ diff --git a/westech_r2/westech_r2/doctype/customer_record/__pycache__/customer_record.cpython-312.pyc b/westech_r2/westech_r2/doctype/customer_record/__pycache__/customer_record.cpython-312.pyc new file mode 100644 index 0000000..241c131 Binary files /dev/null and b/westech_r2/westech_r2/doctype/customer_record/__pycache__/customer_record.cpython-312.pyc differ diff --git a/westech_r2/westech_r2/doctype/customer_record/customer_record.js b/westech_r2/westech_r2/doctype/customer_record/customer_record.js new file mode 100644 index 0000000..e9c44e0 --- /dev/null +++ b/westech_r2/westech_r2/doctype/customer_record/customer_record.js @@ -0,0 +1,8 @@ +// Copyright (c) 2026, Westech and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Customer Record", { +// refresh(frm) { + +// }, +// }); diff --git a/westech_r2/westech_r2/doctype/customer_record/customer_record.json b/westech_r2/westech_r2/doctype/customer_record/customer_record.json new file mode 100644 index 0000000..9141732 --- /dev/null +++ b/westech_r2/westech_r2/doctype/customer_record/customer_record.json @@ -0,0 +1,158 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:record_number", + "creation": "2026-05-20 15:10:58.374067", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "record_number", + "company_name", + "supplier", + "customer_number", + "contact_persons", + "email_address", + "phone_numbers", + "customer_address", + "city", + "state", + "zip", + "date_created", + "contacted_date", + "follow_up_date", + "last_pu_date", + "notes", + "comments", + "hours_operation" + ], + "fields": [ + { + "fieldname": "record_number", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Record Number", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "company_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Company Name" + }, + { + "fieldname": "supplier", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Supplier", + "options": "Supplier" + }, + { + "fieldname": "customer_number", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Customer Number" + }, + { + "fieldname": "contact_persons", + "fieldtype": "Text", + "label": "Contact Persons" + }, + { + "fieldname": "email_address", + "fieldtype": "Text", + "label": "Email Addresses" + }, + { + "fieldname": "phone_numbers", + "fieldtype": "Text", + "label": "Phone Numbers" + }, + { + "fieldname": "customer_address", + "fieldtype": "Data", + "label": "Address" + }, + { + "fieldname": "city", + "fieldtype": "Data", + "label": "City" + }, + { + "fieldname": "state", + "fieldtype": "Data", + "label": "State" + }, + { + "fieldname": "zip", + "fieldtype": "Data", + "label": "Zip" + }, + { + "fieldname": "date_created", + "fieldtype": "Date", + "label": "Date Created" + }, + { + "fieldname": "contacted_date", + "fieldtype": "Date", + "label": "Contacted Date" + }, + { + "fieldname": "follow_up_date", + "fieldtype": "Date", + "label": "Follow Up Date" + }, + { + "fieldname": "last_pu_date", + "fieldtype": "Date", + "label": "Last PU Date" + }, + { + "fieldname": "notes", + "fieldtype": "Text", + "label": "Notes" + }, + { + "fieldname": "comments", + "fieldtype": "Text", + "label": "Comments" + }, + { + "fieldname": "hours_operation", + "fieldtype": "Data", + "label": "Hours of Operation" + } + ], + "grid_page_length": 50, + "index_web_pages_for_search": 1, + "links": [], + "modified": "2026-05-20 15:10:58.374067", + "modified_by": "Administrator", + "module": "Westech R2", + "name": "Customer Record", + "naming_rule": "By fieldname", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "row_format": "Dynamic", + "rows_threshold_for_grid_search": 20, + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} \ No newline at end of file diff --git a/westech_r2/westech_r2/doctype/customer_record/customer_record.py b/westech_r2/westech_r2/doctype/customer_record/customer_record.py new file mode 100644 index 0000000..d1bb1ea --- /dev/null +++ b/westech_r2/westech_r2/doctype/customer_record/customer_record.py @@ -0,0 +1,9 @@ +# Copyright (c) 2026, Westech and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class CustomerRecord(Document): + pass diff --git a/westech_r2/westech_r2/doctype/customer_record/test_customer_record.py b/westech_r2/westech_r2/doctype/customer_record/test_customer_record.py new file mode 100644 index 0000000..6f327c6 --- /dev/null +++ b/westech_r2/westech_r2/doctype/customer_record/test_customer_record.py @@ -0,0 +1,9 @@ +# Copyright (c) 2026, Westech and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestCustomerRecord(FrappeTestCase): + pass