From 77b86851a0ceae638701cdd75b7643fe1d87f1c7 Mon Sep 17 00:00:00 2001 From: Westech Admin Date: Sun, 17 May 2026 21:05:56 +0000 Subject: [PATCH] feat: complete R2 workflow - QA, pricing, sales --- westech_r2/api/__init__.py | 2 + westech_r2/api/qa.py | 35 +++++++++++++-- westech_r2/api/sales.py | 92 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 westech_r2/api/sales.py diff --git a/westech_r2/api/__init__.py b/westech_r2/api/__init__.py index e69de29..9219fdc 100644 --- a/westech_r2/api/__init__.py +++ b/westech_r2/api/__init__.py @@ -0,0 +1,2 @@ + +from westech_r2.api import sales diff --git a/westech_r2/api/qa.py b/westech_r2/api/qa.py index 3f1b2d4..39d68cb 100644 --- a/westech_r2/api/qa.py +++ b/westech_r2/api/qa.py @@ -3,9 +3,9 @@ from frappe import _ @frappe.whitelist() def get_qa_ready_serials(limit=50): - """Get Serial Nos ready for QA (Erasure Complete state).""" + """Get Serial Nos ready for QA.""" return frappe.get_all('Serial No', - filters={'r2_status': 'Erasure Complete'}, + filters={'r2_status': 'Needs QA'}, fields=['name', 'item_code', 'item_name', 'pallet', 'cosmetic_grade'], limit=limit, order_by='creation asc' @@ -19,11 +19,10 @@ def create_qa_from_serial(serial_no): serial = frappe.get_doc('Serial No', serial_no) - # Check if already has active inspection existing = frappe.db.get_value('R2 Device Inspection', {'serial_no': serial_no, 'docstatus': ['!=', 2]}, 'name') if existing: - return {'error': f'Inspection {existing} already exists for this serial'} + return {'error': f'Inspection {existing} already exists'} insp = frappe.get_doc({ 'doctype': 'R2 Device Inspection', @@ -39,3 +38,31 @@ def create_qa_from_serial(serial_no): insp.insert(ignore_permissions=True) return {'success': True, 'inspection': insp.name} + +@frappe.whitelist() +def auto_grade(serial_no, grade='C5'): + """Auto-grade a device and move to Priced state.""" + serial = frappe.get_doc('Serial No', serial_no) + serial.cosmetic_grade = grade + serial.r2_status = 'Graded' + serial.save(ignore_permissions=True) + + # Apply flat pricing + item = frappe.db.get_value('Item', serial.item_code, 'item_group') + flat_prices = { + 'Laptops': {'c3': 250, 'c4': 200, 'c5': 150, 'c6': 100, 'c7': 60, 'c8': 30, 'c9': 15}, + 'Desktops': {'c3': 180, 'c4': 150, 'c5': 120, 'c6': 80, 'c7': 50, 'c8': 25, 'c9': 10}, + 'Tablets': {'c3': 120, 'c4': 100, 'c5': 80, 'c6': 50, 'c7': 30, 'c8': 15, 'c9': 8}, + 'Phones': {'c3': 100, 'c4': 80, 'c5': 60, 'c6': 40, 'c7': 25, 'c8': 12, 'c9': 5} + } + prices = flat_prices.get(item, flat_prices['Laptops']) + price = prices.get(grade.lower(), 100) + + serial.suggested_price = price + serial.assigned_price = price + serial.pricing_status = 'Priced' + serial.price_point = grade + serial.r2_status = 'Priced' + serial.save(ignore_permissions=True) + + return {'success': True, 'price': price} diff --git a/westech_r2/api/sales.py b/westech_r2/api/sales.py new file mode 100644 index 0000000..369502b --- /dev/null +++ b/westech_r2/api/sales.py @@ -0,0 +1,92 @@ +import frappe +from frappe import _ + +@frappe.whitelist() +def quick_sell(serial_no, customer=None, payment_method='Cash'): + """Create Sales Invoice for quick cash sale.""" + try: + serial = frappe.get_doc('Serial No', serial_no) + + if serial.r2_status != 'Ready for Sale': + return {'error': 'Device must be Ready for Sale'} + + # Use default customer if none provided + if not customer: + customer = frappe.db.get_value('Customer', {}, 'name', order_by='creation asc') + if not customer: + # Create walk-in customer + customer = frappe.get_doc({ + 'doctype': 'Customer', + 'customer_name': 'Walk-in Customer', + 'customer_type': 'Individual' + }).insert(ignore_permissions=True).name + + # Create Sales Invoice + price = serial.assigned_price or serial.suggested_price or 0 + + invoice = frappe.get_doc({ + 'doctype': 'Sales Invoice', + 'customer': customer, + 'serial_no': serial_no, + 'device_condition': f"Cosmetic: {serial.cosmetic_grade or 'N/A'}", + 'posting_date': frappe.utils.today(), + 'due_date': frappe.utils.today(), + 'items': [{ + 'item_code': serial.item_code, + 'qty': 1, + 'rate': price, + 'amount': price, + 'serial_no': serial_no + }], + 'payments': [{ + 'mode_of_payment': payment_method, + 'amount': price + }] + }) + invoice.insert(ignore_permissions=True) + invoice.submit() + + # Update Serial No + serial.r2_status = 'Sold' + serial.status = 'Delivered' + serial.customer = customer + serial.save(ignore_permissions=True) + + return { + 'success': True, + 'invoice': invoice.name, + 'customer': customer, + 'amount': price + } + + except Exception as e: + frappe.log_error(f"Quick sell failed: {str(e)}", "Sales") + return {'error': str(e)} + +@frappe.whitelist() +def create_sales_order(quotation_name): + """Create Sales Order from Quotation.""" + try: + from erpnext.selling.doctype.quotation.quotation import make_sales_order + + so = make_sales_order(quotation_name) + so.insert(ignore_permissions=True) + so.submit() + + return {'success': True, 'sales_order': so.name} + except Exception as e: + return {'error': str(e)} + +@frappe.whitelist() +def create_delivery_note(sales_order_name): + """Create Delivery Note from Sales Order.""" + try: + from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note + + dn = make_delivery_note(sales_order_name) + dn.insert(ignore_permissions=True) + dn.submit() + + return {'success': True, 'delivery_note': dn.name} + except Exception as e: + return {'error': str(e)}