Compare commits
4
Commits
4785878025
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68f0817125 | ||
|
|
4a19c60ba2 | ||
|
|
71eda0bec9 | ||
|
|
8cba58ba02 |
File diff suppressed because one or more lines are too long
@@ -139,8 +139,12 @@ fixtures = [
|
||||
# ------------
|
||||
|
||||
# before_install = "default_thai_company.install.before_install"
|
||||
# Fixtures are synced before this runs; VAT schemes for companies that already exist.
|
||||
after_install = "default_thai_company.vat.setup_companies"
|
||||
# Fixtures are synced before this runs; VAT schemes and Shipping Rules for companies
|
||||
# that already exist.
|
||||
after_install = [
|
||||
"default_thai_company.vat.setup_companies",
|
||||
"default_thai_company.shipping.setup_companies",
|
||||
]
|
||||
# Fixture import re-inserts the Location tree; restore lft/rgt for user-added nodes.
|
||||
after_migrate = "default_thai_company.assets.rebuild_locations"
|
||||
|
||||
@@ -191,6 +195,7 @@ after_migrate = "default_thai_company.assets.rebuild_locations"
|
||||
override_doctype_class = {
|
||||
"Sales Invoice": "default_thai_company.tax_withholding.ThaiSalesInvoice",
|
||||
"Payment Entry": "default_thai_company.tax_withholding.ThaiPaymentEntry",
|
||||
"Shipping Rule": "default_thai_company.shipping.ThaiShippingRule",
|
||||
}
|
||||
|
||||
# Document Events
|
||||
@@ -213,6 +218,7 @@ doc_events = {
|
||||
"default_thai_company.tax_withholding.setup_company",
|
||||
"default_thai_company.vat.setup_company",
|
||||
"default_thai_company.assets.setup_company",
|
||||
"default_thai_company.shipping.setup_company",
|
||||
],
|
||||
},
|
||||
"Payment Entry": {
|
||||
|
||||
@@ -6,3 +6,4 @@
|
||||
# Patches added in this section will be executed after doctypes are migrated
|
||||
default_thai_company.patches.create_vat_accounts
|
||||
default_thai_company.patches.create_vat_templates
|
||||
default_thai_company.patches.create_shipping_rules
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
from default_thai_company.shipping import setup_companies
|
||||
|
||||
|
||||
def execute():
|
||||
setup_companies()
|
||||
@@ -0,0 +1,66 @@
|
||||
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)
|
||||
@@ -0,0 +1,50 @@
|
||||
{#- ERPNext's templates/print_formats/includes/taxes.html, printing the amounts that are posted.
|
||||
|
||||
ERPNext takes an Additional Discount off the items' net amounts (item.distributed_discount_amount;
|
||||
a discount on Grand Total is split between net and taxes) and books each charge at
|
||||
tax_amount_after_discount_amount, while upstream prints the discount as entered and the
|
||||
pre-discount tax_amount, so the lines do not add up to Grand Total.
|
||||
|
||||
Inclusive tax with a discount on Grand Total: the Total line is the tax-inclusive item total
|
||||
and the discount is printed as entered; Total After Discount is then broken down into
|
||||
Net Total and the charges. Otherwise the Total line is the net before the discount, so the
|
||||
discount is its net share, followed by Net Total and the charges. A discount that is not
|
||||
distributed (cash / non-trade) comes off the grand total only and stays after the charges,
|
||||
as upstream. -#}
|
||||
{%- macro amount_row(label, value) -%}
|
||||
<div class="row">
|
||||
<div class="col-xs-5 {%- if doc.align_labels_right %} text-right{%- endif -%}">
|
||||
<label>{{ label }}</label>
|
||||
</div>
|
||||
<div class="col-xs-7 text-right">
|
||||
{{ value }}
|
||||
</div>
|
||||
</div>
|
||||
{%- endmacro -%}
|
||||
{%- macro currency(value) -%}
|
||||
{{ frappe.format_value(value, {"fieldtype": "Currency", "options": "currency"}, doc) }}
|
||||
{%- endmacro -%}
|
||||
{%- set net_discount = doc.get("items")|map(attribute="distributed_discount_amount")|select|sum -%}
|
||||
{%- set gross_discount = net_discount and doc.apply_discount_on == "Grand Total" and doc.flags.show_inclusive_tax_in_print -%}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-6"></div>
|
||||
<div class="col-xs-6">
|
||||
{%- if gross_discount -%}
|
||||
{{ amount_row(_(doc.meta.get_label("discount_amount")), "- " ~ doc.get_formatted("discount_amount", doc)) }}
|
||||
{{ amount_row(_("Total After Discount"), currency(doc.total - doc.discount_amount)) }}
|
||||
{{ amount_row(_(doc.meta.get_label("net_total")), doc.get_formatted("net_total", doc)) }}
|
||||
{%- elif net_discount -%}
|
||||
{{ amount_row(_(doc.meta.get_label("discount_amount")), "- " ~ currency(net_discount)) }}
|
||||
{{ amount_row(_(doc.meta.get_label("net_total")), doc.get_formatted("net_total", doc)) }}
|
||||
{%- endif -%}
|
||||
{%- for charge in data -%}
|
||||
{%- if (charge.tax_amount or print_settings.print_taxes_with_zero_amount) and (not charge.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) -%}
|
||||
{{ amount_row(charge.get_formatted("description"), charge.get_formatted("tax_amount_after_discount_amount", doc)) }}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{%- if doc.discount_amount and not net_discount -%}
|
||||
{{ amount_row(_(doc.meta.get_label("discount_amount")), "- " ~ doc.get_formatted("discount_amount", doc)) }}
|
||||
{%- endif -%}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,22 @@
|
||||
{#- ERPNext's templates/print_formats/includes/total.html, paired with the taxes include:
|
||||
with inclusive tax and a discount on Grand Total the Total line is the tax-inclusive item
|
||||
total (the discount is printed as entered below it); with inclusive tax otherwise it is
|
||||
the net before the Additional Discount, since net_total already has the discount's net
|
||||
share (item.distributed_discount_amount) taken off. -#}
|
||||
{%- set net_discount = doc.get("items")|map(attribute="distributed_discount_amount")|select|sum -%}
|
||||
{%- set gross_discount = net_discount and doc.apply_discount_on == "Grand Total" and doc.flags.show_inclusive_tax_in_print -%}
|
||||
<div class="row {% if df.bold %}important{% endif %} data-field">
|
||||
{% if doc.flags.show_inclusive_tax_in_print and not gross_discount %}
|
||||
<div class="col-xs-5 {%- if doc.align_labels_right %} text-right{%- endif -%}">
|
||||
<label>{{ _("Total (Without Tax)") }}</label></div>
|
||||
<div class="col-xs-7 text-right value">
|
||||
{{ frappe.format_value(doc.net_total + net_discount, {"fieldtype": "Currency", "options": "currency"}, doc) }}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="col-xs-5 {%- if doc.align_labels_right %} text-right{%- endif -%}">
|
||||
<label>{{ _(df.label) }}</label></div>
|
||||
<div class="col-xs-7 text-right value">
|
||||
{{ doc.get_formatted("total", doc) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -177,6 +177,39 @@ class TestTaxWithholding(FrappeTestCase):
|
||||
doctype,
|
||||
)
|
||||
|
||||
def test_company_creation_adds_shipping_rule(self):
|
||||
rule = frappe.get_doc("Shipping Rule", f"Shipping Charges - {ABBR}")
|
||||
self.assertEqual(
|
||||
(rule.company, rule.shipping_rule_type, rule.calculate_based_on, rule.account, rule.cost_center),
|
||||
(COMPANY, "Selling", "Fixed", f"Shipping Charges - {ABBR}", f"Main - {ABBR}"),
|
||||
)
|
||||
self.assertEqual(frappe.db.get_value("Account", rule.account, "root_type"), "Income")
|
||||
|
||||
def test_shipping_amount_entered_on_transaction_survives_recalculation(self):
|
||||
rule = f"Shipping Charges - {ABBR}"
|
||||
so = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Sales Order",
|
||||
"company": COMPANY,
|
||||
"customer": CUSTOMER,
|
||||
"transaction_date": nowdate(),
|
||||
"delivery_date": nowdate(),
|
||||
"shipping_rule": rule,
|
||||
"items": [{"item_code": ITEM, "qty": 1, "rate": 1000}],
|
||||
}
|
||||
)
|
||||
so.set_missing_values()
|
||||
so.apply_shipping_rule()
|
||||
(charge,) = so.taxes
|
||||
self.assertEqual((charge.description, charge.tax_amount), (rule, 0))
|
||||
|
||||
charge.tax_amount = 150
|
||||
so.insert()
|
||||
so.apply_shipping_rule()
|
||||
so.save()
|
||||
self.assertEqual([t.tax_amount for t in so.taxes], [150])
|
||||
self.assertEqual(so.grand_total, 1150)
|
||||
|
||||
def test_fixture_reimport_keeps_site_account_and_relinks(self):
|
||||
alt = frappe.get_doc(
|
||||
{
|
||||
|
||||
@@ -50,6 +50,7 @@ Amount,จำนวนเงิน
|
||||
Total,รวม
|
||||
Total (Without Tax),รวมก่อนภาษี
|
||||
Net Total,ยอดรวมสุทธิ
|
||||
Total After Discount,ยอดรวมหลังหักส่วนลด
|
||||
Total Taxes and Charges,รวมภาษีและค่าธรรมเนียม
|
||||
Grand Total,ยอดรวมทั้งสิ้น
|
||||
Rounded Total,ยอดรวมปัดเศษ
|
||||
|
||||
|
Reference in New Issue
Block a user