Jump to content

Repo comfac-webshop ExtensionPoints

From Game in the Brain Wiki

Repo:comfac-webshop/ExtensionPoints — Extension and Customization Points

The Safe Edit Zone

Frappe Override Pattern

Like comfac-hrms, webshop uses the "override instead of modify" pattern:

Location What to Put There
webshop/webshop/doctype/override_doctype/ Override ERPNext classes
webshop/webshop/crud_events/ Event handlers for sync logic
webshop/public/js/ Custom client-side JavaScript
webshop/templates/ Custom Jinja templates
webshop/www/ New web pages or route overrides
Custom Fields (via UI) Non-core fields

Files You Should NOT Edit Directly

  • ERPNext core (Item, Quotation, etc.)
  • Frappe framework
  • Third-party libraries

Hooks & Events

Current Hooks in webshop/hooks.py

Hook Type Purpose Current Usage
after_install Post-install setup Create default Webshop Settings
on_logout Cleanup Clear cart count cookie
on_session_creation Initialize Update debtors account, set cart count
update_website_context Template data Add cart info to all pages
website_generators Auto-routes Website Item, Item Group pages
override_doctype_class Class overrides Item, Item Group, Payment Request
doctype_js Client scripts Item, Homepage forms
doc_events Server events Item sync, Quotation validation
has_website_permission Access control Website Item, Item Group visibility

Document Event Hooks

DocType Event Handler Purpose
Item on_update crud_events.item.update_website_item Sync to Website Item
Item on_update crud_events.item.invalidate_item_variants_cache Clear cache
Item before_rename crud_events.item.validate_duplicate_website_item Prevent conflicts
Item after_rename crud_events.item.invalidate_item_variants_cache Clear cache
Sales Taxes Template on_update webshop_settings.validate_cart_settings Validate config
Quotation validate crud_events.quotation.validate_shopping_cart_items Cart validation
Price List validate crud_events.price_list.check_impact_on_cart Warn users
Tax Rule validate crud_events.tax_rule.validate_use_for_cart Compatibility check

Session Hooks

Hook When Fired Handler
on_session_creation User logs in Update debtors account, set cart count
on_logout User logs out Clear cart count cookie

Override Patterns

Pattern 1: Override ERPNext DocType

Current: Item override in webshop/webshop/doctype/override_doctype/item.py

from erpnext.stock.doctype.item.item import Item

class WebshopItem(Item):
    def on_update(self):
        super().on_update()  # Call parent
        # Custom: Auto-create Website Item
        self.create_website_item()
    
    def create_website_item(self):
        if not frappe.db.exists("Website Item", {"item_code": self.name}):
            # Create logic here
            pass

Registration in hooks.py:

override_doctype_class = {
    "Item": "webshop.webshop.doctype.override_doctype.item.WebshopItem",
}

Pattern 2: Add Custom Cart Validation

Goal: Minimum order amount check

Steps:

  1. Edit webshop/webshop/crud_events/quotation.py:
def validate_shopping_cart_items(doc, method=None):
    """Existing validation"""
    # ... existing code ...
    
    # Add custom validation
    if doc.order_type == "Shopping Cart":
        minimum_order = 1000  # PHP
        if doc.total < minimum_order:
            frappe.throw(_(
                f"Minimum order amount is {minimum_order} PHP"
            ))
  1. Already registered in hooks.py:
doc_events = {
    "Quotation": {
        "validate": ["webshop.webshop.crud_events.quotation.validate_shopping_cart_items"]
    }
}

Pattern 3: Custom Product Filter

Goal: Add "New Arrivals" filter

Create: webshop/webshop/product_data_engine/filters.py

def apply_new_arrivals_filter(query, filters):
    """Show items created in last 30 days"""
    from datetime import datetime, timedelta
    
    thirty_days_ago = datetime.now() - timedelta(days=30)
    query = query.where(
        frappe.qb.DocType("Website Item").creation >= thirty_days_ago
    )
    return query

Use in template:

Add to product listing template to call custom filter.

Pattern 4: Custom Payment Gateway Integration

Goal: Add Philippines-specific payment method (e.g., GCash)

Override Payment Request:

Edit webshop/webshop/doctype/override_doctype/payment_request.py:

from erpnext.accounts.doctype.payment_request.payment_request import PaymentRequest

class WebshopPaymentRequest(PaymentRequest):
    def on_submit(self):
        super().on_submit()
        
        if self.payment_gateway == "GCash":
            # Generate GCash payment URL
            self.payment_url = generate_gcash_url(self)
    
    def get_payment_url(self):
        if self.payment_gateway == "GCash":
            return self.payment_url
        return super().get_payment_url()

Adding New Web Pages

Create a New Page: Order Tracking

Step 1: Create controller webshop/www/track-order/index.py:

import frappe
from frappe import _

def get_context(context):
    context.no_cache = 1
    
    order_id = frappe.form_dict.get('order')
    if order_id:
        context.order = frappe.get_doc("Sales Order", order_id)
        context.tracking_info = get_tracking_info(order_id)
    
    return context

def get_tracking_info(order_id):
    # Custom tracking logic
    pass

Step 2: Create template webshop/www/track-order/index.html:

{% extends "templates/web_base.html" %}

{% block page_content %}
<div class="container">
    <h1>Track Your Order</h1>
    {% if order %}
        <div class="order-details">
            <h3>Order #{{ order.name }}</h3>
            <p>Status: {{ order.status }}</p>
            <!-- Tracking display -->
        </div>
    {% else %}
        <form method="GET">
            <input type="text" name="order" placeholder="Enter Order Number">
            <button type="submit">Track</button>
        </form>
    {% endif %}
</div>
{% endblock %}

Step 3: Access at /track-order

Custom Fields Best Practice

Approach When to Use How
Custom Field (UI) Quick additions Desk → Customize Form
Fixtures Version-controlled fields Add to hooks.py fixtures
DocType JSON edit Core app changes Edit .json, migrate

Export Custom Fields as Fixtures

In webshop/hooks.py:

fixtures = [
    {
        "dt": "Custom Field",
        "filters": [["module", "=", "Webshop"]]
    }
]

Then run: bench export-fixtures

Template Overrides

Override Product Card

Create: webshop/templates/webshop/item_card.html

Copy original from Frappe and modify:

<!-- Custom item card with ComFac branding -->
<div class="card product-card">
    <a href="{{ item.route }}">
        <img src="{{ item.website_image or '/assets/webshop/images/fallback.png' }}" 
             alt="{{ item.web_item_name }}">
        <div class="card-body">
            <h5>{{ item.web_item_name }}</h5>
            <p class="price">₱{{ item.price }}</p>
            <!-- Custom badge -->
            {% if item.on_backorder %}
                <span class="badge badge-warning">Backorder</span>
            {% endif %}
        </div>
    </a>
</div>

JavaScript Customization

Add Custom Cart Behavior

Edit webshop/public/js/cart.js:

// Add to existing file or create new
frappe.ready(function() {
    // Custom: Show confirmation modal for expensive items
    $('.add-to-cart').on('click', function(e) {
        var price = $(this).data('price');
        if (price > 50000) {  // PHP 50,000
            if (!confirm('This item costs ₱' + price + '. Add to cart?')) {
                e.preventDefault();
                return false;
            }
        }
    });
});

Register in webshop/hooks.py:

web_include_js = [
    "web.bundle.js",
    "webshop/js/cart.js"  # Add custom file
]