"Shipping Charges - <abbr>": Selling, Fixed, posting to a "Shipping Charges" income account under Direct Income (created if the chart lacks it) with the default cost center. Shipping Rule is named by its label, so the company abbreviation keeps one rule per company, as ERPNext names its tax templates. Created on Company save, after install, and by a patch for existing companies. The amount is entered on the transaction, not the rule: ERPNext re-applies the rule on every recalculation and would reset the charge row to the rule's fixed amount. ThaiShippingRule overrides the doctype class so a Fixed rule with no amount only seeds the row and leaves the entered amount alone.
67 lines
2.4 KiB
Python
67 lines
2.4 KiB
Python
import erpnext
|
|
import frappe
|
|
from erpnext.accounts.doctype.shipping_rule.shipping_rule import ShippingRule
|
|
|
|
from default_thai_company.tax_withholding import company_ready, get_or_create_account, thai_companies
|
|
|
|
# Shipping Rule is named by its label (site-wide unique) but bound to one company,
|
|
# so each Thai company gets "<LABEL> - <abbr>", the way ERPNext names accounts and
|
|
# tax templates. The label doubles as the charge description on the transaction.
|
|
LABEL = "Shipping Charges"
|
|
|
|
# Shipping billed to customers is revenue; the courier's bill stays an expense.
|
|
SHIPPING_ACCOUNT = "Shipping Charges"
|
|
SHIPPING_ACCOUNT_GROUPS = ("Direct Income", "Income")
|
|
|
|
|
|
class ThaiShippingRule(ShippingRule):
|
|
def add_shipping_rule_to_tax_table(self, doc, shipping_amount):
|
|
"""A Fixed rule without an amount only seeds the charge row; the amount is
|
|
entered on the transaction. ERPNext re-applies the rule on every
|
|
recalculation, which would otherwise reset the row to 0."""
|
|
manual = self.calculate_based_on == "Fixed" and not self.shipping_amount
|
|
entered = [(row, row.tax_amount) for row in doc.get("taxes")] if manual else ()
|
|
super().add_shipping_rule_to_tax_table(doc, shipping_amount)
|
|
for row, amount in entered:
|
|
row.tax_amount = amount
|
|
|
|
|
|
def shipping_rule_name(company):
|
|
abbr = frappe.get_cached_value("Company", company, "abbr")
|
|
return f"{LABEL} - {abbr}"
|
|
|
|
|
|
def ensure_shipping_rule(company):
|
|
"""Selling Shipping Rule for `company` with the amount entered per
|
|
transaction (see ThaiShippingRule). No-op when the rule exists."""
|
|
name = shipping_rule_name(company)
|
|
if frappe.db.exists("Shipping Rule", name):
|
|
return
|
|
frappe.get_doc(
|
|
{
|
|
"doctype": "Shipping Rule",
|
|
"label": name,
|
|
"company": company,
|
|
"shipping_rule_type": "Selling",
|
|
"calculate_based_on": "Fixed",
|
|
"shipping_amount": 0,
|
|
"account": get_or_create_account(
|
|
company, SHIPPING_ACCOUNT, "Income", "Income Account", SHIPPING_ACCOUNT_GROUPS
|
|
),
|
|
"cost_center": erpnext.get_default_cost_center(company),
|
|
}
|
|
).insert(ignore_permissions=True)
|
|
|
|
|
|
def setup_company(doc, method=None):
|
|
"""Company.on_update: default Shipping Rule for a Thai company."""
|
|
if company_ready(doc):
|
|
ensure_shipping_rule(doc.name)
|
|
|
|
|
|
def setup_companies():
|
|
"""after_install and the create_shipping_rules patch: companies that exist
|
|
before this code did never pass through `setup_company`."""
|
|
for company in thai_companies():
|
|
ensure_shipping_rule(company)
|