Jump to content

Repo comfac-webshop ExtensionPoints: Difference between revisions

From Game in the Brain Wiki
"Repo analysis: comfac-hrms and comfac-webshop (14 pages)"
 
"Fix markdown code blocks → wikitext syntaxhighlight in Repo analysis pages"
 
Line 93: Line 93:
'''Current:''' Item override in <code>webshop/webshop/doctype/override_doctype/item.py</code>
'''Current:''' Item override in <code>webshop/webshop/doctype/override_doctype/item.py</code>


```python
<syntaxhighlight lang="python">
from erpnext.stock.doctype.item.item import Item
from erpnext.stock.doctype.item.item import Item


Line 106: Line 106:
             # Create logic here
             # Create logic here
             pass
             pass
```
</syntaxhighlight>


'''Registration in hooks.py:'''
'''Registration in hooks.py:'''


```python
<syntaxhighlight lang="python">
override_doctype_class = {
override_doctype_class = {
     "Item": "webshop.webshop.doctype.override_doctype.item.WebshopItem",
     "Item": "webshop.webshop.doctype.override_doctype.item.WebshopItem",
}
}
```
</syntaxhighlight>


=== Pattern 2: Add Custom Cart Validation ===
=== Pattern 2: Add Custom Cart Validation ===
Line 124: Line 124:
# Edit <code>webshop/webshop/crud_events/quotation.py</code>:
# Edit <code>webshop/webshop/crud_events/quotation.py</code>:


```python
<syntaxhighlight lang="python">
def validate_shopping_cart_items(doc, method=None):
def validate_shopping_cart_items(doc, method=None):
     """Existing validation"""
     """Existing validation"""
Line 136: Line 136:
                 f"Minimum order amount is {minimum_order} PHP"
                 f"Minimum order amount is {minimum_order} PHP"
             ))
             ))
```
</syntaxhighlight>


# Already registered in hooks.py:
# Already registered in hooks.py:


```python
<syntaxhighlight lang="python">
doc_events = {
doc_events = {
     "Quotation": {
     "Quotation": {
Line 146: Line 146:
     }
     }
}
}
```
</syntaxhighlight>


=== Pattern 3: Custom Product Filter ===
=== Pattern 3: Custom Product Filter ===
Line 154: Line 154:
'''Create:''' <code>webshop/webshop/product_data_engine/filters.py</code>
'''Create:''' <code>webshop/webshop/product_data_engine/filters.py</code>


```python
<syntaxhighlight lang="python">
def apply_new_arrivals_filter(query, filters):
def apply_new_arrivals_filter(query, filters):
     """Show items created in last 30 days"""
     """Show items created in last 30 days"""
Line 164: Line 164:
     )
     )
     return query
     return query
```
</syntaxhighlight>


'''Use in template:'''
'''Use in template:'''
Line 178: Line 178:
Edit <code>webshop/webshop/doctype/override_doctype/payment_request.py</code>:
Edit <code>webshop/webshop/doctype/override_doctype/payment_request.py</code>:


```python
<syntaxhighlight lang="python">
from erpnext.accounts.doctype.payment_request.payment_request import PaymentRequest
from erpnext.accounts.doctype.payment_request.payment_request import PaymentRequest


Line 193: Line 193:
             return self.payment_url
             return self.payment_url
         return super().get_payment_url()
         return super().get_payment_url()
```
</syntaxhighlight>


== Adding New Web Pages ==
== Adding New Web Pages ==
Line 201: Line 201:
'''Step 1: Create controller''' <code>webshop/www/track-order/index.py</code>:
'''Step 1: Create controller''' <code>webshop/www/track-order/index.py</code>:


```python
<syntaxhighlight lang="python">
import frappe
import frappe
from frappe import _
from frappe import _
Line 218: Line 218:
     # Custom tracking logic
     # Custom tracking logic
     pass
     pass
```
</syntaxhighlight>


'''Step 2: Create template''' <code>webshop/www/track-order/index.html</code>:
'''Step 2: Create template''' <code>webshop/www/track-order/index.html</code>:


```html
<syntaxhighlight lang="html">
{% extends "templates/web_base.html" %}
{% extends "templates/web_base.html" %}


Line 242: Line 242:
</div>
</div>
{% endblock %}
{% endblock %}
```
</syntaxhighlight>


'''Step 3: Access at''' <code>/track-order</code>
'''Step 3: Access at''' <code>/track-order</code>
Line 262: Line 262:
In <code>webshop/hooks.py</code>:
In <code>webshop/hooks.py</code>:


```python
<syntaxhighlight lang="python">
fixtures = [
fixtures = [
     {
     {
Line 269: Line 269:
     }
     }
]
]
```
</syntaxhighlight>


Then run: <code>bench export-fixtures</code>
Then run: <code>bench export-fixtures</code>
Line 281: Line 281:
Copy original from Frappe and modify:
Copy original from Frappe and modify:


```html
<syntaxhighlight lang="html">
<!-- Custom item card with ComFac branding -->
<!-- Custom item card with ComFac branding -->
<div class="card product-card">
<div class="card product-card">
Line 297: Line 297:
     </a>
     </a>
</div>
</div>
```
</syntaxhighlight>


== JavaScript Customization ==
== JavaScript Customization ==
Line 305: Line 305:
Edit <code>webshop/public/js/cart.js</code>:
Edit <code>webshop/public/js/cart.js</code>:


```javascript
<syntaxhighlight lang="javascript">
// Add to existing file or create new
// Add to existing file or create new
frappe.ready(function() {
frappe.ready(function() {
Line 319: Line 319:
     });
     });
});
});
```
</syntaxhighlight>


Register in <code>webshop/hooks.py</code>:
Register in <code>webshop/hooks.py</code>:


```python
<syntaxhighlight lang="python">
web_include_js = [
web_include_js = [
     "web.bundle.js",
     "web.bundle.js",
     "webshop/js/cart.js"  # Add custom file
     "webshop/js/cart.js"  # Add custom file
]
]
```
</syntaxhighlight>


== Related Pages ==
== Related Pages ==

Latest revision as of 16:40, 9 March 2026

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
]