feat: Customer Records page + CRM data import (7,966 records)
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -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}
|
||||
@@ -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}
|
||||
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
@@ -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) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user