mirror of
https://github.com/frappe/erpnext.git
synced 2026-09-20 03:47:11 +00:00
Merge pull request #56102 from rohitwaghchaure/feat-allocate-full-amount-to-stock-items
feat: allocate full actual charge to stock items only (e.g. Freight)
This commit is contained in:
@@ -11,6 +11,7 @@
|
|||||||
"add_deduct_tax",
|
"add_deduct_tax",
|
||||||
"charge_type",
|
"charge_type",
|
||||||
"row_id",
|
"row_id",
|
||||||
|
"allocate_full_amount_to_stock_items",
|
||||||
"included_in_print_rate",
|
"included_in_print_rate",
|
||||||
"included_in_paid_amount",
|
"included_in_paid_amount",
|
||||||
"col_break1",
|
"col_break1",
|
||||||
@@ -78,6 +79,14 @@
|
|||||||
"oldfieldname": "row_id",
|
"oldfieldname": "row_id",
|
||||||
"oldfieldtype": "Data"
|
"oldfieldtype": "Data"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"depends_on": "eval:doc.charge_type=='Actual' && ['Valuation', 'Valuation and Total'].includes(doc.category)",
|
||||||
|
"description": "If checked, the entire amount (e.g. Freight) is allocated to the valuation of stock & asset items only. If unchecked, the amount is distributed across all items and the portion belonging to non-stock items is not added to valuation.",
|
||||||
|
"fieldname": "allocate_full_amount_to_stock_items",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Allocate Full Amount to Stock Items"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"description": "If checked, the tax amount will be considered as already included in the Print Rate / Print Amount",
|
"description": "If checked, the tax amount will be considered as already included in the Print Rate / Print Amount",
|
||||||
|
|||||||
@@ -413,39 +413,29 @@ class BuyingController(SubcontractingController):
|
|||||||
stock_and_asset_items = []
|
stock_and_asset_items = []
|
||||||
stock_and_asset_items = self.get_stock_items() + self.get_asset_items()
|
stock_and_asset_items = self.get_stock_items() + self.get_asset_items()
|
||||||
|
|
||||||
stock_and_asset_items_qty, stock_and_asset_items_amount = 0, 0
|
(
|
||||||
last_item_idx = 1
|
tax_accounts,
|
||||||
for d in self.get("items"):
|
total_valuation_amount,
|
||||||
if d.item_code:
|
total_actual_tax_amount,
|
||||||
stock_and_asset_items_qty += flt(d.qty)
|
total_actual_tax_on_stock_items,
|
||||||
stock_and_asset_items_amount += flt(d.base_net_amount)
|
) = self.get_tax_details()
|
||||||
|
|
||||||
last_item_idx = d.idx
|
# Pre-compute each item's share of the "Actual" valuation charges (keyed by row object).
|
||||||
|
actual_charge_per_item = self.distribute_actual_tax_amount(
|
||||||
|
stock_and_asset_items, total_actual_tax_amount, total_actual_tax_on_stock_items
|
||||||
|
)
|
||||||
|
|
||||||
tax_accounts, total_valuation_amount, total_actual_tax_amount = self.get_tax_details()
|
last_item_idx = max((d.idx for d in self.get("items")), default=1)
|
||||||
remaining_amount = total_actual_tax_amount
|
|
||||||
|
|
||||||
for i, item in enumerate(self.get("items")):
|
for i, item in enumerate(self.get("items")):
|
||||||
if item.item_code and (item.qty or item.get("rejected_qty")):
|
if item.item_code and (item.qty or item.get("rejected_qty")):
|
||||||
item_tax_amount, actual_tax_amount = 0.0, 0.0
|
|
||||||
if i == (last_item_idx - 1):
|
if i == (last_item_idx - 1):
|
||||||
|
# dump any rounding remainder of the On Net Total valuation on the last item
|
||||||
item_tax_amount = total_valuation_amount
|
item_tax_amount = total_valuation_amount
|
||||||
actual_tax_amount = remaining_amount
|
|
||||||
else:
|
else:
|
||||||
# calculate item tax amount
|
|
||||||
item_tax_amount = self.get_item_tax_amount(item, tax_accounts)
|
item_tax_amount = self.get_item_tax_amount(item, tax_accounts)
|
||||||
total_valuation_amount -= item_tax_amount
|
total_valuation_amount -= item_tax_amount
|
||||||
|
|
||||||
if total_actual_tax_amount:
|
|
||||||
actual_tax_amount = self.get_item_actual_tax_amount(
|
|
||||||
item,
|
|
||||||
total_actual_tax_amount,
|
|
||||||
stock_and_asset_items_amount,
|
|
||||||
stock_and_asset_items_qty,
|
|
||||||
)
|
|
||||||
|
|
||||||
remaining_amount -= actual_tax_amount
|
|
||||||
|
|
||||||
# This code is required here to calculate the correct valuation for stock items
|
# This code is required here to calculate the correct valuation for stock items
|
||||||
if item.item_code not in stock_and_asset_items:
|
if item.item_code not in stock_and_asset_items:
|
||||||
item.valuation_rate = 0.0
|
item.valuation_rate = 0.0
|
||||||
@@ -453,7 +443,8 @@ class BuyingController(SubcontractingController):
|
|||||||
|
|
||||||
# Item tax amount is the total tax amount applied on that item and actual tax type amount
|
# Item tax amount is the total tax amount applied on that item and actual tax type amount
|
||||||
item.item_tax_amount = flt(
|
item.item_tax_amount = flt(
|
||||||
item_tax_amount + actual_tax_amount, self.precision("item_tax_amount", item)
|
item_tax_amount + actual_charge_per_item.get(item.idx, 0.0),
|
||||||
|
self.precision("item_tax_amount", item),
|
||||||
)
|
)
|
||||||
|
|
||||||
self.round_floats_in(item)
|
self.round_floats_in(item)
|
||||||
@@ -494,6 +485,7 @@ class BuyingController(SubcontractingController):
|
|||||||
tax_accounts = []
|
tax_accounts = []
|
||||||
total_valuation_amount = 0.0
|
total_valuation_amount = 0.0
|
||||||
total_actual_tax_amount = 0.0
|
total_actual_tax_amount = 0.0
|
||||||
|
total_actual_tax_on_stock_items = 0.0
|
||||||
|
|
||||||
for d in self.get("taxes"):
|
for d in self.get("taxes"):
|
||||||
if d.category not in ["Valuation", "Valuation and Total"]:
|
if d.category not in ["Valuation", "Valuation and Total"]:
|
||||||
@@ -506,10 +498,13 @@ class BuyingController(SubcontractingController):
|
|||||||
if d.charge_type == "On Net Total":
|
if d.charge_type == "On Net Total":
|
||||||
total_valuation_amount += amount
|
total_valuation_amount += amount
|
||||||
tax_accounts.append(d.account_head)
|
tax_accounts.append(d.account_head)
|
||||||
|
elif d.charge_type == "Actual" and d.get("allocate_full_amount_to_stock_items"):
|
||||||
|
# Allocate the full amount to stock/asset items only (e.g. Freight)
|
||||||
|
total_actual_tax_on_stock_items += amount
|
||||||
else:
|
else:
|
||||||
total_actual_tax_amount += amount
|
total_actual_tax_amount += amount
|
||||||
|
|
||||||
return tax_accounts, total_valuation_amount, total_actual_tax_amount
|
return tax_accounts, total_valuation_amount, total_actual_tax_amount, total_actual_tax_on_stock_items
|
||||||
|
|
||||||
def get_item_tax_amount(self, item, tax_accounts):
|
def get_item_tax_amount(self, item, tax_accounts):
|
||||||
item_tax_amount = 0.0
|
item_tax_amount = 0.0
|
||||||
@@ -530,16 +525,75 @@ class BuyingController(SubcontractingController):
|
|||||||
|
|
||||||
return item_tax_amount
|
return item_tax_amount
|
||||||
|
|
||||||
def get_item_actual_tax_amount(
|
def distribute_actual_tax_amount(self, stock_and_asset_items, total_on_all_items, total_on_stock_items):
|
||||||
self, item, actual_tax_amount, stock_and_asset_items_amount, stock_and_asset_items_qty
|
"""Distribute "Actual" valuation charges to each item, keyed by row idx.
|
||||||
):
|
|
||||||
item_proportion = (
|
`total_on_all_items` is spread across every item by net amount; a non-stock item's
|
||||||
flt(item.base_net_amount) / stock_and_asset_items_amount
|
share is computed but never capitalized (e.g. a genuine tax). `total_on_stock_items`
|
||||||
if stock_and_asset_items_amount
|
(flagged `allocate_full_amount_to_stock_items`) is spread across stock/asset items only,
|
||||||
else flt(item.qty) / stock_and_asset_items_qty
|
so the whole charge is capitalized (e.g. Freight).
|
||||||
|
"""
|
||||||
|
all_items = [d for d in self.get("items") if d.item_code]
|
||||||
|
stock_items = [d for d in all_items if d.item_code in stock_and_asset_items]
|
||||||
|
|
||||||
|
charge_per_item = {}
|
||||||
|
self._spread_charge_over_items(charge_per_item, total_on_all_items, all_items)
|
||||||
|
self._spread_charge_over_items(charge_per_item, total_on_stock_items, stock_items)
|
||||||
|
return charge_per_item
|
||||||
|
|
||||||
|
def _spread_charge_over_items(self, charge_per_item, total_charge, items):
|
||||||
|
"""Add each item's proportional share of `total_charge` into `charge_per_item`.
|
||||||
|
Proportion is by net amount (falling back to qty); any rounding remainder is assigned
|
||||||
|
to the last item in the group."""
|
||||||
|
if not total_charge or not items:
|
||||||
|
return
|
||||||
|
|
||||||
|
total_amount = sum(flt(d.base_net_amount) for d in items)
|
||||||
|
total_qty = sum(flt(d.qty) for d in items)
|
||||||
|
|
||||||
|
# Nothing to proportion against (all rows have zero amount and zero qty)
|
||||||
|
if not total_amount and not total_qty:
|
||||||
|
return
|
||||||
|
|
||||||
|
remaining = total_charge
|
||||||
|
for d in items[:-1]:
|
||||||
|
proportion = flt(d.base_net_amount) / total_amount if total_amount else flt(d.qty) / total_qty
|
||||||
|
charge = flt(proportion * total_charge, self.precision("item_tax_amount", d))
|
||||||
|
charge_per_item[d.idx] = charge_per_item.get(d.idx, 0.0) + charge
|
||||||
|
remaining -= charge
|
||||||
|
|
||||||
|
last = items[-1]
|
||||||
|
charge_per_item[last.idx] = charge_per_item.get(last.idx, 0.0) + flt(
|
||||||
|
remaining, self.precision("item_tax_amount", last)
|
||||||
)
|
)
|
||||||
|
|
||||||
return flt(item_proportion * actual_tax_amount, self.precision("item_tax_amount", item))
|
def get_capitalized_valuation_tax(self):
|
||||||
|
stock_and_asset_items = self.get_stock_items() + self.get_asset_items()
|
||||||
|
all_items = [d for d in self.get("items") if d.item_code]
|
||||||
|
stock_item_idx = {d.idx for d in all_items if d.item_code in stock_and_asset_items}
|
||||||
|
|
||||||
|
capitalized = {}
|
||||||
|
for tax in self.get("taxes"):
|
||||||
|
if tax.category not in ("Valuation", "Valuation and Total"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
amount = flt(tax.base_tax_amount_after_discount_amount) * (
|
||||||
|
-1 if tax.get("add_deduct_tax") == "Deduct" else 1
|
||||||
|
)
|
||||||
|
if not amount:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if tax.charge_type == "Actual" and not tax.get("allocate_full_amount_to_stock_items"):
|
||||||
|
# Spread across all items; only the stock/asset items' share is capitalized.
|
||||||
|
charge_per_item = {}
|
||||||
|
self._spread_charge_over_items(charge_per_item, amount, all_items)
|
||||||
|
amount = sum(
|
||||||
|
charge for item_idx, charge in charge_per_item.items() if item_idx in stock_item_idx
|
||||||
|
)
|
||||||
|
|
||||||
|
capitalized[tax.name] = amount
|
||||||
|
|
||||||
|
return capitalized
|
||||||
|
|
||||||
def set_incoming_rate(self):
|
def set_incoming_rate(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -362,6 +362,12 @@ class PurchaseReceiptGLComposer(BaseStockGLComposer):
|
|||||||
def _make_tax_gl_entries(self, gl_entries: list, via_landed_cost_voucher: bool = False) -> None:
|
def _make_tax_gl_entries(self, gl_entries: list, via_landed_cost_voucher: bool = False) -> None:
|
||||||
doc = self.doc
|
doc = self.doc
|
||||||
negative_expense_to_be_booked = sum([flt(d.item_tax_amount) for d in doc.get("items")])
|
negative_expense_to_be_booked = sum([flt(d.item_tax_amount) for d in doc.get("items")])
|
||||||
|
|
||||||
|
# Amount of each valuation charge actually capitalized into stock/asset valuation, keyed by
|
||||||
|
# tax row name. This is what must be credited to each tax account - a non-stock item's share
|
||||||
|
# of a spread-across-all-items charge is not capitalized, so it is excluded here.
|
||||||
|
capitalized_valuation_tax = doc.get_capitalized_valuation_tax()
|
||||||
|
|
||||||
valuation_tax = {}
|
valuation_tax = {}
|
||||||
for tax in doc.get("taxes"):
|
for tax in doc.get("taxes"):
|
||||||
if tax.category in ("Valuation", "Valuation and Total") and flt(
|
if tax.category in ("Valuation", "Valuation and Total") and flt(
|
||||||
@@ -373,10 +379,8 @@ class PurchaseReceiptGLComposer(BaseStockGLComposer):
|
|||||||
tax.idx, _(tax.category)
|
tax.idx, _(tax.category)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
valuation_tax.setdefault(tax.name, 0)
|
|
||||||
valuation_tax[tax.name] += (tax.add_deduct_tax == "Add" and 1 or -1) * flt(
|
valuation_tax[tax.name] = capitalized_valuation_tax.get(tax.name, 0.0)
|
||||||
tax.base_tax_amount_after_discount_amount
|
|
||||||
)
|
|
||||||
|
|
||||||
if negative_expense_to_be_booked and valuation_tax:
|
if negative_expense_to_be_booked and valuation_tax:
|
||||||
against_accounts = ", ".join([d.account for d in gl_entries if flt(d.debit) > 0])
|
against_accounts = ", ".join([d.account for d in gl_entries if flt(d.debit) > 0])
|
||||||
|
|||||||
@@ -1334,11 +1334,12 @@ class TestPurchaseReceipt(ERPNextTestSuite):
|
|||||||
pr.delete()
|
pr.delete()
|
||||||
|
|
||||||
def test_valuation_tax_distribution_with_non_stock_item(self):
|
def test_valuation_tax_distribution_with_non_stock_item(self):
|
||||||
"""A "Valuation and Total" tax is distributed across all items by net amount, but only
|
"""When "Allocate Full Amount to Stock Items" is unchecked, a "Valuation and Total"
|
||||||
stock/asset items can carry valuation. For a document with 2 stock items + 1 service
|
actual charge is distributed across all items by net amount, but only stock/asset items
|
||||||
item (each net 100) and a 30 valuation tax, each item's share is 10; only the two stock
|
can carry valuation. For a document with 2 stock items + 1 service item (each net 100)
|
||||||
items capitalize their share (20 total), so the non-stock item's 10 share must not be
|
and a 30 valuation charge, each item's share is 10; only the two stock items capitalize
|
||||||
capitalized onto the stock items."""
|
their share (20 total), so the non-stock item's 10 share must not be capitalized onto the
|
||||||
|
stock items."""
|
||||||
company = "_Test Company with perpetual inventory"
|
company = "_Test Company with perpetual inventory"
|
||||||
warehouse = "Stores - TCP1"
|
warehouse = "Stores - TCP1"
|
||||||
|
|
||||||
@@ -1373,6 +1374,8 @@ class TestPurchaseReceipt(ERPNextTestSuite):
|
|||||||
"cost_center": "Main - TCP1",
|
"cost_center": "Main - TCP1",
|
||||||
"description": "Valuation Tax",
|
"description": "Valuation Tax",
|
||||||
"tax_amount": 30,
|
"tax_amount": 30,
|
||||||
|
# Spread across all items (incl. non-stock); do not allocate full amount to stock items
|
||||||
|
"allocate_full_amount_to_stock_items": 0,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1400,6 +1403,158 @@ class TestPurchaseReceipt(ERPNextTestSuite):
|
|||||||
# Only the stock items' share (20) is capitalized; the service item's 10 is excluded
|
# Only the stock items' share (20) is capitalized; the service item's 10 is excluded
|
||||||
self.assertAlmostEqual(gl_map["_Test Account Shipping Charges - TCP1"].credit, 20.0, places=2)
|
self.assertAlmostEqual(gl_map["_Test Account Shipping Charges - TCP1"].credit, 20.0, places=2)
|
||||||
|
|
||||||
|
def test_full_actual_charge_capitalized_on_stock_items_only(self):
|
||||||
|
"""When "Allocate Full Amount to Stock Items" is checked (the default), an actual
|
||||||
|
valuation charge such as Freight is fully capitalized onto stock/asset items only. For a
|
||||||
|
document with 2 stock items + 1 service item (each net 100) and a 30 freight charge, the
|
||||||
|
charge is distributed over the 200 stock net only: 15 per stock item, and the entire 30
|
||||||
|
is capitalized (nothing is lost to the non-stock item)."""
|
||||||
|
company = "_Test Company with perpetual inventory"
|
||||||
|
warehouse = "Stores - TCP1"
|
||||||
|
|
||||||
|
stock_item1 = make_item(properties={"is_stock_item": 1}).name
|
||||||
|
stock_item2 = make_item(properties={"is_stock_item": 1}).name
|
||||||
|
service_item = make_item(properties={"is_stock_item": 0}).name
|
||||||
|
|
||||||
|
pr = frappe.new_doc("Purchase Receipt")
|
||||||
|
pr.company = company
|
||||||
|
pr.supplier = "_Test Supplier"
|
||||||
|
pr.currency = "INR"
|
||||||
|
# Order matters: stock, service, stock (service item in the middle)
|
||||||
|
for code in (stock_item1, service_item, stock_item2):
|
||||||
|
pr.append(
|
||||||
|
"items",
|
||||||
|
{
|
||||||
|
"item_code": code,
|
||||||
|
"qty": 1,
|
||||||
|
"rate": 100,
|
||||||
|
"warehouse": warehouse,
|
||||||
|
"cost_center": "Main - TCP1",
|
||||||
|
"expense_account": "Cost of Goods Sold - TCP1",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
pr.append(
|
||||||
|
"taxes",
|
||||||
|
{
|
||||||
|
"charge_type": "Actual",
|
||||||
|
"account_head": "_Test Account Shipping Charges - TCP1",
|
||||||
|
"category": "Valuation and Total",
|
||||||
|
"cost_center": "Main - TCP1",
|
||||||
|
"description": "Freight",
|
||||||
|
"tax_amount": 30,
|
||||||
|
# Default behavior: allocate the full amount to stock/asset items only
|
||||||
|
"allocate_full_amount_to_stock_items": 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
pr.insert()
|
||||||
|
|
||||||
|
# 30 freight / 200 stock net = 15 per stock item. The service item carries nothing.
|
||||||
|
self.assertAlmostEqual(pr.items[0].item_tax_amount, 15.0, places=2)
|
||||||
|
self.assertAlmostEqual(pr.items[1].item_tax_amount, 0.0, places=2)
|
||||||
|
self.assertAlmostEqual(pr.items[2].item_tax_amount, 15.0, places=2)
|
||||||
|
self.assertAlmostEqual(pr.items[0].valuation_rate, 115.0, places=2)
|
||||||
|
self.assertAlmostEqual(pr.items[2].valuation_rate, 115.0, places=2)
|
||||||
|
|
||||||
|
pr.submit()
|
||||||
|
|
||||||
|
gl_entries = get_gl_entries("Purchase Receipt", pr.name, skip_cancelled=True, as_dict=True)
|
||||||
|
gl_map = {row.account: row for row in gl_entries}
|
||||||
|
|
||||||
|
warehouse_account = get_warehouse_account_map(company)
|
||||||
|
stock_account = warehouse_account[warehouse]["account"]
|
||||||
|
|
||||||
|
# Stock asset = 200 (goods) + 30 (the entire freight charge)
|
||||||
|
self.assertAlmostEqual(gl_map[stock_account].debit, 230.0, places=2)
|
||||||
|
self.assertAlmostEqual(gl_map["Stock Received But Not Billed - TCP1"].credit, 200.0, places=2)
|
||||||
|
# The whole freight charge (30) is capitalized
|
||||||
|
self.assertAlmostEqual(gl_map["_Test Account Shipping Charges - TCP1"].credit, 30.0, places=2)
|
||||||
|
|
||||||
|
def test_actual_charge_distribution_with_both_allocation_modes(self):
|
||||||
|
"""Both allocation modes can coexist on the same document, and each item's share from
|
||||||
|
each charge adds up. For 2 stock items + 1 service item (each net 100):
|
||||||
|
- a 30 charge with the flag unchecked spreads over all 3 items (10 each); the service
|
||||||
|
item's 10 is not capitalized, so each stock item keeps 10.
|
||||||
|
- a 20 charge with the flag checked spreads over the 2 stock items only (10 each).
|
||||||
|
So each stock item carries 10 + 10 = 20, and the service item carries nothing."""
|
||||||
|
company = "_Test Company with perpetual inventory"
|
||||||
|
warehouse = "Stores - TCP1"
|
||||||
|
|
||||||
|
stock_item1 = make_item(properties={"is_stock_item": 1}).name
|
||||||
|
stock_item2 = make_item(properties={"is_stock_item": 1}).name
|
||||||
|
service_item = make_item(properties={"is_stock_item": 0}).name
|
||||||
|
|
||||||
|
pr = frappe.new_doc("Purchase Receipt")
|
||||||
|
pr.company = company
|
||||||
|
pr.supplier = "_Test Supplier"
|
||||||
|
pr.currency = "INR"
|
||||||
|
# Order matters: stock, service, stock (service item in the middle)
|
||||||
|
for code in (stock_item1, service_item, stock_item2):
|
||||||
|
pr.append(
|
||||||
|
"items",
|
||||||
|
{
|
||||||
|
"item_code": code,
|
||||||
|
"qty": 1,
|
||||||
|
"rate": 100,
|
||||||
|
"warehouse": warehouse,
|
||||||
|
"cost_center": "Main - TCP1",
|
||||||
|
"expense_account": "Cost of Goods Sold - TCP1",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Spread across all items (service share dropped)
|
||||||
|
pr.append(
|
||||||
|
"taxes",
|
||||||
|
{
|
||||||
|
"charge_type": "Actual",
|
||||||
|
"account_head": "_Test Account Shipping Charges - TCP1",
|
||||||
|
"category": "Valuation and Total",
|
||||||
|
"cost_center": "Main - TCP1",
|
||||||
|
"description": "Valuation Tax",
|
||||||
|
"tax_amount": 30,
|
||||||
|
"allocate_full_amount_to_stock_items": 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
# Allocate the full amount to stock items only
|
||||||
|
pr.append(
|
||||||
|
"taxes",
|
||||||
|
{
|
||||||
|
"charge_type": "Actual",
|
||||||
|
"account_head": "_Test Account Customs Duty - TCP1",
|
||||||
|
"category": "Valuation and Total",
|
||||||
|
"cost_center": "Main - TCP1",
|
||||||
|
"description": "Freight",
|
||||||
|
"tax_amount": 20,
|
||||||
|
"allocate_full_amount_to_stock_items": 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
pr.insert()
|
||||||
|
|
||||||
|
# Each stock item: 10 (all-items charge) + 10 (stock-only charge) = 20
|
||||||
|
self.assertAlmostEqual(pr.items[0].item_tax_amount, 20.0, places=2)
|
||||||
|
self.assertAlmostEqual(pr.items[1].item_tax_amount, 0.0, places=2)
|
||||||
|
self.assertAlmostEqual(pr.items[2].item_tax_amount, 20.0, places=2)
|
||||||
|
self.assertAlmostEqual(pr.items[0].valuation_rate, 120.0, places=2)
|
||||||
|
self.assertAlmostEqual(pr.items[2].valuation_rate, 120.0, places=2)
|
||||||
|
|
||||||
|
pr.submit()
|
||||||
|
|
||||||
|
gl_entries = get_gl_entries("Purchase Receipt", pr.name, skip_cancelled=True, as_dict=True)
|
||||||
|
gl_map = {row.account: row for row in gl_entries}
|
||||||
|
|
||||||
|
warehouse_account = get_warehouse_account_map(company)
|
||||||
|
stock_account = warehouse_account[warehouse]["account"]
|
||||||
|
|
||||||
|
# Stock asset = 200 (goods) + 20 (stock share of the spread charge) + 20 (the full freight)
|
||||||
|
self.assertAlmostEqual(gl_map[stock_account].debit, 240.0, places=2)
|
||||||
|
self.assertAlmostEqual(gl_map["Stock Received But Not Billed - TCP1"].credit, 200.0, places=2)
|
||||||
|
# Only the stock items' 20 share of the spread charge is capitalized (service 10 excluded)
|
||||||
|
self.assertAlmostEqual(gl_map["_Test Account Shipping Charges - TCP1"].credit, 20.0, places=2)
|
||||||
|
# The whole freight charge (20) is capitalized
|
||||||
|
self.assertAlmostEqual(gl_map["_Test Account Customs Duty - TCP1"].credit, 20.0, places=2)
|
||||||
|
|
||||||
def test_po_to_pi_and_po_to_pr_worflow_full(self):
|
def test_po_to_pi_and_po_to_pr_worflow_full(self):
|
||||||
"""Test following behaviour:
|
"""Test following behaviour:
|
||||||
- Create PO
|
- Create PO
|
||||||
|
|||||||
Reference in New Issue
Block a user