Merge pull request #4 from frappe/develop

sync fork
This commit is contained in:
Jannat Patel 2021-01-04 12:18:54 +05:30 committed by GitHub
commit 5f9a219429
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
191 changed files with 7357 additions and 1940 deletions

View File

@ -21,8 +21,8 @@ def docs_link_exists(body):
if word.startswith('http') and uri_validator(word):
parsed_url = urlparse(word)
if parsed_url.netloc == "github.com":
_, org, repo, _type, ref = parsed_url.path.split('/')
if org == "frappe" and repo in docs_repos:
parts = parsed_url.path.split('/')
if len(parts) == 5 and parts[1] == "frappe" and parts[2] in docs_repos:
return True

View File

@ -910,98 +910,8 @@
},
"is_group": 1
},
"Passiva": {
"Passiva - Verbindlichkeiten": {
"root_type": "Liability",
"A - Eigenkapital": {
"account_type": "Equity",
"is_group": 1,
"I - Gezeichnetes Kapital": {
"account_type": "Equity",
"is_group": 1,
"Gezeichnetes Kapital": {
"account_type": "Equity",
"account_number": "2900"
},
"Ausstehende Einlagen auf das gezeichnete Kapital": {
"account_number": "2910",
"is_group": 1
}
},
"II - Kapitalr\u00fccklage": {
"account_type": "Equity",
"is_group": 1,
"Kapitalr\u00fccklage": {
"account_number": "2920"
}
},
"III - Gewinnr\u00fccklagen": {
"account_type": "Equity",
"1 - gesetzliche R\u00fccklage": {
"account_type": "Equity",
"is_group": 1,
"Gesetzliche R\u00fccklage": {
"account_number": "2930"
}
},
"2 - R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
"account_type": "Equity",
"is_group": 1
},
"3 - satzungsm\u00e4\u00dfige R\u00fccklagen": {
"account_type": "Equity",
"is_group": 1,
"Satzungsm\u00e4\u00dfige R\u00fccklagen": {
"account_number": "2950"
}
},
"4 - andere Gewinnr\u00fccklagen": {
"account_type": "Equity",
"is_group": 1,
"Gewinnr\u00fccklagen aus den \u00dcbergangsvorschriften BilMoG": {
"is_group": 1,
"Gewinnr\u00fccklagen (BilMoG)": {
"account_number": "2963"
},
"Gewinnr\u00fccklagen aus Zuschreibung Sachanlageverm\u00f6gen (BilMoG)": {
"account_number": "2964"
},
"Gewinnr\u00fccklagen aus Zuschreibung Finanzanlageverm\u00f6gen (BilMoG)": {
"account_number": "2965"
},
"Gewinnr\u00fccklagen aus Aufl\u00f6sung der Sonderposten mit R\u00fccklageanteil (BilMoG)": {
"account_number": "2966"
}
},
"Latente Steuern (Gewinnr\u00fccklage Haben) aus erfolgsneutralen Verrechnungen": {
"account_number": "2967"
},
"Latente Steuern (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
"account_number": "2968"
},
"Rechnungsabgrenzungsposten (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
"account_number": "2969"
}
},
"is_group": 1
},
"IV - Gewinnvortrag/Verlustvortrag": {
"account_type": "Equity",
"is_group": 1,
"Gewinnvortrag vor Verwendung": {
"account_number": "2970"
},
"Verlustvortrag vor Verwendung": {
"account_number": "2978"
}
},
"V - Jahres\u00fcberschu\u00df/Jahresfehlbetrag": {
"account_type": "Equity",
"is_group": 1
},
"Einlagen stiller Gesellschafter": {
"account_number": "9295"
}
},
"B - R\u00fcckstellungen": {
"is_group": 1,
"1 - R\u00fcckstellungen f. Pensionen und \u00e4hnliche Verplicht.": {
@ -1618,6 +1528,143 @@
},
"is_group": 1
},
"Passiva - Eigenkapital": {
"root_type": "Equity",
"A - Eigenkapital": {
"account_type": "Equity",
"is_group": 1,
"I - Gezeichnetes Kapital": {
"account_type": "Equity",
"is_group": 1,
"Gezeichnetes Kapital": {
"account_number": "2900",
"account_type": "Equity"
},
"Gesch\u00e4ftsguthaben der verbleibenden Mitglieder": {
"account_number": "2901"
},
"Gesch\u00e4ftsguthaben der ausscheidenden Mitglieder": {
"account_number": "2902"
},
"Gesch\u00e4ftsguthaben aus gek\u00fcndigten Gesch\u00e4ftsanteilen": {
"account_number": "2903"
},
"R\u00fcckst\u00e4ndige f\u00e4llige Einzahlungen auf Gesch\u00e4ftsanteile, vermerkt": {
"account_number": "2906"
},
"Gegenkonto R\u00fcckst\u00e4ndige f\u00e4llige Einzahlungen auf Gesch\u00e4ftsanteile, vermerkt": {
"account_number": "2907"
},
"Kapitalerh\u00f6hung aus Gesellschaftsmitteln": {
"account_number": "2908"
},
"Ausstehende Einlagen auf das gezeichnete Kapital, nicht eingefordert": {
"account_number": "2910"
}
},
"II - Kapitalr\u00fccklage": {
"account_type": "Equity",
"is_group": 1,
"Kapitalr\u00fccklage": {
"account_number": "2920"
},
"Kapitalr\u00fccklage durch Ausgabe von Anteilen \u00fcber Nennbetrag": {
"account_number": "2925"
},
"Kapitalr\u00fccklage durch Ausgabe von Schuldverschreibungen": {
"account_number": "2926"
},
"Kapitalr\u00fccklage durch Zuzahlungen gegen Gew\u00e4hrung eines Vorzugs": {
"account_number": "2927"
},
"Kapitalr\u00fccklage durch Zuzahlungen in das Eigenkapital": {
"account_number": "2928"
},
"Nachschusskapital (Gegenkonto 1299)": {
"account_number": "2929"
}
},
"III - Gewinnr\u00fccklagen": {
"account_type": "Equity",
"1 - gesetzliche R\u00fccklage": {
"account_type": "Equity",
"is_group": 1,
"Gesetzliche R\u00fccklage": {
"account_number": "2930"
}
},
"2 - R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
"account_type": "Equity",
"is_group": 1,
"R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
"account_number": "2935"
}
},
"3 - satzungsm\u00e4\u00dfige R\u00fccklagen": {
"account_type": "Equity",
"is_group": 1,
"Satzungsm\u00e4\u00dfige R\u00fccklagen": {
"account_number": "2950"
}
},
"4 - andere Gewinnr\u00fccklagen": {
"account_type": "Equity",
"is_group": 1,
"Andere Gewinnr\u00fccklagen": {
"account_number": "2960"
},
"Andere Gewinnr\u00fccklagen aus dem Erwerb eigener Anteile": {
"account_number": "2961"
},
"Eigenkapitalanteil von Wertaufholungen": {
"account_number": "2962"
},
"Gewinnr\u00fccklagen aus den \u00dcbergangsvorschriften BilMoG": {
"is_group": 1,
"Gewinnr\u00fccklagen (BilMoG)": {
"account_number": "2963"
},
"Gewinnr\u00fccklagen aus Zuschreibung Sachanlageverm\u00f6gen (BilMoG)": {
"account_number": "2964"
},
"Gewinnr\u00fccklagen aus Zuschreibung Finanzanlageverm\u00f6gen (BilMoG)": {
"account_number": "2965"
},
"Gewinnr\u00fccklagen aus Aufl\u00f6sung der Sonderposten mit R\u00fccklageanteil (BilMoG)": {
"account_number": "2966"
}
},
"Latente Steuern (Gewinnr\u00fccklage Haben) aus erfolgsneutralen Verrechnungen": {
"account_number": "2967"
},
"Latente Steuern (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
"account_number": "2968"
},
"Rechnungsabgrenzungsposten (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
"account_number": "2969"
}
},
"is_group": 1
},
"IV - Gewinnvortrag/Verlustvortrag": {
"account_type": "Equity",
"is_group": 1,
"Gewinnvortrag vor Verwendung": {
"account_number": "2970"
},
"Verlustvortrag vor Verwendung": {
"account_number": "2978"
}
},
"V - Jahres\u00fcberschu\u00df/Jahresfehlbetrag": {
"account_type": "Equity",
"is_group": 1
},
"Einlagen stiller Gesellschafter": {
"account_number": "9295"
}
}
},
"1 - Umsatzerl\u00f6se": {
"root_type": "Income",
"is_group": 1,

View File

@ -172,7 +172,7 @@ class TestAccount(unittest.TestCase):
frappe.delete_doc("Account", doc)
def _make_test_records(verbose):
def _make_test_records(verbose=None):
from frappe.test_runner import make_test_objects
accounts = [

View File

@ -28,22 +28,22 @@ def test_create_test_data():
"item_group": "_Test Item Group",
"item_name": "_Test Tesla Car",
"apply_warehouse_wise_reorder_level": 0,
"warehouse":"Stores - TCP1",
"warehouse":"Stores - _TC",
"gst_hsn_code": "999800",
"valuation_rate": 5000,
"standard_rate":5000,
"item_defaults": [{
"company": "_Test Company with perpetual inventory",
"default_warehouse": "Stores - TCP1",
"company": "_Test Company",
"default_warehouse": "Stores - _TC",
"default_price_list":"_Test Price List",
"expense_account": "Cost of Goods Sold - TCP1",
"buying_cost_center": "Main - TCP1",
"selling_cost_center": "Main - TCP1",
"income_account": "Sales - TCP1"
"expense_account": "Cost of Goods Sold - _TC",
"buying_cost_center": "Main - _TC",
"selling_cost_center": "Main - _TC",
"income_account": "Sales - _TC"
}],
"show_in_website": 1,
"route":"-test-tesla-car",
"website_warehouse": "Stores - TCP1"
"website_warehouse": "Stores - _TC"
})
item.insert()
# create test item price
@ -65,12 +65,12 @@ def test_create_test_data():
"items": [{
"item_code": "_Test Tesla Car"
}],
"warehouse":"Stores - TCP1",
"warehouse":"Stores - _TC",
"coupon_code_based":1,
"selling": 1,
"rate_or_discount": "Discount Percentage",
"discount_percentage": 30,
"company": "_Test Company with perpetual inventory",
"company": "_Test Company",
"currency":"INR",
"for_price_list":"_Test Price List"
})
@ -85,7 +85,7 @@ def test_create_test_data():
})
sales_partner.insert()
# create test item coupon code
if not frappe.db.exists("Coupon Code","SAVE30"):
if not frappe.db.exists("Coupon Code", "SAVE30"):
coupon_code = frappe.get_doc({
"doctype": "Coupon Code",
"coupon_name":"SAVE30",
@ -102,35 +102,27 @@ class TestCouponCode(unittest.TestCase):
test_create_test_data()
def tearDown(self):
frappe.set_user("Administrator")
frappe.set_user("Administrator")
def test_1_check_coupon_code_used_before_so(self):
coupon_code = frappe.get_doc("Coupon Code", frappe.db.get_value("Coupon Code", {"coupon_name":"SAVE30"}))
# reset used coupon code count
coupon_code.used=0
coupon_code.save()
# check no coupon code is used before sales order is made
self.assertEqual(coupon_code.get("used"),0)
def test_sales_order_with_coupon_code(self):
frappe.db.set_value("Coupon Code", "SAVE30", "used", 0)
def test_2_sales_order_with_coupon_code(self):
so = make_sales_order(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1',
customer="_Test Customer", selling_price_list="_Test Price List", item_code="_Test Tesla Car", rate=5000,qty=1,
so = make_sales_order(company='_Test Company', warehouse='Stores - _TC',
customer="_Test Customer", selling_price_list="_Test Price List",
item_code="_Test Tesla Car", rate=5000, qty=1,
do_not_submit=True)
so = frappe.get_doc('Sales Order', so.name)
# check item price before coupon code is applied
self.assertEqual(so.items[0].rate, 5000)
so.coupon_code='SAVE30'
so.sales_partner='_Test Coupon Partner'
so.save()
# check item price after coupon code is applied
self.assertEqual(so.items[0].rate, 3500)
so.submit()
def test_3_check_coupon_code_used_after_so(self):
doc = frappe.get_doc("Coupon Code", frappe.db.get_value("Coupon Code", {"coupon_name":"SAVE30"}))
# check no coupon code is used before sales order is made
self.assertEqual(doc.get("used"),1)
self.assertEqual(frappe.db.get_value("Coupon Code", "SAVE30", "used"), 1)

View File

@ -30,20 +30,22 @@ class GLEntry(Document):
self.pl_must_have_cost_center()
self.validate_cost_center()
self.check_pl_account()
self.validate_party()
self.validate_currency()
if not self.flags.from_repost:
self.check_pl_account()
self.validate_party()
self.validate_currency()
def on_update_with_args(self, adv_adj, update_outstanding = 'Yes'):
self.validate_account_details(adv_adj)
self.validate_dimensions_for_pl_and_bs()
def on_update_with_args(self, adv_adj, update_outstanding = 'Yes', from_repost=False):
if not from_repost:
self.validate_account_details(adv_adj)
self.validate_dimensions_for_pl_and_bs()
validate_frozen_account(self.account, adv_adj)
validate_balance_type(self.account, adv_adj)
# Update outstanding amt on against voucher
if self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees'] \
and self.against_voucher and update_outstanding == 'Yes':
and self.against_voucher and update_outstanding == 'Yes' and not from_repost:
update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type,
self.against_voucher)
@ -106,8 +108,8 @@ class GLEntry(Document):
from tabAccount where name=%s""", self.account, as_dict=1)[0]
if ret.is_group==1:
frappe.throw(_('''{0} {1}: Account {2} is a Group Account and group accounts cannot be used in
transactions''').format(self.voucher_type, self.voucher_no, self.account))
frappe.throw(_('''{0} {1}: Account {2} is a Group Account and group accounts cannot be used in transactions''')
.format(self.voucher_type, self.voucher_no, self.account))
if ret.docstatus==2:
frappe.throw(_("{0} {1}: Account {2} is inactive")
@ -136,8 +138,8 @@ class GLEntry(Document):
.format(self.voucher_type, self.voucher_no, self.cost_center, self.company))
if self.cost_center and _check_is_group():
frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot
be used in transactions""").format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions""")
.format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
def validate_party(self):
validate_party_frozen_disabled(self.party_type, self.party)

View File

@ -6,14 +6,18 @@ import frappe, erpnext, json
from frappe.utils import cstr, flt, fmt_money, formatdate, getdate, nowdate, cint, get_link_to_form
from frappe import msgprint, _, scrub
from erpnext.controllers.accounts_controller import AccountsController
from erpnext.accounts.utils import get_balance_on, get_account_currency
from erpnext.accounts.utils import get_balance_on, get_stock_accounts, get_stock_and_account_balance, \
get_account_currency, check_if_stock_and_account_balance_synced
from erpnext.accounts.party import get_party_account
from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import get_party_account_based_on_invoice_discounting
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting \
import get_party_account_based_on_invoice_discounting
from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts
from six import string_types, iteritems
class StockAccountInvalidTransaction(frappe.ValidationError): pass
class JournalEntry(AccountsController):
def __init__(self, *args, **kwargs):
super(JournalEntry, self).__init__(*args, **kwargs)
@ -46,6 +50,7 @@ class JournalEntry(AccountsController):
self.validate_empty_accounts_table()
self.set_account_and_party_balance()
self.validate_inter_company_accounts()
self.validate_stock_accounts()
if not self.title:
self.title = self.get_title()
@ -57,6 +62,8 @@ class JournalEntry(AccountsController):
self.update_expense_claim()
self.update_inter_company_jv()
self.update_invoice_discounting()
check_if_stock_and_account_balance_synced(self.posting_date,
self.company, self.doctype, self.name)
def on_cancel(self):
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
@ -95,6 +102,16 @@ class JournalEntry(AccountsController):
if account_currency == previous_account_currency:
if self.total_credit != doc.total_debit or self.total_debit != doc.total_credit:
frappe.throw(_("Total Credit/ Debit Amount should be same as linked Journal Entry"))
def validate_stock_accounts(self):
stock_accounts = get_stock_accounts(self.company, self.doctype, self.name)
for account in stock_accounts:
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(account,
self.posting_date, self.company)
if account_bal == stock_bal:
frappe.throw(_("Account: {0} can only be updated via Stock Transactions")
.format(account), StockAccountInvalidTransaction)
def update_inter_company_jv(self):
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:

View File

@ -6,7 +6,7 @@ import unittest, frappe
from frappe.utils import flt, nowdate
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.exceptions import InvalidAccountCurrency
from erpnext.accounts.general_ledger import StockAccountInvalidTransaction
from erpnext.accounts.doctype.journal_entry.journal_entry import StockAccountInvalidTransaction
class TestJournalEntry(unittest.TestCase):
def test_journal_entry_with_against_jv(self):
@ -75,54 +75,46 @@ class TestJournalEntry(unittest.TestCase):
elif test_voucher.doctype in ["Sales Order", "Purchase Order"]:
# if test_voucher is a Sales Order/Purchase Order, test error on cancellation of test_voucher
frappe.db.set_value("Accounts Settings", "Accounts Settings",
"unlink_advance_payment_on_cancelation_of_order", 0)
submitted_voucher = frappe.get_doc(test_voucher.doctype, test_voucher.name)
self.assertRaises(frappe.LinkExistsError, submitted_voucher.cancel)
def test_jv_against_stock_account(self):
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
set_perpetual_inventory()
company = "_Test Company with perpetual inventory"
stock_account = get_inventory_account(company)
jv = frappe.copy_doc({
"cheque_date": nowdate(),
"cheque_no": "33",
"company": "_Test Company with perpetual inventory",
"doctype": "Journal Entry",
"accounts": [
{
"account": "Debtors - TCP1",
"party_type": "Customer",
"party": "_Test Customer",
"credit_in_account_currency": 400.0,
"debit_in_account_currency": 0.0,
"doctype": "Journal Entry Account",
"parentfield": "accounts",
"cost_center": "Main - TCP1"
},
{
"account": "_Test Bank - TCP1",
"credit_in_account_currency": 0.0,
"debit_in_account_currency": 400.0,
"doctype": "Journal Entry Account",
"parentfield": "accounts",
"cost_center": "Main - TCP1"
}
],
"naming_series": "_T-Journal Entry-",
"posting_date": nowdate(),
"user_remark": "test",
"voucher_type": "Bank Entry"
})
from erpnext.accounts.utils import get_stock_and_account_balance
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(stock_account, nowdate(), company)
diff = flt(account_bal) - flt(stock_bal)
jv.get("accounts")[0].update({
"account": get_inventory_account('_Test Company with perpetual inventory'),
"company": "_Test Company with perpetual inventory",
"party_type": None,
"party": None
if not diff:
diff = 100
jv = frappe.new_doc("Journal Entry")
jv.company = company
jv.posting_date = nowdate()
jv.append("accounts", {
"account": stock_account,
"cost_center": "Main - TCP1",
"debit_in_account_currency": 0 if diff > 0 else abs(diff),
"credit_in_account_currency": diff if diff > 0 else 0
})
jv.append("accounts", {
"account": "Stock Adjustment - TCP1",
"cost_center": "Main - TCP1",
"debit_in_account_currency": diff if diff > 0 else 0,
"credit_in_account_currency": 0 if diff > 0 else abs(diff)
})
jv.insert()
self.assertRaises(StockAccountInvalidTransaction, jv.submit)
jv.cancel()
set_perpetual_inventory(0)
if account_bal == stock_bal:
self.assertRaises(StockAccountInvalidTransaction, jv.submit)
frappe.db.rollback()
else:
jv.submit()
jv.cancel()
def test_multi_currency(self):
jv = make_journal_entry("_Test Bank USD - _TC",

View File

@ -8,12 +8,10 @@ import unittest
from frappe.utils import today, cint, flt, getdate
from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points
from erpnext.accounts.party import get_dashboard_info
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
class TestLoyaltyProgram(unittest.TestCase):
@classmethod
def setUpClass(self):
set_perpetual_inventory(0)
# create relevant item, customer, loyalty program, etc
create_records()

View File

@ -267,6 +267,8 @@ class POSInvoice(SalesInvoice):
from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile
if not self.pos_profile:
pos_profile = get_pos_profile(self.company) or {}
if not pos_profile:
frappe.throw(_("No POS Profile found. Please create a New POS Profile first"))
self.pos_profile = pos_profile.get('name')
profile = {}

View File

@ -14,7 +14,6 @@
"column_break_9",
"update_stock",
"ignore_pricing_rule",
"hide_unavailable_items",
"warehouse",
"campaign",
"company_address",
@ -23,6 +22,9 @@
"section_break_11",
"payments",
"section_break_14",
"hide_images",
"hide_unavailable_items",
"auto_add_item_to_cart",
"item_groups",
"column_break_16",
"customer_groups",
@ -124,7 +126,8 @@
},
{
"fieldname": "section_break_14",
"fieldtype": "Section Break"
"fieldtype": "Section Break",
"label": "Configuration"
},
{
"description": "Only show Items from these Item Groups",
@ -314,13 +317,25 @@
"fieldname": "hide_unavailable_items",
"fieldtype": "Check",
"label": "Hide Unavailable Items"
},
{
"default": "0",
"fieldname": "hide_images",
"fieldtype": "Check",
"label": "Hide Images"
},
{
"default": "0",
"fieldname": "auto_add_item_to_cart",
"fieldtype": "Check",
"label": "Automatically Add Filtered Item To Cart"
}
],
"icon": "icon-cog",
"idx": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-10-29 13:18:38.795925",
"modified": "2020-12-10 13:59:28.877572",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Profile",

View File

@ -345,9 +345,13 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
if ((pricing_rule.margin_type in ['Amount', 'Percentage'] and pricing_rule.currency == args.currency)
or (pricing_rule.margin_type == 'Percentage')):
item_details.margin_type = pricing_rule.margin_type
item_details.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
item_details.has_margin = True
if pricing_rule.apply_multiple_pricing_rules and item_details.margin_rate_or_amount is not None:
item_details.margin_rate_or_amount += pricing_rule.margin_rate_or_amount
else:
item_details.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
if pricing_rule.rate_or_discount == 'Rate':
pricing_rule_rate = 0.0
if pricing_rule.currency == args.currency:

View File

@ -164,7 +164,15 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True):
frappe.throw(_("Invalid {0}").format(args.get(field)))
parent_groups = frappe.db.sql_list("""select name from `tab%s`
where lft<=%s and rgt>=%s""" % (parenttype, '%s', '%s'), (lft, rgt))
where lft>=%s and rgt<=%s""" % (parenttype, '%s', '%s'), (lft, rgt))
if parenttype in ["Customer Group", "Item Group", "Territory"]:
parent_field = "parent_{0}".format(frappe.scrub(parenttype))
root_name = frappe.db.get_list(parenttype,
{"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1)
if root_name and root_name[0][0]:
parent_groups.append(root_name[0][0])
if parent_groups:
if allow_blank: parent_groups.append('')

View File

@ -15,6 +15,16 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
return (doc.qty<=doc.received_qty) ? "green" : "orange";
});
}
this.frm.set_query("unrealized_profit_loss_account", function() {
return {
filters: {
company: doc.company,
is_group: 0,
root_type: "Liability",
}
};
});
},
onload: function() {
this._super();

View File

@ -1,6 +1,5 @@
{
"actions": [],
"allow_auto_repeat": 1,
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-05-21 16:16:39",
@ -127,6 +126,7 @@
"write_off_cost_center",
"advances_section",
"allocate_advances_automatically",
"adjust_advance_taxes",
"get_advances",
"advances",
"payment_schedule_section",
@ -152,9 +152,11 @@
"is_opening",
"against_expense_account",
"column_break_63",
"unrealized_profit_loss_account",
"status",
"inter_company_invoice_reference",
"is_internal_supplier",
"represents_company",
"remarks",
"subscription_section",
"from_date",
@ -1223,7 +1225,7 @@
"fieldtype": "Select",
"in_standard_filter": 1,
"label": "Status",
"options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled",
"options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled\nInternal Transfer",
"print_hide": 1
},
{
@ -1330,13 +1332,37 @@
"fieldtype": "Link",
"label": "Project",
"options": "Project"
},
{
"default": "0",
"description": "Taxes paid while advance payment will be adjusted against this invoice",
"fieldname": "adjust_advance_taxes",
"fieldtype": "Check",
"label": "Adjust Advance Taxes"
},
{
"depends_on": "eval:doc.is_internal_supplier",
"description": "Unrealized Profit / Loss account for intra-company transfers",
"fieldname": "unrealized_profit_loss_account",
"fieldtype": "Link",
"label": "Unrealized Profit / Loss Account",
"options": "Account"
},
{
"depends_on": "eval:doc.is_internal_supplier",
"description": "Company which internal supplier represents",
"fetch_from": "supplier.represents_company",
"fieldname": "represents_company",
"fieldtype": "Link",
"label": "Represents Company",
"options": "Company"
}
],
"icon": "fa fa-file-text",
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2020-10-30 13:57:18.266978",
"modified": "2020-12-11 12:46:12.796378",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@ -206,8 +206,8 @@ class PurchaseInvoice(BuyingController):
["Purchase Receipt", "purchase_receipt", "pr_detail"]
])
def validate_warehouse(self):
if self.update_stock:
def validate_warehouse(self, for_validate=True):
if self.update_stock and for_validate:
for d in self.get('items'):
if not d.warehouse:
frappe.throw(_("Warehouse required at Row No {0}, please set default warehouse for the item {1} for the company {2}").
@ -233,7 +233,7 @@ class PurchaseInvoice(BuyingController):
if self.update_stock:
self.validate_item_code()
self.validate_warehouse()
self.validate_warehouse(for_validate)
if auto_accounting_for_stock:
warehouse_account = get_warehouse_account_map(self.company)
@ -410,10 +410,13 @@ class PurchaseInvoice(BuyingController):
# this sequence because outstanding may get -negative
self.make_gl_entries()
if self.update_stock == 1:
self.repost_future_sle_and_gle()
self.update_project()
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
def make_gl_entries(self, gl_entries=None):
def make_gl_entries(self, gl_entries=None, from_repost=False):
if not gl_entries:
gl_entries = self.get_gl_entries()
@ -421,7 +424,7 @@ class PurchaseInvoice(BuyingController):
update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes"
if self.docstatus == 1:
make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False)
make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False, from_repost=from_repost)
elif self.docstatus == 2:
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
@ -436,9 +439,11 @@ class PurchaseInvoice(BuyingController):
self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
if self.auto_accounting_for_stock:
self.stock_received_but_not_billed = self.get_company_default("stock_received_but_not_billed")
self.expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
else:
self.stock_received_but_not_billed = None
self.expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
self.expenses_included_in_valuation = None
self.negative_expense_to_be_booked = 0.0
gl_entries = []
@ -449,15 +454,15 @@ class PurchaseInvoice(BuyingController):
self.get_asset_gl_entry(gl_entries)
self.make_tax_gl_entries(gl_entries)
self.make_internal_transfer_gl_entries(gl_entries)
gl_entries = make_regional_gl_entries(gl_entries, self)
gl_entries = merge_similar_entries(gl_entries)
self.make_payment_gl_entries(gl_entries)
self.make_write_off_gl_entry(gl_entries)
self.make_gle_for_rounding_adjustment(gl_entries)
return gl_entries
def check_asset_cwip_enabled(self):
@ -474,31 +479,30 @@ class PurchaseInvoice(BuyingController):
# because rounded_total had value even before introcution of posting GLE based on rounded total
grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
if grand_total:
# Didnot use base_grand_total to book rounding loss gle
grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
self.precision("grand_total"))
gl_entries.append(
self.get_gl_dict({
"account": self.credit_to,
"party_type": "Supplier",
"party": self.supplier,
"due_date": self.due_date,
"against": self.against_expense_account,
"credit": grand_total_in_company_currency,
"credit_in_account_currency": grand_total_in_company_currency \
if self.party_account_currency==self.company_currency else grand_total,
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype,
"project": self.project,
"cost_center": self.cost_center
}, self.party_account_currency, item=self)
)
if grand_total and not self.is_internal_transfer():
# Didnot use base_grand_total to book rounding loss gle
grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
self.precision("grand_total"))
gl_entries.append(
self.get_gl_dict({
"account": self.credit_to,
"party_type": "Supplier",
"party": self.supplier,
"due_date": self.due_date,
"against": self.against_expense_account,
"credit": grand_total_in_company_currency,
"credit_in_account_currency": grand_total_in_company_currency \
if self.party_account_currency==self.company_currency else grand_total,
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype,
"project": self.project,
"cost_center": self.cost_center
}, self.party_account_currency, item=self)
)
def make_item_gl_entries(self, gl_entries):
# item gl entries
stock_items = self.get_stock_items()
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
if self.update_stock and self.auto_accounting_for_stock:
warehouse_account = get_warehouse_account_map(self.company)
@ -526,7 +530,6 @@ class PurchaseInvoice(BuyingController):
item, voucher_wise_stock_value, account_currency)
if item.from_warehouse:
gl_entries.append(self.get_gl_dict({
"account": warehouse_account[item.warehouse]['account'],
"against": warehouse_account[item.from_warehouse]["account"],
@ -546,16 +549,18 @@ class PurchaseInvoice(BuyingController):
"debit": -1 * flt(item.base_net_amount, item.precision("base_net_amount")),
}, warehouse_account[item.from_warehouse]["account_currency"], item=item))
gl_entries.append(
self.get_gl_dict({
"account": item.expense_account,
"against": self.supplier,
"debit": flt(item.base_net_amount, item.precision("base_net_amount")),
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"cost_center": item.cost_center,
"project": item.project
}, account_currency, item=item)
)
# Do not book expense for transfer within same company transfer
if not self.is_internal_transfer():
gl_entries.append(
self.get_gl_dict({
"account": item.expense_account,
"against": self.supplier,
"debit": flt(item.base_net_amount, item.precision("base_net_amount")),
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"cost_center": item.cost_center,
"project": item.project
}, account_currency, item=item)
)
else:
gl_entries.append(
@ -832,7 +837,8 @@ class PurchaseInvoice(BuyingController):
}, account_currency, item=tax)
)
# accumulate valuation tax
if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount):
if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount) \
and not self.is_internal_transfer():
if self.auto_accounting_for_stock and not tax.cost_center:
frappe.throw(_("Cost Center is required in row {0} in Taxes table for type {1}").format(tax.idx, _(tax.category)))
valuation_tax.setdefault(tax.name, 0)
@ -876,8 +882,19 @@ class PurchaseInvoice(BuyingController):
"against": self.supplier,
"credit": valuation_tax[tax.name],
"remarks": self.remarks or "Accounting Entry for Stock"
}, item=tax)
)
}, item=tax))
def make_internal_transfer_gl_entries(self, gl_entries):
if self.is_internal_transfer() and flt(self.base_total_taxes_and_charges):
account_currency = get_account_currency(self.unrealized_profit_loss_account)
gl_entries.append(
self.get_gl_dict({
"account": self.unrealized_profit_loss_account,
"against": self.supplier,
"credit": flt(self.total_taxes_and_charges),
"credit_in_account_currency": flt(self.base_total_taxes_and_charges),
"cost_center": self.cost_center
}, account_currency, item=self))
def make_payment_gl_entries(self, gl_entries):
# Make Cash GL Entries
@ -982,11 +999,15 @@ class PurchaseInvoice(BuyingController):
self.delete_auto_created_batches()
self.make_gl_entries_on_cancel()
if self.update_stock == 1:
self.repost_future_sle_and_gle()
self.update_project()
frappe.db.set(self, 'status', 'Cancelled')
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
def update_project(self):
project_list = []
@ -1095,7 +1116,9 @@ class PurchaseInvoice(BuyingController):
if self.docstatus == 2:
status = "Cancelled"
elif self.docstatus == 1:
if outstanding_amount > 0 and due_date < nowdate:
if self.is_internal_transfer():
self.status = 'Internal Transfer'
elif outstanding_amount > 0 and due_date < nowdate:
self.status = "Overdue"
elif outstanding_amount > 0 and due_date >= nowdate:
self.status = "Unpaid"

View File

@ -4,23 +4,25 @@
// render
frappe.listview_settings['Purchase Invoice'] = {
add_fields: ["supplier", "supplier_name", "base_grand_total", "outstanding_amount", "due_date", "company",
"currency", "is_return", "release_date", "on_hold"],
"currency", "is_return", "release_date", "on_hold", "represents_company", "is_internal_supplier"],
get_indicator: function(doc) {
if( (flt(doc.outstanding_amount) <= 0) && doc.docstatus == 1 && doc.status == 'Debit Note Issued') {
if ((flt(doc.outstanding_amount) <= 0) && doc.docstatus == 1 && doc.status == 'Debit Note Issued') {
return [__("Debit Note Issued"), "darkgrey", "outstanding_amount,<=,0"];
} else if(flt(doc.outstanding_amount) > 0 && doc.docstatus==1) {
} else if (flt(doc.outstanding_amount) > 0 && doc.docstatus==1) {
if(cint(doc.on_hold) && !doc.release_date) {
return [__("On Hold"), "darkgrey"];
} else if(cint(doc.on_hold) && doc.release_date && frappe.datetime.get_diff(doc.release_date, frappe.datetime.nowdate()) > 0) {
} else if (cint(doc.on_hold) && doc.release_date && frappe.datetime.get_diff(doc.release_date, frappe.datetime.nowdate()) > 0) {
return [__("Temporarily on Hold"), "darkgrey"];
} else if(frappe.datetime.get_diff(doc.due_date) < 0) {
} else if (frappe.datetime.get_diff(doc.due_date) < 0) {
return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<,Today"];
} else {
return [__("Unpaid"), "orange", "outstanding_amount,>,0|due_date,>=,Today"];
}
} else if(cint(doc.is_return)) {
} else if (cint(doc.is_return)) {
return [__("Return"), "darkgrey", "is_return,=,Yes"];
} else if(flt(doc.outstanding_amount)==0 && doc.docstatus==1) {
} else if (doc.company == doc.represents_company && doc.is_internal_supplier) {
return [__("Internal Transfer"), "darkgrey", "outstanding_amount,=,0"];
} else if (flt(doc.outstanding_amount)==0 && doc.docstatus==1) {
return [__("Paid"), "green", "outstanding_amount,=,0"];
}
}

View File

@ -9,8 +9,7 @@ import frappe.model
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from frappe.utils import cint, flt, today, nowdate, add_days, getdate
import frappe.defaults
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory, \
test_records as pr_test_records, make_purchase_receipt, get_taxes
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt, get_taxes
from erpnext.controllers.accounts_controller import get_payment_terms
from erpnext.exceptions import InvalidCurrency
from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_transaction
@ -33,13 +32,10 @@ class TestPurchaseInvoice(unittest.TestCase):
def test_gl_entries_without_perpetual_inventory(self):
frappe.db.set_value("Company", "_Test Company", "round_off_account", "Round Off - _TC")
wrapper = frappe.copy_doc(test_records[0])
set_perpetual_inventory(0, wrapper.company)
self.assertTrue(not cint(erpnext.is_perpetual_inventory_enabled(wrapper.company)))
wrapper.insert()
wrapper.submit()
wrapper.load_from_db()
dl = wrapper
pi = frappe.copy_doc(test_records[0])
self.assertTrue(not cint(erpnext.is_perpetual_inventory_enabled(pi.company)))
pi.insert()
pi.submit()
expected_gl_entries = {
"_Test Payable - _TC": [0, 1512.0],
@ -54,12 +50,16 @@ class TestPurchaseInvoice(unittest.TestCase):
"Round Off - _TC": [0, 0.3]
}
gl_entries = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
where voucher_type = 'Purchase Invoice' and voucher_no = %s""", dl.name, as_dict=1)
where voucher_type = 'Purchase Invoice' and voucher_no = %s""", pi.name, as_dict=1)
for d in gl_entries:
self.assertEqual([d.debit, d.credit], expected_gl_entries.get(d.account))
def test_gl_entries_with_perpetual_inventory(self):
pi = make_purchase_invoice(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1", get_taxes_and_charges=True, qty=10)
pi = make_purchase_invoice(company="_Test Company with perpetual inventory",
warehouse= "Stores - TCP1", cost_center = "Main - TCP1",
expense_account ="_Test Account Cost for Goods Sold - TCP1",
get_taxes_and_charges=True, qty=10)
self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pi.company)), 1)
self.check_gle_for_pi(pi.name)
@ -198,8 +198,6 @@ class TestPurchaseInvoice(unittest.TestCase):
pr = make_purchase_receipt(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", get_taxes_and_charges=True,)
self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pr.company)), 1)
pi = make_purchase_invoice(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1", get_taxes_and_charges=True, qty=10,do_not_save= "True")
for d in pi.items:
@ -247,17 +245,11 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertRaises(frappe.CannotChangeConstantError, pi.save)
def test_gl_entries_with_aia_for_non_stock_items(self):
pi = frappe.copy_doc(test_records[1])
set_perpetual_inventory(1, pi.company)
self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pi.company)), 1)
pi.get("items")[0].item_code = "_Test Non Stock Item"
pi.get("items")[0].expense_account = "_Test Account Cost for Goods Sold - _TC"
pi.get("taxes").pop(0)
pi.get("taxes").pop(1)
pi.insert()
pi.submit()
pi.load_from_db()
def test_gl_entries_for_non_stock_items_with_perpetual_inventory(self):
pi = make_purchase_invoice(item_code = "_Test Non Stock Item",
company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
self.assertTrue(pi.status, "Unpaid")
gl_entries = frappe.db.sql("""select account, debit, credit
@ -265,17 +257,15 @@ class TestPurchaseInvoice(unittest.TestCase):
order by account asc""", pi.name, as_dict=1)
self.assertTrue(gl_entries)
expected_values = sorted([
["_Test Payable - _TC", 0, 620],
["_Test Account Cost for Goods Sold - _TC", 500.0, 0],
["_Test Account VAT - _TC", 120.0, 0],
])
expected_values = [
["_Test Account Cost for Goods Sold - TCP1", 250.0, 0],
["Creditors - TCP1", 0, 250]
]
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[i][0], gle.account)
self.assertEqual(expected_values[i][1], gle.debit)
self.assertEqual(expected_values[i][2], gle.credit)
set_perpetual_inventory(0, pi.company)
def test_purchase_invoice_calculation(self):
pi = frappe.copy_doc(test_records[0])
@ -457,12 +447,13 @@ class TestPurchaseInvoice(unittest.TestCase):
pi.cancel()
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"), existing_purchase_cost)
def test_return_purchase_invoice(self):
set_perpetual_inventory()
def test_return_purchase_invoice_with_perpetual_inventory(self):
pi = make_purchase_invoice(company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
pi = make_purchase_invoice()
return_pi = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2)
return_pi = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2,
company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
# check gl entries for return
@ -473,19 +464,15 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertTrue(gl_entries)
expected_values = {
"Creditors - _TC": [100.0, 0.0],
"Stock Received But Not Billed - _TC": [0.0, 100.0],
"Creditors - TCP1": [100.0, 0.0],
"Stock Received But Not Billed - TCP1": [0.0, 100.0],
}
for gle in gl_entries:
self.assertEqual(expected_values[gle.account][0], gle.debit)
self.assertEqual(expected_values[gle.account][1], gle.credit)
set_perpetual_inventory(0)
def test_multi_currency_gle(self):
set_perpetual_inventory(0)
pi = make_purchase_invoice(supplier="_Test Supplier USD", credit_to="_Test Payable USD - _TC",
currency="USD", conversion_rate=50)
@ -640,10 +627,9 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertEqual(len(pi.get("supplied_items")), 2)
rm_supp_cost = sum([d.amount for d in pi.get("supplied_items")])
self.assertEqual(pi.get("items")[0].rm_supp_cost, flt(rm_supp_cost, 2))
self.assertEqual(flt(pi.get("items")[0].rm_supp_cost, 2), flt(rm_supp_cost, 2))
def test_rejected_serial_no(self):
set_perpetual_inventory(0)
pi = make_purchase_invoice(item_code="_Test Serialized Item With Series", received_qty=2, qty=1,
rejected_qty=1, rate=500, update_stock=1,
rejected_warehouse = "_Test Rejected Warehouse - _TC")

View File

@ -1,6 +1,8 @@
{% include "erpnext/regional/india/taxes.js" %}
{% include "erpnext/regional/india/e_invoice/einvoice.js" %}
erpnext.setup_auto_gst_taxation('Sales Invoice');
erpnext.setup_einvoice_actions('Sales Invoice')
frappe.ui.form.on("Sales Invoice", {
setup: function(frm) {

View File

@ -580,6 +580,16 @@ frappe.ui.form.on('Sales Invoice', {
};
});
frm.set_query("unrealized_profit_loss_account", function() {
return {
filters: {
company: frm.doc.company,
is_group: 0,
root_type: "Liability",
}
};
});
frm.custom_make_buttons = {
'Delivery Note': 'Delivery',
'Sales Invoice': 'Sales Return',

View File

@ -1,6 +1,5 @@
{
"actions": [],
"allow_auto_repeat": 1,
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-05-24 19:29:05",
@ -158,6 +157,7 @@
"more_information",
"inter_company_invoice_reference",
"is_internal_customer",
"represents_company",
"customer_group",
"campaign",
"is_discounted",
@ -171,6 +171,7 @@
"c_form_applicable",
"c_form_no",
"column_break8",
"unrealized_profit_loss_account",
"remarks",
"sales_team_section_break",
"sales_partner",
@ -1655,7 +1656,7 @@
"in_standard_filter": 1,
"label": "Status",
"no_copy": 1,
"options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled",
"options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled\nInternal Transfer",
"print_hide": 1,
"read_only": 1
},
@ -1950,13 +1951,31 @@
"fieldtype": "Data",
"label": "Company Tax ID",
"read_only": 1
},
{
"depends_on": "eval:doc.is_internal_customer",
"description": "Unrealized Profit / Loss account for intra-company transfers",
"fieldname": "unrealized_profit_loss_account",
"fieldtype": "Link",
"label": "Unrealized Profit / Loss Account",
"options": "Account"
},
{
"depends_on": "eval:doc.is_internal_customer",
"description": "Company which internal customer represents",
"fetch_from": "customer.represents_company",
"fieldname": "represents_company",
"fieldtype": "Link",
"label": "Represents Company",
"options": "Company",
"read_only": 1
}
],
"icon": "fa fa-file-text",
"idx": 181,
"is_submittable": 1,
"links": [],
"modified": "2020-10-30 13:57:45.086303",
"modified": "2020-12-11 12:48:31.769958",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@ -179,6 +179,9 @@ class SalesInvoice(SellingController):
# this sequence because outstanding may get -ve
self.make_gl_entries()
if self.update_stock == 1:
self.repost_future_sle_and_gle()
if not self.is_return:
self.update_billing_status_for_zero_amount_refdoc("Delivery Note")
@ -229,9 +232,9 @@ class SalesInvoice(SellingController):
frappe.throw(_("At least one mode of payment is required for POS invoice."))
def before_cancel(self):
super(SalesInvoice, self).before_cancel()
self.update_time_sheet(None)
def on_cancel(self):
super(SalesInvoice, self).on_cancel()
@ -258,6 +261,10 @@ class SalesInvoice(SellingController):
self.update_stock_ledger()
self.make_gl_entries_on_cancel()
if self.update_stock == 1:
self.repost_future_sle_and_gle()
frappe.db.set(self, 'status', 'Cancelled')
if frappe.db.get_single_value('Selling Settings', 'sales_update_frequency') == "Each Transaction":
@ -279,7 +286,7 @@ class SalesInvoice(SellingController):
if "Healthcare" in active_domains:
manage_invoice_submit_cancel(self, "on_cancel")
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
def update_status_updater_args(self):
if cint(self.update_stock):
@ -722,22 +729,20 @@ class SalesInvoice(SellingController):
if d.delivery_note and frappe.db.get_value("Delivery Note", d.delivery_note, "docstatus") != 1:
throw(_("Delivery Note {0} is not submitted").format(d.delivery_note))
def make_gl_entries(self, gl_entries=None):
from erpnext.accounts.general_ledger import make_reverse_gl_entries
def make_gl_entries(self, gl_entries=None, from_repost=False):
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries
auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
if not gl_entries:
gl_entries = self.get_gl_entries()
if gl_entries:
from erpnext.accounts.general_ledger import make_gl_entries
# if POS and amount is written off, updating outstanding amt after posting all gl entries
update_outstanding = "No" if (cint(self.is_pos) or self.write_off_account or
cint(self.redeem_loyalty_points)) else "Yes"
if self.docstatus == 1:
make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False)
make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False, from_repost=from_repost)
elif self.docstatus == 2:
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
@ -758,6 +763,7 @@ class SalesInvoice(SellingController):
self.make_customer_gl_entry(gl_entries)
self.make_tax_gl_entries(gl_entries)
self.make_internal_transfer_gl_entries(gl_entries)
self.make_item_gl_entries(gl_entries)
@ -777,7 +783,7 @@ class SalesInvoice(SellingController):
# Checked both rounding_adjustment and rounded_total
# because rounded_total had value even before introcution of posting GLE based on rounded total
grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
if grand_total:
if grand_total and not self.is_internal_transfer():
# Didnot use base_grand_total to book rounding loss gle
grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
self.precision("grand_total"))
@ -816,6 +822,18 @@ class SalesInvoice(SellingController):
}, account_currency, item=tax)
)
def make_internal_transfer_gl_entries(self, gl_entries):
if self.is_internal_transfer() and flt(self.base_total_taxes_and_charges):
account_currency = get_account_currency(self.unrealized_profit_loss_account)
gl_entries.append(
self.get_gl_dict({
"account": self.unrealized_profit_loss_account,
"against": self.customer,
"debit": flt(self.total_taxes_and_charges),
"debit_in_account_currency": flt(self.base_total_taxes_and_charges),
"cost_center": self.cost_center
}, account_currency, item=self))
def make_item_gl_entries(self, gl_entries):
# income account gl entries
for item in self.get("items"):
@ -838,22 +856,24 @@ class SalesInvoice(SellingController):
asset.db_set("disposal_date", self.posting_date)
asset.set_status("Sold" if self.docstatus==1 else None)
else:
income_account = (item.income_account
if (not item.enable_deferred_revenue or self.is_return) else item.deferred_revenue_account)
# Do not book income for transfer within same company
if not self.is_internal_transfer():
income_account = (item.income_account
if (not item.enable_deferred_revenue or self.is_return) else item.deferred_revenue_account)
account_currency = get_account_currency(income_account)
gl_entries.append(
self.get_gl_dict({
"account": income_account,
"against": self.customer,
"credit": flt(item.base_net_amount, item.precision("base_net_amount")),
"credit_in_account_currency": (flt(item.base_net_amount, item.precision("base_net_amount"))
if account_currency==self.company_currency
else flt(item.net_amount, item.precision("net_amount"))),
"cost_center": item.cost_center,
"project": item.project or self.project
}, account_currency, item=item)
)
account_currency = get_account_currency(income_account)
gl_entries.append(
self.get_gl_dict({
"account": income_account,
"against": self.customer,
"credit": flt(item.base_net_amount, item.precision("base_net_amount")),
"credit_in_account_currency": (flt(item.base_net_amount, item.precision("base_net_amount"))
if account_currency==self.company_currency
else flt(item.net_amount, item.precision("net_amount"))),
"cost_center": item.cost_center,
"project": item.project or self.project
}, account_currency, item=item)
)
# expense account gl entries
if cint(self.update_stock) and \
@ -1265,7 +1285,9 @@ class SalesInvoice(SellingController):
if self.docstatus == 2:
status = "Cancelled"
elif self.docstatus == 1:
if outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discountng_status=='Disbursed':
if self.is_internal_transfer():
self.status = 'Internal Transfer'
elif outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discountng_status=='Disbursed':
self.status = "Overdue and Discounted"
elif outstanding_amount > 0 and due_date < nowdate:
self.status = "Overdue"
@ -1530,9 +1552,13 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
if doctype in ["Sales Invoice", "Sales Order"]:
source_doc = frappe.get_doc(doctype, source_name)
target_doctype = "Purchase Invoice" if doctype == "Sales Invoice" else "Purchase Order"
source_document_warehouse_field = 'target_warehouse'
target_document_warehouse_field = 'from_warehouse'
else:
source_doc = frappe.get_doc(doctype, source_name)
target_doctype = "Sales Invoice" if doctype == "Purchase Invoice" else "Sales Order"
source_document_warehouse_field = 'from_warehouse'
target_document_warehouse_field = 'target_warehouse'
validate_inter_company_transaction(source_doc, doctype)
details = get_inter_company_details(source_doc, doctype)
@ -1559,6 +1585,26 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
if currency:
target_doc.currency = currency
item_field_map = {
"doctype": target_doctype + " Item",
"field_no_map": [
"income_account",
"expense_account",
"cost_center",
"warehouse"
]
}
if source_doc.get('update_stock'):
item_field_map.update({
'field_map': {
source_document_warehouse_field: target_document_warehouse_field,
'batch_no': 'batch_no',
'serial_no': 'serial_no'
}
})
doclist = get_mapped_doc(doctype, source_name, {
doctype: {
"doctype": target_doctype,
@ -1567,15 +1613,7 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
"taxes_and_charges"
]
},
doctype +" Item": {
"doctype": target_doctype + " Item",
"field_no_map": [
"income_account",
"expense_account",
"cost_center",
"warehouse"
]
}
doctype +" Item": item_field_map
}, target_doc, set_missing_values)

View File

@ -14,8 +14,8 @@ frappe.listview_settings['Sales Invoice'] = {
"Credit Note Issued": "darkgrey",
"Unpaid and Discounted": "orange",
"Overdue and Discounted": "red",
"Overdue": "red"
"Overdue": "red",
"Internal Transfer": "darkgrey"
};
return [__(doc.status), status_color[doc.status], "status,=,"+doc.status];
},

View File

@ -17,7 +17,8 @@
"description": "138-CMS Shoe",
"doctype": "Sales Invoice Item",
"income_account": "Sales - _TC",
"expense_account": "_Test Account Cost for Goods Sold - _TC",
"expense_account": "_Test Account Cost for Goods Sold - _TC",
"item_code": "138-CMS Shoe",
"item_name": "138-CMS Shoe",
"parentfield": "items",
"qty": 1.0,

View File

@ -10,7 +10,6 @@ from frappe.model.dynamic_links import get_dynamic_link_map
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError
from frappe.model.naming import make_autoname
@ -659,7 +658,6 @@ class TestSalesInvoice(unittest.TestCase):
def test_sales_invoice_gl_entry_without_perpetual_inventory(self):
si = frappe.copy_doc(test_records[1])
set_perpetual_inventory(0, si.company)
si.insert()
si.submit()
@ -815,7 +813,6 @@ class TestSalesInvoice(unittest.TestCase):
frappe.db.sql("delete from `tabPOS Profile`")
def test_pos_si_without_payment(self):
set_perpetual_inventory()
make_pos_profile()
pos = copy.deepcopy(test_records[1])
@ -829,9 +826,8 @@ class TestSalesInvoice(unittest.TestCase):
self.assertRaises(frappe.ValidationError, si.submit)
def test_sales_invoice_gl_entry_with_perpetual_inventory_no_item_code(self):
set_perpetual_inventory()
si = frappe.get_doc(test_records[1])
si = create_sales_invoice(company="_Test Company with perpetual inventory", debit_to = "Debtors - TCP1",
income_account="Sales - TCP1", cost_center = "Main - TCP1", do_not_save=True)
si.get("items")[0].item_code = None
si.insert()
si.submit()
@ -842,24 +838,16 @@ class TestSalesInvoice(unittest.TestCase):
self.assertTrue(gl_entries)
expected_values = dict((d[0], d) for d in [
[si.debit_to, 630.0, 0.0],
[test_records[1]["items"][0]["income_account"], 0.0, 500.0],
[test_records[1]["taxes"][0]["account_head"], 0.0, 80.0],
[test_records[1]["taxes"][1]["account_head"], 0.0, 50.0],
["Debtors - TCP1", 100.0, 0.0],
["Sales - TCP1", 0.0, 100.0]
])
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account][0], gle.account)
self.assertEqual(expected_values[gle.account][1], gle.debit)
self.assertEqual(expected_values[gle.account][2], gle.credit)
set_perpetual_inventory(0)
def test_sales_invoice_gl_entry_with_perpetual_inventory_non_stock_item(self):
set_perpetual_inventory()
si = frappe.get_doc(test_records[1])
si.get("items")[0].item_code = "_Test Non Stock Item"
si.insert()
si.submit()
si = create_sales_invoice(item="_Test Non Stock Item")
gl_entries = frappe.db.sql("""select account, debit, credit
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
@ -867,17 +855,14 @@ class TestSalesInvoice(unittest.TestCase):
self.assertTrue(gl_entries)
expected_values = dict((d[0], d) for d in [
[si.debit_to, 630.0, 0.0],
[test_records[1]["items"][0]["income_account"], 0.0, 500.0],
[test_records[1]["taxes"][0]["account_head"], 0.0, 80.0],
[test_records[1]["taxes"][1]["account_head"], 0.0, 50.0],
[si.debit_to, 100.0, 0.0],
[test_records[1]["items"][0]["income_account"], 0.0, 100.0]
])
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account][0], gle.account)
self.assertEqual(expected_values[gle.account][1], gle.debit)
self.assertEqual(expected_values[gle.account][2], gle.credit)
set_perpetual_inventory(0)
def _insert_purchase_receipt(self):
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import test_records \
@ -1106,7 +1091,6 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(si.grand_total, 859.43)
def test_multi_currency_gle(self):
set_perpetual_inventory(0)
si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
currency="USD", conversion_rate=50)
@ -1573,7 +1557,7 @@ class TestSalesInvoice(unittest.TestCase):
for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
def test_sales_invoice_with_project_link(self):
from erpnext.projects.doctype.project.test_project import make_project
@ -1607,9 +1591,9 @@ class TestSalesInvoice(unittest.TestCase):
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
order by account asc""", sales_invoice.name, as_dict=1)
self.assertTrue(gl_entries)
for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["project"], gle.project)
@ -1776,99 +1760,72 @@ class TestSalesInvoice(unittest.TestCase):
si.submit()
target_doc = make_inter_company_transaction("Sales Invoice", si.name)
target_doc.items[0].update({
"expense_account": "Cost of Goods Sold - _TC1",
"cost_center": "Main - _TC1",
"warehouse": "Stores - _TC1"
})
target_doc.submit()
self.assertEqual(target_doc.company, "_Test Company 1")
self.assertEqual(target_doc.supplier, "_Test Internal Supplier")
# def test_internal_transfer_gl_entry(self):
# ## Create internal transfer account
# account = create_account(account_name="Unrealized Profit",
# parent_account="Current Liabilities - TCP1", company="_Test Company with perpetual inventory")
# frappe.db.set_value('Company', '_Test Company with perpetual inventory',
# 'unrealized_profit_loss_account', account)
# customer = create_internal_customer("_Test Internal Customer 2", "_Test Company with perpetual inventory",
# "_Test Company with perpetual inventory")
# create_internal_supplier("_Test Internal Supplier 2", "_Test Company with perpetual inventory",
# "_Test Company with perpetual inventory")
# si = create_sales_invoice(
# company = "_Test Company with perpetual inventory",
# customer = customer,
# debit_to = "Debtors - TCP1",
# warehouse = "Stores - TCP1",
# income_account = "Sales - TCP1",
# expense_account = "Cost of Goods Sold - TCP1",
# cost_center = "Main - TCP1",
# currency = "INR",
# do_not_save = 1
# )
# si.selling_price_list = "_Test Price List Rest of the World"
# si.update_stock = 1
# si.items[0].target_warehouse = 'Work In Progress - TCP1'
# add_taxes(si)
# si.save()
# si.submit()
# target_doc = make_inter_company_transaction("Sales Invoice", si.name)
# target_doc.company = '_Test Company with perpetual inventory'
# target_doc.items[0].warehouse = 'Finished Goods - TCP1'
# add_taxes(target_doc)
# target_doc.save()
# target_doc.submit()
# si_gl_entries = [
# ["_Test Account Excise Duty - TCP1", 0.0, 12.0, nowdate()],
# ["Unrealized Profit - TCP1", 12.0, 0.0, nowdate()]
# ]
# check_gl_entries(self, si.name, si_gl_entries, add_days(nowdate(), -1))
# pi_gl_entries = [
# ["_Test Account Excise Duty - TCP1", 12.0 , 0.0, nowdate()],
# ["Unrealized Profit - TCP1", 0.0, 12.0, nowdate()]
# ]
# check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1))
def test_eway_bill_json(self):
if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
address = frappe.get_doc({
"address_line1": "_Test Address Line 1",
"address_title": "_Test Address for Eway bill",
"address_type": "Billing",
"city": "_Test City",
"state": "Test State",
"country": "India",
"doctype": "Address",
"is_primary_address": 1,
"phone": "+91 0000000000",
"gstin": "27AAECE4835E1ZR",
"gst_state": "Maharashtra",
"gst_state_number": "27",
"pincode": "401108"
}).insert()
address.append("links", {
"link_doctype": "Company",
"link_name": "_Test Company"
})
address.save()
if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Shipping'):
address = frappe.get_doc({
"address_line1": "_Test Address Line 1",
"address_title": "_Test Customer-Address for Eway bill",
"address_type": "Shipping",
"city": "_Test City",
"state": "Test State",
"country": "India",
"doctype": "Address",
"is_primary_address": 1,
"phone": "+91 0000000000",
"gst_state": "Maharashtra",
"gst_state_number": "27",
"pincode": "410038"
}).insert()
address.append("links", {
"link_doctype": "Customer",
"link_name": "_Test Customer"
})
address.save()
gst_settings = frappe.get_doc("GST Settings")
gst_account = frappe.get_all(
"GST Account",
fields=["cgst_account", "sgst_account", "igst_account"],
filters = {"company": "_Test Company"})
if not gst_account:
gst_settings.append("gst_accounts", {
"company": "_Test Company",
"cgst_account": "CGST - _TC",
"sgst_account": "SGST - _TC",
"igst_account": "IGST - _TC",
})
gst_settings.save()
si = create_sales_invoice(do_not_save =1, rate = '60000')
si.distance = 2000
si.company_address = "_Test Address for Eway bill-Billing"
si.customer_address = "_Test Customer-Address for Eway bill-Shipping"
si.vehicle_no = "KA12KA1234"
si.gst_category = "Registered Regular"
si.append("taxes", {
"charge_type": "On Net Total",
"account_head": "CGST - _TC",
"cost_center": "Main - _TC",
"description": "CGST @ 9.0",
"rate": 9
})
si.append("taxes", {
"charge_type": "On Net Total",
"account_head": "SGST - _TC",
"cost_center": "Main - _TC",
"description": "SGST @ 9.0",
"rate": 9
})
si = make_sales_invoice_for_ewaybill()
si.submit()
@ -1884,6 +1841,214 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(data['billLists'][0]['sgstValue'], 5400)
self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234')
self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000)
def test_einvoice_submission_without_irn(self):
# init
frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 1)
country = frappe.flags.country
frappe.flags.country = 'India'
si = make_sales_invoice_for_ewaybill()
self.assertRaises(frappe.ValidationError, si.submit)
si.irn = 'test_irn'
si.submit()
# reset
frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 0)
frappe.flags.country = country
def test_einvoice_json(self):
from erpnext.regional.india.e_invoice.utils import make_einvoice
customer_gstin = '27AACCM7806M1Z3'
customer_gstin_dtls = {
'LegalName': '_Test Customer', 'TradeName': '_Test Customer', 'AddrLoc': '_Test City',
'StateCode': '27', 'AddrPncd': '410038', 'AddrBno': '_Test Bldg',
'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street'
}
company_gstin = '27AAECE4835E1ZR'
company_gstin_dtls = {
'LegalName': '_Test Company', 'TradeName': '_Test Company', 'AddrLoc': '_Test City',
'StateCode': '27', 'AddrPncd': '401108', 'AddrBno': '_Test Bldg',
'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street'
}
# set cache gstin details to avoid fetching details which will require connection to GSP servers
frappe.local.gstin_cache = {}
frappe.local.gstin_cache[customer_gstin] = customer_gstin_dtls
frappe.local.gstin_cache[company_gstin] = company_gstin_dtls
si = make_sales_invoice_for_ewaybill()
si.naming_series = 'INV-2020-.#####'
si.items = []
si.append("items", {
"item_code": "_Test Item",
"uom": "Nos",
"warehouse": "_Test Warehouse - _TC",
"qty": 2000,
"rate": 12,
"income_account": "Sales - _TC",
"expense_account": "Cost of Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC",
})
si.append("items", {
"item_code": "_Test Item 2",
"uom": "Nos",
"warehouse": "_Test Warehouse - _TC",
"qty": 420,
"rate": 15,
"income_account": "Sales - _TC",
"expense_account": "Cost of Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC",
})
si.discount_amount = 100
si.save()
einvoice = make_einvoice(si)
total_item_ass_value = 0
total_item_cgst_value = 0
total_item_sgst_value = 0
total_item_igst_value = 0
total_item_value = 0
for item in einvoice['ItemList']:
total_item_ass_value += item['AssAmt']
total_item_cgst_value += item['CgstAmt']
total_item_sgst_value += item['SgstAmt']
total_item_igst_value += item['IgstAmt']
total_item_value += item['TotItemVal']
self.assertTrue(item['AssAmt'], item['TotAmt'] - item['Discount'])
self.assertTrue(item['TotItemVal'], item['AssAmt'] + item['CgstAmt'] + item['SgstAmt'] + item['IgstAmt'])
value_details = einvoice['ValDtls']
self.assertEqual(einvoice['Version'], '1.1')
self.assertEqual(value_details['AssVal'], total_item_ass_value)
self.assertEqual(value_details['CgstVal'], total_item_cgst_value)
self.assertEqual(value_details['SgstVal'], total_item_sgst_value)
self.assertEqual(value_details['IgstVal'], total_item_igst_value)
self.assertEqual(
value_details['TotInvVal'],
value_details['AssVal'] + value_details['CgstVal']
+ value_details['SgstVal'] + value_details['IgstVal']
+ value_details['OthChrg'] - value_details['Discount']
)
self.assertEqual(value_details['TotInvVal'], si.base_grand_total)
self.assertTrue(einvoice['EwbDtls'])
def make_test_address_for_ewaybill():
if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
address = frappe.get_doc({
"address_line1": "_Test Address Line 1",
"address_title": "_Test Address for Eway bill",
"address_type": "Billing",
"city": "_Test City",
"state": "Test State",
"country": "India",
"doctype": "Address",
"is_primary_address": 1,
"phone": "+910000000000",
"gstin": "27AAECE4835E1ZR",
"gst_state": "Maharashtra",
"gst_state_number": "27",
"pincode": "401108"
}).insert()
address.append("links", {
"link_doctype": "Company",
"link_name": "_Test Company"
})
address.save()
if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Shipping'):
address = frappe.get_doc({
"address_line1": "_Test Address Line 1",
"address_title": "_Test Customer-Address for Eway bill",
"address_type": "Shipping",
"city": "_Test City",
"state": "Test State",
"country": "India",
"doctype": "Address",
"is_primary_address": 1,
"phone": "+910000000000",
"gstin": "27AACCM7806M1Z3",
"gst_state": "Maharashtra",
"gst_state_number": "27",
"pincode": "410038"
}).insert()
address.append("links", {
"link_doctype": "Customer",
"link_name": "_Test Customer"
})
address.save()
def make_test_transporter_for_ewaybill():
if not frappe.db.exists('Supplier', '_Test Transporter'):
frappe.get_doc({
"doctype": "Supplier",
"supplier_name": "_Test Transporter",
"country": "India",
"supplier_group": "_Test Supplier Group",
"supplier_type": "Company",
"is_transporter": 1
}).insert()
def make_sales_invoice_for_ewaybill():
make_test_address_for_ewaybill()
make_test_transporter_for_ewaybill()
gst_settings = frappe.get_doc("GST Settings")
gst_account = frappe.get_all(
"GST Account",
fields=["cgst_account", "sgst_account", "igst_account"],
filters = {"company": "_Test Company"}
)
if not gst_account:
gst_settings.append("gst_accounts", {
"company": "_Test Company",
"cgst_account": "CGST - _TC",
"sgst_account": "SGST - _TC",
"igst_account": "IGST - _TC",
})
gst_settings.save()
si = create_sales_invoice(do_not_save=1, rate='60000')
si.distance = 2000
si.company_address = "_Test Address for Eway bill-Billing"
si.customer_address = "_Test Customer-Address for Eway bill-Shipping"
si.vehicle_no = "KA12KA1234"
si.gst_category = "Registered Regular"
si.mode_of_transport = 'Road'
si.transporter = '_Test Transporter'
si.append("taxes", {
"charge_type": "On Net Total",
"account_head": "CGST - _TC",
"cost_center": "Main - _TC",
"description": "CGST @ 9.0",
"rate": 9
})
si.append("taxes", {
"charge_type": "On Net Total",
"account_head": "SGST - _TC",
"cost_center": "Main - _TC",
"description": "SGST @ 9.0",
"rate": 9
})
return si
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
@ -1937,14 +2102,19 @@ def create_sales_invoice(**args):
si.append("items", {
"item_code": args.item or args.item_code or "_Test Item",
"item_name": args.item_name or "_Test Item",
"description": args.description or "_Test Item",
"gst_hsn_code": "999800",
"warehouse": args.warehouse or "_Test Warehouse - _TC",
"qty": args.qty or 1,
"uom": args.uom or "Nos",
"stock_uom": args.uom or "Nos",
"rate": args.rate if args.get("rate") is not None else 100,
"income_account": args.income_account or "Sales - _TC",
"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
"cost_center": args.cost_center or "_Test Cost Center - _TC",
"serial_no": args.serial_no
"serial_no": args.serial_no,
"conversion_factor": 1
})
if not args.do_not_save:
@ -2039,4 +2209,57 @@ def get_taxes_and_charges():
"parentfield": "taxes",
"rate": 2,
"row_id": 1
}]
}]
def create_internal_customer(customer_name, represents_company, allowed_to_interact_with):
if not frappe.db.exists("Customer", customer_name):
customer = frappe.get_doc({
"customer_group": "_Test Customer Group",
"customer_name": customer_name,
"customer_type": "Individual",
"doctype": "Customer",
"territory": "_Test Territory",
"is_internal_customer": 1,
"represents_company": represents_company
})
customer.append("companies", {
"company": allowed_to_interact_with
})
customer.insert()
customer_name = customer.name
else:
customer_name = frappe.db.get_value("Customer", customer_name)
return customer_name
def create_internal_supplier(supplier_name, represents_company, allowed_to_interact_with):
if not frappe.db.exists("Supplier", supplier_name):
supplier = frappe.get_doc({
"supplier_group": "_Test Supplier Group",
"supplier_name": supplier_name,
"doctype": "Supplier",
"is_internal_supplier": 1,
"represents_company": represents_company
})
supplier.append("companies", {
"company": allowed_to_interact_with
})
supplier.insert()
supplier_name = supplier.name
else:
supplier_name = frappe.db.exists("Supplier", supplier_name)
return supplier_name
def add_taxes(doc):
doc.append('taxes', {
'account_head': '_Test Account Excise Duty - TCP1',
"charge_type": "On Net Total",
"cost_center": "Main - TCP1",
"description": "Excise Duty",
"rate": 12
})

View File

@ -1,4 +1,5 @@
{
"actions": [],
"autoname": "hash",
"creation": "2013-06-04 11:02:19",
"doctype": "DocType",
@ -51,6 +52,7 @@
"column_break_24",
"base_net_rate",
"base_net_amount",
"incoming_rate",
"drop_ship",
"delivered_by_supplier",
"accounting",
@ -792,20 +794,28 @@
"options": "Project"
},
{
"depends_on": "eval:parent.update_stock == 1",
"fieldname": "sales_invoice_item",
"fieldtype": "Data",
"ignore_user_permissions": 1,
"label": "Sales Invoice Item",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
}
"depends_on": "eval:parent.update_stock == 1",
"fieldname": "sales_invoice_item",
"fieldtype": "Data",
"ignore_user_permissions": 1,
"label": "Sales Invoice Item",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "incoming_rate",
"fieldtype": "Currency",
"label": "Incoming Rate",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2020-08-20 11:24:41.749986",
"modified": "2020-09-23 19:59:04.879322",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",

View File

@ -5,23 +5,19 @@ from __future__ import unicode_literals
import frappe, erpnext
from frappe.utils import flt, cstr, cint, comma_and, today, getdate, formatdate, now
from frappe import _
from erpnext.accounts.utils import get_stock_and_account_balance
from frappe.model.meta import get_field_precision
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
class ClosedAccountingPeriod(frappe.ValidationError): pass
class StockAccountInvalidTransaction(frappe.ValidationError): pass
class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass
def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes'):
def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes', from_repost=False):
if gl_map:
if not cancel:
validate_accounting_period(gl_map)
gl_map = process_gl_map(gl_map, merge_entries)
if gl_map and len(gl_map) > 1:
save_entries(gl_map, adv_adj, update_outstanding)
save_entries(gl_map, adv_adj, update_outstanding, from_repost)
else:
frappe.throw(_("Incorrect number of General Ledger Entries found. You might have selected a wrong Account in the transaction."))
else:
@ -119,8 +115,9 @@ def check_if_in_list(gle, gl_map, dimensions=None):
if same_head:
return e
def save_entries(gl_map, adv_adj, update_outstanding):
validate_cwip_accounts(gl_map)
def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
if not from_repost:
validate_cwip_accounts(gl_map)
round_off_debit_credit(gl_map)
@ -128,76 +125,19 @@ def save_entries(gl_map, adv_adj, update_outstanding):
check_freezing_date(gl_map[0]["posting_date"], adv_adj)
for entry in gl_map:
make_entry(entry, adv_adj, update_outstanding)
make_entry(entry, adv_adj, update_outstanding, from_repost)
# check against budget
validate_expense_against_budget(entry)
validate_account_for_perpetual_inventory(gl_map)
def make_entry(args, adv_adj, update_outstanding):
def make_entry(args, adv_adj, update_outstanding, from_repost=False):
gle = frappe.new_doc("GL Entry")
gle.update(args)
gle.flags.ignore_permissions = 1
gle.flags.from_repost = from_repost
gle.insert()
gle.run_method("on_update_with_args", adv_adj, update_outstanding)
gle.run_method("on_update_with_args", adv_adj, update_outstanding, from_repost)
gle.submit()
# check against budget
validate_expense_against_budget(args)
def validate_account_for_perpetual_inventory(gl_map):
if cint(erpnext.is_perpetual_inventory_enabled(gl_map[0].company)):
account_list = [gl_entries.account for gl_entries in gl_map]
aii_accounts = [d.name for d in frappe.get_all("Account",
filters={'account_type': 'Stock', 'is_group': 0, 'company': gl_map[0].company})]
for account in account_list:
if account not in aii_accounts:
continue
# Always use current date to get stock and account balance as there can future entries for
# other items
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(account,
getdate(), gl_map[0].company)
if gl_map[0].voucher_type=="Journal Entry":
# In case of Journal Entry, there are no corresponding SL entries,
# hence deducting currency amount
account_bal -= flt(gl_map[0].debit) - flt(gl_map[0].credit)
if account_bal == stock_bal:
frappe.throw(_("Account: {0} can only be updated via Stock Transactions")
.format(account), StockAccountInvalidTransaction)
elif abs(account_bal - stock_bal) > 0.1:
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency"))
diff = flt(stock_bal - account_bal, precision)
error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses.").format(
stock_bal, account_bal, frappe.bold(account))
error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(diff))
stock_adjustment_account = frappe.db.get_value("Company",gl_map[0].company,"stock_adjustment_account")
db_or_cr_warehouse_account =('credit_in_account_currency' if diff < 0 else 'debit_in_account_currency')
db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if diff < 0 else 'credit_in_account_currency')
journal_entry_args = {
'accounts':[
{'account': account, db_or_cr_warehouse_account : abs(diff)},
{'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff) }]
}
frappe.msgprint(msg="""{0}<br></br>{1}<br></br>""".format(error_reason, error_resolution),
raise_exception=StockValueAndAccountBalanceOutOfSync,
title=_('Values Out Of Sync'),
primary_action={
'label': _('Make Journal Entry'),
'client_action': 'erpnext.route_to_adjustment_jv',
'args': journal_entry_args
})
if not from_repost:
validate_expense_against_budget(args)
def validate_cwip_accounts(gl_map):
cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")])

View File

@ -0,0 +1,162 @@
{%- from "templates/print_formats/standard_macros.html" import add_header, render_field, print_value -%}
{%- set einvoice = json.loads(doc.signed_einvoice) -%}
<div class="page-break">
<div {% if print_settings.repeat_header_footer %} id="header-html" class="hidden-pdf" {% endif %}>
{% if letter_head and not no_letterhead %}
<div class="letter-head">{{ letter_head }}</div>
{% endif %}
<div class="print-heading">
<h2>E Invoice<br><small>{{ doc.name }}</small></h2>
</div>
</div>
{% if print_settings.repeat_header_footer %}
<div id="footer-html" class="visible-pdf">
{% if not no_letterhead and footer %}
<div class="letter-head-footer">
{{ footer }}
</div>
{% endif %}
<p class="text-center small page-number visible-pdf">
{{ _("Page {0} of {1}").format('<span class="page"></span>', '<span class="topage"></span>') }}
</p>
</div>
{% endif %}
<div class="row section-break" style="border-bottom: 1px solid #d1d8dd; padding-bottom: 10px;">
<h5 class="font-bold" style="margin-left: 15px; margin-top: 0px;">1. Transaction Details</h5>
<div class="col-xs-8 column-break">
<div class="row data-field">
<div class="col-xs-4"><label>IRN</label></div>
<div class="col-xs-8 value">{{ einvoice.Irn }}</div>
</div>
<div class="row data-field">
<div class="col-xs-4"><label>Ack. No</label></div>
<div class="col-xs-8 value">{{ einvoice.AckNo }}</div>
</div>
<div class="row data-field">
<div class="col-xs-4"><label>Ack. Date</label></div>
<div class="col-xs-8 value">{{ frappe.utils.format_datetime(einvoice.AckDt, "dd/MM/yyyy hh:mm:ss") }}</div>
</div>
<div class="row data-field">
<div class="col-xs-4"><label>Category</label></div>
<div class="col-xs-8 value">{{ einvoice.TranDtls.SupTyp }}</div>
</div>
<div class="row data-field">
<div class="col-xs-4"><label>Document Type</label></div>
<div class="col-xs-8 value">{{ einvoice.DocDtls.Typ }}</div>
</div>
<div class="row data-field">
<div class="col-xs-4"><label>Document No</label></div>
<div class="col-xs-8 value">{{ einvoice.DocDtls.No }}</div>
</div>
</div>
<div class="col-xs-4 column-break">
<img src="{{ doc.qrcode_image }}" width="175px" style="float: right;">
</div>
</div>
<div class="row section-break" style="border-bottom: 1px solid #d1d8dd; padding-bottom: 10px;">
<h5 class="font-bold" style="margin-left: 15px; margin-bottom: 0px;">2. Party Details</h5>
{%- set seller = einvoice.SellerDtls -%}
<div class="col-xs-6 column-break">
<h5 style="margin-bottom: 5px;">Seller</h5>
<p>{{ seller.Gstin }}</p>
<p>{{ seller.LglNm }}</p>
<p>{{ seller.Addr1 }}</p>
{%- if seller.Addr2 -%} <p>{{ seller.Addr2 }}</p> {% endif %}
<p>{{ seller.Loc }}</p>
<p>{{ frappe.db.get_value("Address", doc.company_address, "gst_state") }} - {{ seller.Pin }}</p>
{%- if einvoice.ShipDtls -%}
{%- set shipping = einvoice.ShipDtls -%}
<h5 style="margin-bottom: 5px;">Shipping</h5>
<p>{{ shipping.Gstin }}</p>
<p>{{ shipping.LglNm }}</p>
<p>{{ shipping.Addr1 }}</p>
{%- if shipping.Addr2 -%} <p>{{ shipping.Addr2 }}</p> {% endif %}
<p>{{ shipping.Loc }}</p>
<p>{{ frappe.db.get_value("Address", doc.shipping_address_name, "gst_state") }} - {{ shipping.Pin }}</p>
{% endif %}
</div>
{%- set buyer = einvoice.BuyerDtls -%}
<div class="col-xs-6 column-break">
<h5 style="margin-bottom: 5px;">Buyer</h5>
<p>{{ buyer.Gstin }}</p>
<p>{{ buyer.LglNm }}</p>
<p>{{ buyer.Addr1 }}</p>
{%- if buyer.Addr2 -%} <p>{{ buyer.Addr2 }}</p> {% endif %}
<p>{{ buyer.Loc }}</p>
<p>{{ frappe.db.get_value("Address", doc.customer_address, "gst_state") }} - {{ buyer.Pin }}</p>
</div>
</div>
<div style="overflow-x: auto;">
<h5 class="font-bold" style="margin-bottom: 0px;">3. Item Details</h5>
<table class="table table-bordered">
<thead>
<tr>
<th class="text-left" style="width: 3%;">Sr. No.</th>
<th class="text-left">Item</th>
<th class="text-left" style="width: 10%;">HSN Code</th>
<th class="text-left" style="width: 5%;">Qty</th>
<th class="text-left" style="width: 5%;">UOM</th>
<th class="text-left">Rate</th>
<th class="text-left" style="width: 5%;">Discount</th>
<th class="text-left">Taxable Amount</th>
<th class="text-left" style="width: 7%;">Tax Rate</th>
<th class="text-left" style="width: 5%;">Other Charges</th>
<th class="text-left">Total</th>
</tr>
</thead>
<tbody>
{% for item in einvoice.ItemList %}
<tr>
<td class="text-left" style="width: 3%;">{{ item.SlNo }}</td>
<td class="text-left">{{ item.PrdDesc }}</td>
<td class="text-left" style="width: 10%;">{{ item.HsnCd }}</td>
<td class="text-right" style="width: 5%;">{{ item.Qty }}</td>
<td class="text-left" style="width: 5%;">{{ item.Unit }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(item.UnitPrice, None, "INR") }}</td>
<td class="text-right" style="width: 5%;">{{ frappe.utils.fmt_money(item.Discount, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(item.AssAmt, None, "INR") }}</td>
<td class="text-right" style="width: 7%;">{{ item.GstRt + item.CesRt }} %</td>
<td class="text-right" style="width: 5%;">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(item.TotItemVal, None, "INR") }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div style="overflow-x: auto;">
<h5 class="font-bold" style="margin-bottom: 0px;">4. Value Details</h5>
<table class="table table-bordered">
<thead>
<tr>
<th class="text-left">Taxable Amount</th>
<th class="text-left">CGST</th>
<th class="text-left"">SGST</th>
<th class="text-left">IGST</th>
<th class="text-left">CESS</th>
<th class="text-left" style="width: 10%;">State CESS</th>
<th class="text-left">Discount</th>
<th class="text-left" style="width: 10%;">Other Charges</th>
<th class="text-left" style="width: 10%;">Round Off</th>
<th class="text-left">Total Value</th>
</tr>
</thead>
<tbody>
{%- set value_details = einvoice.ValDtls -%}
<tr>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.AssVal, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.CgstVal, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.SgstVal, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.IgstVal, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.CesVal, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.Discount, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.RndOffAmt, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.TotInvVal, None, "INR") }}</td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@ -0,0 +1,24 @@
{
"align_labels_right": 1,
"creation": "2020-10-10 18:01:21.032914",
"custom_format": 0,
"default_print_language": "en-US",
"disabled": 1,
"doc_type": "Sales Invoice",
"docstatus": 0,
"doctype": "Print Format",
"font": "Default",
"html": "",
"idx": 0,
"line_breaks": 1,
"modified": "2020-10-23 19:54:40.634936",
"modified_by": "Administrator",
"module": "Accounts",
"name": "GST E-Invoice",
"owner": "Administrator",
"print_format_builder": 0,
"print_format_type": "Jinja",
"raw_printing": 0,
"show_section_headings": 1,
"standard": "Yes"
}

View File

@ -42,11 +42,13 @@
{% if(filters.show_future_payments) { %}
{% var balance_row = data.slice(-1).pop();
var range1 = report.columns[11].label;
var range2 = report.columns[12].label;
var range3 = report.columns[13].label;
var range4 = report.columns[14].label;
var range5 = report.columns[15].label;
var start = filters.based_on_payment_terms ? 13 : 11;
var range1 = report.columns[start].label;
var range2 = report.columns[start+1].label;
var range3 = report.columns[start+2].label;
var range4 = report.columns[start+3].label;
var range5 = report.columns[start+4].label;
var range6 = report.columns[start+5].label;
%}
{% if(balance_row) { %}
<table class="table table-bordered table-condensed">
@ -70,20 +72,34 @@
<th>{%= __(range3) %}</th>
<th>{%= __(range4) %}</th>
<th>{%= __(range5) %}</th>
<th>{%= __(range6) %}</th>
<th>{%= __("Total") %}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{%= __("Total Outstanding") %}</td>
<td class="text-right">{%= format_number(balance_row["range1"], null, 2) %}</td>
<td class="text-right">{%= format_currency(balance_row["range2"]) %}</td>
<td class="text-right">{%= format_currency(balance_row["range3"]) %}</td>
<td class="text-right">{%= format_currency(balance_row["range4"]) %}</td>
<td class="text-right">{%= format_currency(balance_row["range5"]) %}</td>
<td class="text-right">
{%= format_number(balance_row["age"], null, 2) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range1"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range2"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range3"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range4"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range5"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) %}
</td>
</td>
</tr>
<td>{%= __("Future Payments") %}</td>
<td></td>
@ -91,6 +107,7 @@
<td></td>
<td></td>
<td></td>
<td></td>
<td class="text-right">
{%= format_currency(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) %}
</td>
@ -101,6 +118,7 @@
<th></th>
<th></th>
<th></th>
<th></th>
<th class="text-right">
{%= format_currency(flt(balance_row["outstanding"] - balance_row[("future_amount")]), data[data.length-1]["currency"]) %}</th>
</tr>
@ -218,15 +236,15 @@
<td></td>
<td style="text-align: right"><b>{%= __("Total") %}</b></td>
<td style="text-align: right">
{%= format_currency(data[i]["invoiced"], data[0]["currency"] ) %}</td>
{%= format_currency(data[i]["invoiced"], data[i]["currency"] ) %}</td>
{% if(!filters.show_future_payments) { %}
<td style="text-align: right">
{%= format_currency(data[i]["paid"], data[0]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[0]["currency"]) %} </td>
{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %} </td>
{% } %}
<td style="text-align: right">
{%= format_currency(data[i]["outstanding"], data[0]["currency"]) %}</td>
{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
{% if(filters.show_future_payments) { %}
{% if(report.report_name === "Accounts Receivable") { %}
@ -234,8 +252,8 @@
{%= data[i]["po_no"] %}</td>
{% } %}
<td style="text-align: right">{%= data[i]["future_ref"] %}</td>
<td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[0]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[0]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %}</td>
{% } %}
{% } %}
{% } else { %}
@ -256,10 +274,10 @@
{% } else { %}
<td><b>{%= __("Total") %}</b></td>
{% } %}
<td style="text-align: right">{%= format_currency(data[i]["invoiced"], data[0]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["paid"], data[0]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[0]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["outstanding"], data[0]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
{% } %}
{% } %}
</tr>

View File

@ -47,21 +47,22 @@ def get_data(filters):
for d in gl_entries:
asset_data = assets_details.get(d.against_voucher)
if not asset_data.get("accumulated_depreciation_amount"):
asset_data.accumulated_depreciation_amount = d.debit
else:
asset_data.accumulated_depreciation_amount += d.debit
if asset_data:
if not asset_data.get("accumulated_depreciation_amount"):
asset_data.accumulated_depreciation_amount = d.debit
else:
asset_data.accumulated_depreciation_amount += d.debit
row = frappe._dict(asset_data)
row.update({
"depreciation_amount": d.debit,
"depreciation_date": d.posting_date,
"amount_after_depreciation": (flt(row.gross_purchase_amount) -
flt(row.accumulated_depreciation_amount)),
"depreciation_entry": d.voucher_no
})
row = frappe._dict(asset_data)
row.update({
"depreciation_amount": d.debit,
"depreciation_date": d.posting_date,
"amount_after_depreciation": (flt(row.gross_purchase_amount) -
flt(row.accumulated_depreciation_amount)),
"depreciation_entry": d.voucher_no
})
data.append(row)
data.append(row)
return data

View File

@ -8,6 +8,7 @@ from frappe.utils import flt
from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import (get_tax_accounts,
get_grand_total, add_total_row, get_display_value, get_group_by_and_display_fields, add_sub_total_row,
get_group_by_conditions)
from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import get_item_details
def execute(filters=None):
return _execute(filters)
@ -22,7 +23,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
aii_account_map = get_aii_accounts()
if item_list:
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency,
doctype="Purchase Invoice", tax_doctype="Purchase Taxes and Charges")
doctype='Purchase Invoice', tax_doctype='Purchase Taxes and Charges')
po_pr_map = get_purchase_receipts_against_purchase_order(item_list)
@ -34,10 +35,14 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
if filters.get('group_by'):
grand_total = get_grand_total(filters, 'Purchase Invoice')
item_details = get_item_details()
for d in item_list:
if not d.stock_qty:
continue
item_record = item_details.get(d.item_code)
purchase_receipt = None
if d.purchase_receipt:
purchase_receipt = d.purchase_receipt
@ -48,8 +53,8 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
row = {
'item_code': d.item_code,
'item_name': d.item_name,
'item_group': d.item_group,
'item_name': item_record.item_name,
'item_group': item_record.item_group,
'description': d.description,
'invoice': d.parent,
'posting_date': d.posting_date,
@ -81,10 +86,10 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
for tax in tax_columns:
item_tax = itemised_tax.get(d.name, {}).get(tax, {})
row.update({
frappe.scrub(tax + ' Rate'): item_tax.get("tax_rate", 0),
frappe.scrub(tax + ' Amount'): item_tax.get("tax_amount", 0),
frappe.scrub(tax + ' Rate'): item_tax.get('tax_rate', 0),
frappe.scrub(tax + ' Amount'): item_tax.get('tax_amount', 0),
})
total_tax += flt(item_tax.get("tax_amount"))
total_tax += flt(item_tax.get('tax_amount'))
row.update({
'total_tax': total_tax,
@ -309,8 +314,8 @@ def get_items(filters, additional_query_columns):
select
`tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`,
`tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company,
`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total, `tabPurchase Invoice Item`.`item_code`,
`tabPurchase Invoice Item`.`item_name`, `tabPurchase Invoice Item`.`item_group`, `tabPurchase Invoice Item`.description,
`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
`tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
`tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`,
`tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`,

View File

@ -8,6 +8,7 @@ from frappe.utils import flt, cstr
from frappe.model.meta import get_field_precision
from frappe.utils.xlsxutils import handle_html
from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments
from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import get_item_details, get_customer_details
def execute(filters=None):
return _execute(filters)
@ -16,7 +17,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
if not filters: filters = {}
columns = get_columns(additional_table_columns, filters)
company_currency = frappe.get_cached_value('Company', filters.get("company"), "default_currency")
company_currency = frappe.get_cached_value('Company', filters.get('company'), 'default_currency')
item_list = get_items(filters, additional_query_columns)
if item_list:
@ -33,7 +34,13 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
if filters.get('group_by'):
grand_total = get_grand_total(filters, 'Sales Invoice')
customer_details = get_customer_details()
item_details = get_item_details()
for d in item_list:
customer_record = customer_details.get(d.customer)
item_record = item_details.get(d.item_code)
delivery_note = None
if d.delivery_note:
delivery_note = d.delivery_note
@ -45,14 +52,14 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
row = {
'item_code': d.item_code,
'item_name': d.item_name,
'item_group': d.item_group,
'item_name': item_record.item_name,
'item_group': item_record.item_group,
'description': d.description,
'invoice': d.parent,
'posting_date': d.posting_date,
'customer': d.customer,
'customer_name': d.customer_name,
'customer_group': d.customer_group,
'customer_name': customer_record.customer_name,
'customer_group': customer_record.customer_group,
}
if additional_query_columns:
@ -90,10 +97,10 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
for tax in tax_columns:
item_tax = itemised_tax.get(d.name, {}).get(tax, {})
row.update({
frappe.scrub(tax + ' Rate'): item_tax.get("tax_rate", 0),
frappe.scrub(tax + ' Amount'): item_tax.get("tax_amount", 0),
frappe.scrub(tax + ' Rate'): item_tax.get('tax_rate', 0),
frappe.scrub(tax + ' Amount'): item_tax.get('tax_amount', 0),
})
total_tax += flt(item_tax.get("tax_amount"))
total_tax += flt(item_tax.get('tax_amount'))
row.update({
'total_tax': total_tax,
@ -226,7 +233,7 @@ def get_columns(additional_table_columns, filters):
if filters.get('group_by') != 'Territory':
columns.extend([
{
'label': _("Territory"),
'label': _('Territory'),
'fieldname': 'territory',
'fieldtype': 'Link',
'options': 'Territory',
@ -374,13 +381,12 @@ def get_items(filters, additional_query_columns):
`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
`tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks,
`tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.item_name,
`tabSales Invoice Item`.item_group, `tabSales Invoice Item`.description, `tabSales Invoice Item`.sales_order,
`tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.income_account,
`tabSales Invoice Item`.cost_center, `tabSales Invoice Item`.stock_qty,
`tabSales Invoice Item`.stock_uom, `tabSales Invoice Item`.base_net_rate,
`tabSales Invoice Item`.base_net_amount, `tabSales Invoice`.customer_name,
`tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail,
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
`tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note,
`tabSales Invoice Item`.income_account, `tabSales Invoice Item`.cost_center,
`tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_uom,
`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
`tabSales Invoice`.customer_name, `tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail,
`tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {0}
from `tabSales Invoice`, `tabSales Invoice Item`
where `tabSales Invoice`.name = `tabSales Invoice Item`.parent
@ -417,14 +423,14 @@ def get_deducted_taxes():
return frappe.db.sql_list("select name from `tabPurchase Taxes and Charges` where add_deduct_tax = 'Deduct'")
def get_tax_accounts(item_list, columns, company_currency,
doctype="Sales Invoice", tax_doctype="Sales Taxes and Charges"):
doctype='Sales Invoice', tax_doctype='Sales Taxes and Charges'):
import json
item_row_map = {}
tax_columns = []
invoice_item_row = {}
itemised_tax = {}
tax_amount_precision = get_field_precision(frappe.get_meta(tax_doctype).get_field("tax_amount"),
tax_amount_precision = get_field_precision(frappe.get_meta(tax_doctype).get_field('tax_amount'),
currency=company_currency) or 2
for d in item_list:
@ -469,8 +475,8 @@ def get_tax_accounts(item_list, columns, company_currency,
tax_rate = tax_data
tax_amount = 0
if charge_type == "Actual" and not tax_rate:
tax_rate = "NA"
if charge_type == 'Actual' and not tax_rate:
tax_rate = 'NA'
item_net_amount = sum([flt(d.base_net_amount)
for d in item_row_map.get(parent, {}).get(item_code, [])])
@ -484,17 +490,17 @@ def get_tax_accounts(item_list, columns, company_currency,
if (doctype == 'Purchase Invoice' and name in deducted_tax) else tax_value)
itemised_tax.setdefault(d.name, {})[description] = frappe._dict({
"tax_rate": tax_rate,
"tax_amount": tax_value
'tax_rate': tax_rate,
'tax_amount': tax_value
})
except ValueError:
continue
elif charge_type == "Actual" and tax_amount:
elif charge_type == 'Actual' and tax_amount:
for d in invoice_item_row.get(parent, []):
itemised_tax.setdefault(d.name, {})[description] = frappe._dict({
"tax_rate": "NA",
"tax_amount": flt((tax_amount * d.base_net_amount) / d.base_net_total,
'tax_rate': 'NA',
'tax_amount': flt((tax_amount * d.base_net_amount) / d.base_net_total,
tax_amount_precision)
})
@ -563,7 +569,7 @@ def add_total_row(data, filters, prev_group_by_value, item, total_row_map,
})
total_row_map.setdefault('total_row', {
subtotal_display_field: "Total",
subtotal_display_field: 'Total',
'stock_qty': 0.0,
'amount': 0.0,
'bold': 1,

View File

@ -12,11 +12,12 @@ from frappe.utils import formatdate, get_number_format_info
from six import iteritems
# imported to enable erpnext.accounts.utils.get_account_currency
from erpnext.accounts.doctype.account.account import get_account_currency
from frappe.model.meta import get_field_precision
from erpnext.stock.utils import get_stock_value_on
from erpnext.stock import get_warehouse_account_map
class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass
class FiscalYearError(frappe.ValidationError): pass
@frappe.whitelist()
@ -585,24 +586,6 @@ def fix_total_debit_credit():
(dr_or_cr, dr_or_cr, '%s', '%s', '%s', dr_or_cr),
(d.diff, d.voucher_type, d.voucher_no))
def get_stock_and_account_balance(account=None, posting_date=None, company=None):
if not posting_date: posting_date = nowdate()
warehouse_account = get_warehouse_account_map(company)
account_balance = get_balance_on(account, posting_date, in_account_currency=False, ignore_account_permission=True)
related_warehouses = [wh for wh, wh_details in warehouse_account.items()
if wh_details.account == account and not wh_details.is_group]
total_stock_value = 0.0
for warehouse in related_warehouses:
value = get_stock_value_on(warehouse, posting_date)
total_stock_value += value
precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency")
return flt(account_balance, precision), flt(total_stock_value, precision), related_warehouses
def get_currency_precision():
precision = cint(frappe.db.get_default("currency_precision"))
if not precision:
@ -903,12 +886,6 @@ def get_coa(doctype, parent, is_root, chart=None):
return accounts
def get_stock_accounts(company):
return frappe.get_all("Account", filters = {
"account_type": "Stock",
"company": company
})
def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
warehouse_account=None, company=None):
def _delete_gl_entries(voucher_type, voucher_no):
@ -928,7 +905,7 @@ def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for
if expected_gle:
if not existing_gle or not compare_existing_and_expected_gle(existing_gle, expected_gle):
_delete_gl_entries(voucher_type, voucher_no)
voucher_obj.make_gl_entries(gl_entries=expected_gle, repost_future_gle=False, from_repost=True)
voucher_obj.make_gl_entries(gl_entries=expected_gle, from_repost=True)
else:
_delete_gl_entries(voucher_type, voucher_no)
@ -947,7 +924,10 @@ def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, f
for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no
from `tabStock Ledger Entry` sle
where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) {condition}
where
timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s)
and is_cancelled = 0
{condition}
order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc for update""".format(condition=condition),
tuple([posting_date, posting_time] + values), as_dict=True):
future_stock_vouchers.append([d.voucher_type, d.voucher_no])
@ -964,3 +944,106 @@ def get_voucherwise_gl_entries(future_stock_vouchers, posting_date):
gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d)
return gl_entries
def compare_existing_and_expected_gle(existing_gle, expected_gle):
matched = True
for entry in expected_gle:
account_existed = False
for e in existing_gle:
if entry.account == e.account:
account_existed = True
if entry.account == e.account and entry.against_account == e.against_account \
and (not entry.cost_center or not e.cost_center or entry.cost_center == e.cost_center) \
and (entry.debit != e.debit or entry.credit != e.credit):
matched = False
break
if not account_existed:
matched = False
break
return matched
def check_if_stock_and_account_balance_synced(posting_date, company, voucher_type=None, voucher_no=None):
if not cint(erpnext.is_perpetual_inventory_enabled(company)):
return
accounts = get_stock_accounts(company, voucher_type, voucher_no)
stock_adjustment_account = frappe.db.get_value("Company", company, "stock_adjustment_account")
for account in accounts:
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(account,
posting_date, company)
if abs(account_bal - stock_bal) > 0.1:
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
currency=frappe.get_cached_value('Company', company, "default_currency"))
diff = flt(stock_bal - account_bal, precision)
error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses as on {3}.").format(
stock_bal, account_bal, frappe.bold(account), posting_date)
error_resolution = _("Please create an adjustment Journal Entry for amount {0} on {1}")\
.format(frappe.bold(diff), frappe.bold(posting_date))
frappe.msgprint(
msg="""{0}<br></br>{1}<br></br>""".format(error_reason, error_resolution),
raise_exception=StockValueAndAccountBalanceOutOfSync,
title=_('Values Out Of Sync'),
primary_action={
'label': _('Make Journal Entry'),
'client_action': 'erpnext.route_to_adjustment_jv',
'args': get_journal_entry(account, stock_adjustment_account, diff)
})
def get_stock_accounts(company, voucher_type=None, voucher_no=None):
stock_accounts = [d.name for d in frappe.db.get_all("Account", {
"account_type": "Stock",
"company": company,
"is_group": 0
})]
if voucher_type and voucher_no:
if voucher_type == "Journal Entry":
stock_accounts = [d.account for d in frappe.db.get_all("Journal Entry Account", {
"parent": voucher_no,
"account": ["in", stock_accounts]
}, "account")]
else:
stock_accounts = [d.account for d in frappe.db.get_all("GL Entry", {
"voucher_type": voucher_type,
"voucher_no": voucher_no,
"account": ["in", stock_accounts]
}, "account")]
return stock_accounts
def get_stock_and_account_balance(account=None, posting_date=None, company=None):
if not posting_date: posting_date = nowdate()
warehouse_account = get_warehouse_account_map(company)
account_balance = get_balance_on(account, posting_date, in_account_currency=False, ignore_account_permission=True)
related_warehouses = [wh for wh, wh_details in warehouse_account.items()
if wh_details.account == account and not wh_details.is_group]
total_stock_value = 0.0
for warehouse in related_warehouses:
value = get_stock_value_on(warehouse, posting_date)
total_stock_value += value
precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency")
return flt(account_balance, precision), flt(total_stock_value, precision), related_warehouses
def get_journal_entry(account, stock_adjustment_account, amount):
db_or_cr_warehouse_account =('credit_in_account_currency' if amount < 0 else 'debit_in_account_currency')
db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if amount < 0 else 'credit_in_account_currency')
return {
'accounts':[{
'account': account,
db_or_cr_warehouse_account: abs(amount)
}, {
'account': stock_adjustment_account,
db_or_cr_stock_adjustment_account : abs(amount)
}]
}

View File

@ -13,19 +13,16 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import g
class AssetValueAdjustment(Document):
def validate(self):
self.validate_date()
self.set_difference_amount()
self.set_current_asset_value()
self.set_difference_amount()
def on_submit(self):
self.make_depreciation_entry()
self.reschedule_depreciations(self.new_asset_value)
def on_cancel(self):
if self.journal_entry:
frappe.throw(_("Cancel the journal entry {0} first").format(self.journal_entry))
self.reschedule_depreciations(self.current_asset_value)
def validate_date(self):
asset_purchase_date = frappe.db.get_value('Asset', self.asset, 'purchase_date')
if getdate(self.date) < getdate(asset_purchase_date):
@ -53,6 +50,7 @@ class AssetValueAdjustment(Document):
je.posting_date = self.date
je.company = self.company
je.remark = "Depreciation Entry against {0} worth {1}".format(self.asset, self.difference_amount)
je.finance_book = self.finance_book
credit_entry = {
"account": accumulated_depreciation_account,
@ -78,7 +76,7 @@ class AssetValueAdjustment(Document):
debit_entry.update({
dimension['fieldname']: self.get(dimension['fieldname']) or dimension.get('default_dimension')
})
je.append("accounts", credit_entry)
je.append("accounts", debit_entry)

View File

@ -75,24 +75,23 @@ def get_data(filters):
for asset in assets_record:
asset_value = asset.gross_purchase_amount - flt(asset.opening_accumulated_depreciation) \
- flt(depreciation_amount_map.get(asset.name))
if asset_value:
row = {
"asset_id": asset.asset_id,
"asset_name": asset.asset_name,
"status": asset.status,
"department": asset.department,
"cost_center": asset.cost_center,
"vendor_name": pr_supplier_map.get(asset.purchase_receipt) or pi_supplier_map.get(asset.purchase_invoice),
"gross_purchase_amount": asset.gross_purchase_amount,
"opening_accumulated_depreciation": asset.opening_accumulated_depreciation,
"depreciated_amount": depreciation_amount_map.get(asset.asset_id) or 0.0,
"available_for_use_date": asset.available_for_use_date,
"location": asset.location,
"asset_category": asset.asset_category,
"purchase_date": asset.purchase_date,
"asset_value": asset_value
}
data.append(row)
row = {
"asset_id": asset.asset_id,
"asset_name": asset.asset_name,
"status": asset.status,
"department": asset.department,
"cost_center": asset.cost_center,
"vendor_name": pr_supplier_map.get(asset.purchase_receipt) or pi_supplier_map.get(asset.purchase_invoice),
"gross_purchase_amount": asset.gross_purchase_amount,
"opening_accumulated_depreciation": asset.opening_accumulated_depreciation,
"depreciated_amount": depreciation_amount_map.get(asset.asset_id) or 0.0,
"available_for_use_date": asset.available_for_use_date,
"location": asset.location,
"asset_category": asset.asset_category,
"purchase_date": asset.purchase_date,
"asset_value": asset_value
}
data.append(row)
return data

View File

@ -732,7 +732,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-10-30 11:59:47.670951",
"modified": "2020-12-07 11:59:47.670951",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",

View File

@ -49,6 +49,12 @@ class Supplier(TransactionBase):
msgprint(_("Series is mandatory"), raise_exception=1)
validate_party_accounts(self)
self.validate_internal_supplier()
def validate_internal_supplier(self):
if self.is_internal_supplier and frappe.db.get_value("Supplier", {"represents_company": self.represents_company}, "name"):
frappe.throw(_("Internal Supplier for company {0} already exists").format(
frappe.bold(self.represents_company)))
def on_trash(self):
delete_contact_and_address('Supplier', self.name)

View File

@ -107,9 +107,17 @@ class AccountsController(TransactionBase):
else:
self.validate_deferred_start_and_end_date()
self.set_inter_company_account()
validate_regional(self)
validate_einvoice_fields(self)
if self.doctype != 'Material Request':
apply_pricing_rule_on_transaction(self)
def before_cancel(self):
validate_einvoice_fields(self)
def validate_deferred_start_and_end_date(self):
for d in self.items:
@ -932,6 +940,38 @@ class AccountsController(TransactionBase):
else:
return frappe.db.get_single_value("Global Defaults", "disable_rounded_total")
def set_inter_company_account(self):
"""
Set intercompany account for inter warehouse transactions
This account will be used in case billing company and internal customer's
representation company is same
"""
if self.is_internal_transfer() and not self.unrealized_profit_loss_account:
unrealized_profit_loss_account = frappe.db.get_value('Company', self.company, 'unrealized_profit_loss_account')
if not unrealized_profit_loss_account:
msg = _("Please select Unrealized Profit / Loss account or add default Unrealized Profit / Loss account account for company {0}").format(
frappe.bold(self.company))
frappe.throw(msg)
self.unrealized_profit_loss_account = unrealized_profit_loss_account
def is_internal_transfer(self):
"""
It will an internal transfer if its an internal customer and representation
company is same as billing company
"""
if self.doctype == 'Sales Invoice':
internal_party_field = 'is_internal_customer'
else:
internal_party_field = 'is_internal_supplier'
if self.get(internal_party_field) and (self.represents_company == self.company):
return True
return False
@frappe.whitelist()
def get_tax_rate(account_head):
return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True)
@ -1484,3 +1524,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
@erpnext.allow_regional
def validate_regional(doc):
pass
@erpnext.allow_regional
def validate_einvoice_fields(doc):
pass

View File

@ -16,6 +16,8 @@ from frappe.contacts.doctype.address.address import get_address_display
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
from erpnext.controllers.stock_controller import StockController
from erpnext.controllers.sales_and_purchase_return import get_rate_for_return
from erpnext.stock.utils import get_incoming_rate
class BuyingController(StockController):
def __setup__(self):
@ -42,6 +44,7 @@ class BuyingController(StockController):
self.validate_items()
self.set_qty_as_per_stock_uom()
self.validate_stock_or_nonstock_items()
self.update_tax_category_for_internal_transfer()
self.validate_warehouse()
self.validate_from_warehouse()
self.set_supplier_address()
@ -62,7 +65,7 @@ class BuyingController(StockController):
self.set_landed_cost_voucher_amount()
if self.doctype in ("Purchase Receipt", "Purchase Invoice"):
self.update_valuation_rate("items")
self.update_valuation_rate()
def set_missing_values(self, for_validate=False):
super(BuyingController, self).set_missing_values(for_validate)
@ -94,13 +97,23 @@ class BuyingController(StockController):
def validate_stock_or_nonstock_items(self):
if self.meta.get_field("taxes") and not self.get_stock_items() and not self.get_asset_items():
tax_for_valuation = [d for d in self.get("taxes")
msg = _('Tax Category has been changed to "Total" because all the Items are non-stock items')
self.update_tax_category(msg)
def update_tax_category_for_internal_transfer(self):
if self.doctype == 'Purchase Invoice' and self.is_internal_transfer():
msg = _('Tax Category has been changed to "Total" as its an internal purchase.')
self.update_tax_category(msg)
def update_tax_category(self, msg):
tax_for_valuation = [d for d in self.get("taxes")
if d.category in ["Valuation", "Valuation and Total"]]
if tax_for_valuation:
for d in tax_for_valuation:
d.category = 'Total'
msgprint(_('Tax Category has been changed to "Total" because all the Items are non-stock items'))
if tax_for_valuation:
for d in tax_for_valuation:
d.category = 'Total'
msgprint(msg)
def validate_asset_return(self):
if self.doctype not in ['Purchase Receipt', 'Purchase Invoice'] or not self.is_return:
@ -166,7 +179,7 @@ class BuyingController(StockController):
self.in_words = money_in_words(amount, self.currency)
# update valuation rate
def update_valuation_rate(self, parentfield):
def update_valuation_rate(self, reset_outgoing_rate=True):
"""
item_tax_amount is the total tax amount applied on that item
stored for valuation
@ -177,7 +190,7 @@ class BuyingController(StockController):
stock_and_asset_items_qty, stock_and_asset_items_amount = 0, 0
last_item_idx = 1
for d in self.get(parentfield):
for d in self.get("items"):
if d.item_code and d.item_code in stock_and_asset_items:
stock_and_asset_items_qty += flt(d.qty)
stock_and_asset_items_amount += flt(d.base_net_amount)
@ -187,7 +200,7 @@ class BuyingController(StockController):
if d.category in ["Valuation", "Valuation and Total"]])
valuation_amount_adjustment = total_valuation_amount
for i, item in enumerate(self.get(parentfield)):
for i, item in enumerate(self.get("items")):
if item.item_code and item.qty and item.item_code in stock_and_asset_items:
item_proportion = flt(item.base_net_amount) / stock_and_asset_items_amount if stock_and_asset_items_amount \
else flt(item.qty) / stock_and_asset_items_qty
@ -205,16 +218,34 @@ class BuyingController(StockController):
item.conversion_factor = get_conversion_factor(item.item_code, item.uom).get("conversion_factor") or 1.0
qty_in_stock_uom = flt(item.qty * item.conversion_factor)
rm_supp_cost = flt(item.rm_supp_cost) if self.doctype in ["Purchase Receipt", "Purchase Invoice"] else 0.0
landed_cost_voucher_amount = flt(item.landed_cost_voucher_amount) \
if self.doctype in ["Purchase Receipt", "Purchase Invoice"] else 0.0
item.valuation_rate = ((item.base_net_amount + item.item_tax_amount + rm_supp_cost
+ landed_cost_voucher_amount) / qty_in_stock_uom)
item.rm_supp_cost = self.get_supplied_items_cost(item.name, reset_outgoing_rate)
item.valuation_rate = ((item.base_net_amount + item.item_tax_amount + item.rm_supp_cost
+ flt(item.landed_cost_voucher_amount)) / qty_in_stock_uom)
else:
item.valuation_rate = 0.0
def get_supplied_items_cost(self, item_row_id, reset_outgoing_rate=True):
supplied_items_cost = 0.0
for d in self.get("supplied_items"):
if d.reference_name == item_row_id:
if reset_outgoing_rate and frappe.db.get_value('Item', d.rm_item_code, 'is_stock_item'):
rate = get_incoming_rate({
"item_code": d.rm_item_code,
"warehouse": self.supplier_warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"qty": -1 * d.consumed_qty,
"serial_no": d.serial_no
})
if rate > 0:
d.rate = rate
d.amount = flt(flt(d.consumed_qty) * flt(d.rate), d.precision("amount"))
supplied_items_cost += flt(d.amount)
return supplied_items_cost
def validate_for_subcontracting(self):
if not self.is_subcontracted and self.sub_contracted_items:
frappe.throw(_("Please enter 'Is Subcontracted' as Yes or No"))
@ -341,35 +372,17 @@ class BuyingController(StockController):
else:
self.append_raw_material_to_be_backflushed(item, raw_material, qty)
def append_raw_material_to_be_backflushed(self, fg_item_doc, raw_material_data, qty):
def append_raw_material_to_be_backflushed(self, fg_item_row, raw_material_data, qty):
rm = self.append('supplied_items', {})
rm.update(raw_material_data)
if not rm.main_item_code:
rm.main_item_code = fg_item_doc.item_code
rm.main_item_code = fg_item_row.item_code
rm.reference_name = fg_item_doc.name
rm.reference_name = fg_item_row.name
rm.required_qty = qty
rm.consumed_qty = qty
if not raw_material_data.get('non_stock_item'):
from erpnext.stock.utils import get_incoming_rate
rm.rate = get_incoming_rate({
"item_code": raw_material_data.rm_item_code,
"warehouse": self.supplier_warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"qty": -1 * qty,
"serial_no": rm.serial_no
})
if not rm.rate:
rm.rate = get_valuation_rate(raw_material_data.rm_item_code, self.supplier_warehouse,
self.doctype, self.name, currency=self.company_currency, company=self.company)
rm.amount = qty * flt(rm.rate)
fg_item_doc.rm_supp_cost += rm.amount
def update_raw_materials_supplied_based_on_bom(self, item, raw_material_table):
exploded_item = 1
if hasattr(item, 'include_exploded_items'):
@ -378,7 +391,7 @@ class BuyingController(StockController):
bom_items = get_items_from_bom(item.item_code, item.bom, exploded_item)
used_alternative_items = []
if self.doctype == 'Purchase Receipt' and item.purchase_order:
if self.doctype in ["Purchase Receipt", "Purchase Invoice"] and item.purchase_order:
used_alternative_items = get_used_alternative_items(purchase_order = item.purchase_order)
raw_materials_cost = 0
@ -395,7 +408,7 @@ class BuyingController(StockController):
reserve_warehouse = None
conversion_factor = item.conversion_factor
if (self.doctype == 'Purchase Receipt' and item.purchase_order and
if (self.doctype in ["Purchase Receipt", "Purchase Invoice"] and item.purchase_order and
bom_item.item_code in used_alternative_items):
alternative_item_data = used_alternative_items.get(bom_item.item_code)
bom_item.item_code = alternative_item_data.item_code
@ -423,9 +436,7 @@ class BuyingController(StockController):
rm.rm_item_code = bom_item.item_code
rm.stock_uom = bom_item.stock_uom
rm.required_qty = required_qty
if self.doctype == "Purchase Order" and not rm.reserve_warehouse:
rm.reserve_warehouse = reserve_warehouse
rm.rate = bom_item.rate
rm.conversion_factor = conversion_factor
if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
@ -433,29 +444,8 @@ class BuyingController(StockController):
rm.description = bom_item.description
if item.batch_no and frappe.db.get_value("Item", rm.rm_item_code, "has_batch_no") and not rm.batch_no:
rm.batch_no = item.batch_no
# get raw materials rate
if self.doctype == "Purchase Receipt":
from erpnext.stock.utils import get_incoming_rate
rm.rate = get_incoming_rate({
"item_code": bom_item.item_code,
"warehouse": self.supplier_warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"qty": -1 * required_qty,
"serial_no": rm.serial_no
})
if not rm.rate:
rm.rate = get_valuation_rate(bom_item.item_code, self.supplier_warehouse,
self.doctype, self.name, currency=self.company_currency, company = self.company)
else:
rm.rate = bom_item.rate
rm.amount = required_qty * flt(rm.rate)
raw_materials_cost += flt(rm.amount)
if self.doctype in ("Purchase Receipt", "Purchase Invoice"):
item.rm_supp_cost = raw_materials_cost
elif not rm.reserve_warehouse:
rm.reserve_warehouse = reserve_warehouse
def cleanup_raw_materials_supplied(self, parent_items, raw_material_table):
"""Remove all those child items which are no longer present in main item table"""
@ -568,7 +558,8 @@ class BuyingController(StockController):
or (cint(self.is_return) and self.docstatus==2)):
from_warehouse_sle = self.get_sl_entries(d, {
"actual_qty": -1 * pr_qty,
"warehouse": d.from_warehouse
"warehouse": d.from_warehouse,
"dependant_sle_voucher_detail_no": d.name
})
sl_entries.append(from_warehouse_sle)
@ -578,28 +569,20 @@ class BuyingController(StockController):
"serial_no": cstr(d.serial_no).strip()
})
if self.is_return:
filters = {
"voucher_type": self.doctype,
"voucher_no": self.return_against,
"item_code": d.item_code
}
if (self.doctype == "Purchase Invoice" and self.update_stock
and d.get("purchase_invoice_item")):
filters["voucher_detail_no"] = d.purchase_invoice_item
elif self.doctype == "Purchase Receipt" and d.get("purchase_receipt_item"):
filters["voucher_detail_no"] = d.purchase_receipt_item
original_incoming_rate = frappe.db.get_value("Stock Ledger Entry", filters, "incoming_rate")
outgoing_rate = get_rate_for_return(self.doctype, self.name, d.item_code, self.return_against, item_row=d)
sle.update({
"outgoing_rate": original_incoming_rate
"outgoing_rate": outgoing_rate,
"recalculate_rate": 1
})
if d.from_warehouse:
sle.dependant_sle_voucher_detail_no = d.name
else:
val_rate_db_precision = 6 if cint(self.precision("valuation_rate", d)) <= 6 else 9
incoming_rate = flt(d.valuation_rate, val_rate_db_precision)
sle.update({
"incoming_rate": incoming_rate
"incoming_rate": incoming_rate,
"recalculate_rate": 1 if (self.is_subcontracted and d.bom) or d.from_warehouse else 0
})
sl_entries.append(sle)
@ -607,7 +590,8 @@ class BuyingController(StockController):
or (cint(self.is_return) and self.docstatus==1)):
from_warehouse_sle = self.get_sl_entries(d, {
"actual_qty": -1 * pr_qty,
"warehouse": d.from_warehouse
"warehouse": d.from_warehouse,
"recalculate_rate": 1
})
sl_entries.append(from_warehouse_sle)
@ -655,6 +639,7 @@ class BuyingController(StockController):
"item_code": d.rm_item_code,
"warehouse": self.supplier_warehouse,
"actual_qty": -1*flt(d.consumed_qty),
"dependant_sle_voucher_detail_no": d.reference_name
}))
def on_submit(self):
@ -846,6 +831,7 @@ class BuyingController(StockController):
else:
validate_item_type(self, "is_purchase_item", "purchase")
def get_items_from_bom(item_code, bom, exploded_item=1):
doctype = "BOM Item" if not exploded_item else "BOM Explosion Item"

View File

@ -204,21 +204,25 @@ def get_already_returned_items(doc):
return items
def get_returned_qty_map_for_row(row_name, doctype):
if doctype == "POS Invoice": return {}
child_doctype = doctype + " Item"
reference_field = frappe.scrub(child_doctype) if doctype == "Purchase Receipt" else "dn_detail"
reference_field = "dn_detail" if doctype == "Delivery Note" else frappe.scrub(child_doctype)
fields = [
"sum(abs(`tab{0}`.qty)) as qty".format(child_doctype),
"sum(abs(`tab{0}`.stock_qty)) as stock_qty".format(child_doctype)
]
if doctype == "Purchase Receipt":
if doctype in ("Purchase Receipt", "Purchase Invoice"):
fields += [
"sum(abs(`tab{0}`.rejected_qty)) as rejected_qty".format(child_doctype),
"sum(abs(`tab{0}`.received_qty)) as received_qty".format(child_doctype),
"sum(abs(`tab{0}`.received_stock_qty)) as received_stock_qty".format(child_doctype)
"sum(abs(`tab{0}`.received_qty)) as received_qty".format(child_doctype)
]
if doctype == "Purchase Receipt":
fields += ["sum(abs(`tab{0}`.received_stock_qty)) as received_stock_qty".format(child_doctype)]
data = frappe.db.get_list(doctype,
fields = fields,
filters = [
@ -231,6 +235,7 @@ def get_returned_qty_map_for_row(row_name, doctype):
def make_return_doc(doctype, source_name, target_doc=None):
from frappe.model.mapper import get_mapped_doc
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
company = frappe.db.get_value("Delivery Note", source_name, "company")
default_warehouse_for_sales_return = frappe.db.get_value("Company", company, "default_warehouse_for_sales_return")
@ -290,6 +295,12 @@ def make_return_doc(doctype, source_name, target_doc=None):
def update_item(source_doc, target_doc, source_parent):
target_doc.qty = -1 * source_doc.qty
if source_doc.serial_no:
returned_serial_nos = get_returned_serial_nos(source_doc, source_parent)
serial_nos = list(set(get_serial_nos(source_doc.serial_no)) - set(returned_serial_nos))
if serial_nos:
target_doc.serial_no = '\n'.join(serial_nos)
if doctype == "Purchase Receipt":
returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
target_doc.received_qty = -1 * flt(source_doc.received_qty - (returned_qty_map.get('received_qty') or 0))
@ -305,10 +316,12 @@ def make_return_doc(doctype, source_name, target_doc=None):
target_doc.purchase_receipt_item = source_doc.name
elif doctype == "Purchase Invoice":
target_doc.received_qty = -1 * source_doc.received_qty
target_doc.rejected_qty = -1 * source_doc.rejected_qty
target_doc.qty = -1* source_doc.qty
target_doc.stock_qty = -1 * source_doc.stock_qty
returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
target_doc.received_qty = -1 * flt(source_doc.received_qty - (returned_qty_map.get('received_qty') or 0))
target_doc.rejected_qty = -1 * flt(source_doc.rejected_qty - (returned_qty_map.get('rejected_qty') or 0))
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
target_doc.purchase_order = source_doc.purchase_order
target_doc.purchase_receipt = source_doc.purchase_receipt
target_doc.rejected_warehouse = source_doc.rejected_warehouse
@ -330,6 +343,10 @@ def make_return_doc(doctype, source_name, target_doc=None):
if default_warehouse_for_sales_return:
target_doc.warehouse = default_warehouse_for_sales_return
elif doctype == "Sales Invoice" or doctype == "POS Invoice":
returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
target_doc.sales_order = source_doc.sales_order
target_doc.delivery_note = source_doc.delivery_note
target_doc.so_detail = source_doc.so_detail
@ -365,3 +382,63 @@ def make_return_doc(doctype, source_name, target_doc=None):
}, target_doc, set_missing_values)
return doclist
def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None, item_row=None, voucher_detail_no=None):
if not return_against:
return_against = frappe.get_cached_value(voucher_type, voucher_no, "return_against")
return_against_item_field = get_return_against_item_fields(voucher_type)
filters = get_filters(voucher_type, voucher_no, voucher_detail_no,
return_against, item_code, return_against_item_field, item_row)
if voucher_type in ("Purchase Receipt", "Purchase Invoice"):
select_field = "incoming_rate"
else:
select_field = "abs(stock_value_difference / actual_qty)"
return flt(frappe.db.get_value("Stock Ledger Entry", filters, select_field))
def get_return_against_item_fields(voucher_type):
return_against_item_fields = {
"Purchase Receipt": "purchase_receipt_item",
"Purchase Invoice": "purchase_invoice_item",
"Delivery Note": "dn_detail",
"Sales Invoice": "sales_invoice_item"
}
return return_against_item_fields[voucher_type]
def get_filters(voucher_type, voucher_no, voucher_detail_no, return_against, item_code, return_against_item_field, item_row):
filters = {
"voucher_type": voucher_type,
"voucher_no": return_against,
"item_code": item_code
}
if item_row:
reference_voucher_detail_no = item_row.get(return_against_item_field)
else:
reference_voucher_detail_no = frappe.db.get_value(voucher_type + " Item", voucher_detail_no, return_against_item_field)
if reference_voucher_detail_no:
filters["voucher_detail_no"] = reference_voucher_detail_no
return filters
def get_returned_serial_nos(child_doc, parent_doc):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
return_ref_field = frappe.scrub(child_doc.doctype)
if child_doc.doctype == "Delivery Note Item":
return_ref_field = "dn_detail"
serial_nos = []
fields = ["`{0}`.`serial_no`".format("tab" + child_doc.doctype)]
filters = [[parent_doc.doctype, "return_against", "=", parent_doc.name], [parent_doc.doctype, "is_return", "=", 1],
[child_doc.doctype, return_ref_field, "=", child_doc.name], [parent_doc.doctype, "docstatus", "=", 1]]
for row in frappe.get_all(parent_doc.doctype, fields = fields, filters=filters):
serial_nos.extend(get_serial_nos(row.serial_no))
return serial_nos

View File

@ -13,6 +13,7 @@ from frappe.contacts.doctype.address.address import get_address_display
from erpnext.controllers.accounts_controller import get_taxes_and_charges
from erpnext.controllers.stock_controller import StockController
from erpnext.controllers.sales_and_purchase_return import get_rate_for_return
class SellingController(StockController):
def __setup__(self):
@ -48,6 +49,7 @@ class SellingController(StockController):
self.set_customer_address()
self.validate_for_duplicate_items()
self.validate_target_warehouse()
self.set_incoming_rate()
def set_missing_values(self, for_validate=False):
@ -230,7 +232,8 @@ class SellingController(StockController):
'voucher_type': self.doctype,
'allow_zero_valuation': d.allow_zero_valuation_rate,
'sales_invoice_item': d.get("sales_invoice_item"),
'delivery_note_item': d.get("dn_detail")
'dn_detail': d.get("dn_detail"),
'incoming_rate': p.incoming_rate
}))
else:
il.append(frappe._dict({
@ -248,7 +251,8 @@ class SellingController(StockController):
'voucher_type': self.doctype,
'allow_zero_valuation': d.allow_zero_valuation_rate,
'sales_invoice_item': d.get("sales_invoice_item"),
'delivery_note_item': d.get("dn_detail")
'dn_detail': d.get("dn_detail"),
'incoming_rate': d.incoming_rate
}))
return il
@ -307,69 +311,89 @@ class SellingController(StockController):
sales_order.update_reserved_qty(so_item_rows)
def set_incoming_rate(self):
if self.doctype not in ("Delivery Note", "Sales Invoice"):
return
items = self.get("items") + (self.get("packed_items") or [])
for d in items:
if not cint(self.get("is_return")):
# Get incoming rate based on original item cost based on valuation method
d.incoming_rate = get_incoming_rate({
"item_code": d.item_code,
"warehouse": d.warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"qty": -1*flt(d.qty),
"serial_no": d.serial_no,
"company": self.company,
"voucher_type": self.doctype,
"voucher_no": self.name,
"allow_zero_valuation": d.get("allow_zero_valuation")
}, raise_error_if_no_rate=False)
elif self.get("return_against"):
# Get incoming rate of return entry from reference document
# based on original item cost as per valuation method
d.incoming_rate = get_rate_for_return(self.doctype, self.name, d.item_code, self.return_against, item_row=d)
def update_stock_ledger(self):
self.update_reserved_qty()
sl_entries = []
# Loop over items and packed items table
for d in self.get_item_list():
if frappe.get_cached_value("Item", d.item_code, "is_stock_item") == 1 and flt(d.qty):
if flt(d.conversion_factor)==0.0:
d.conversion_factor = get_conversion_factor(d.item_code, d.uom).get("conversion_factor") or 1.0
return_rate = 0
if cint(self.is_return) and self.return_against and self.docstatus==1:
against_document_no = (d.get("sales_invoice_item")
if self.doctype == "Sales Invoice" else d.get("delivery_note_item"))
return_rate = self.get_incoming_rate_for_return(d.item_code,
self.return_against, against_document_no)
# On cancellation or if return entry submission, make stock ledger entry for
# On cancellation or return entry submission, make stock ledger entry for
# target warehouse first, to update serial no values properly
if d.warehouse and ((not cint(self.is_return) and self.docstatus==1)
or (cint(self.is_return) and self.docstatus==2)):
sl_entries.append(self.get_sl_entries(d, {
"actual_qty": -1*flt(d.qty),
"incoming_rate": return_rate
}))
sl_entries.append(self.get_sle_for_source_warehouse(d))
if d.target_warehouse:
target_warehouse_sle = self.get_sl_entries(d, {
"actual_qty": flt(d.qty),
"warehouse": d.target_warehouse
})
if self.docstatus == 1:
if not cint(self.is_return):
args = frappe._dict({
"item_code": d.item_code,
"warehouse": d.warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"qty": -1*flt(d.qty),
"serial_no": d.serial_no,
"company": d.company,
"voucher_type": d.voucher_type,
"voucher_no": d.name,
"allow_zero_valuation": d.allow_zero_valuation
})
target_warehouse_sle.update({
"incoming_rate": get_incoming_rate(args)
})
else:
target_warehouse_sle.update({
"outgoing_rate": return_rate
})
sl_entries.append(target_warehouse_sle)
sl_entries.append(self.get_sle_for_target_warehouse(d))
if d.warehouse and ((not cint(self.is_return) and self.docstatus==2)
or (cint(self.is_return) and self.docstatus==1)):
sl_entries.append(self.get_sl_entries(d, {
"actual_qty": -1*flt(d.qty),
"incoming_rate": return_rate
}))
sl_entries.append(self.get_sle_for_source_warehouse(d))
self.make_sl_entries(sl_entries)
def get_sle_for_source_warehouse(self, item_row):
sle = self.get_sl_entries(item_row, {
"actual_qty": -1*flt(item_row.qty),
"incoming_rate": item_row.incoming_rate,
"recalculate_rate": cint(self.is_return)
})
if item_row.target_warehouse and not cint(self.is_return):
sle.dependant_sle_voucher_detail_no = item_row.name
return sle
def get_sle_for_target_warehouse(self, item_row):
sle = self.get_sl_entries(item_row, {
"actual_qty": flt(item_row.qty),
"warehouse": item_row.target_warehouse
})
if self.docstatus == 1:
if not cint(self.is_return):
sle.update({
"incoming_rate": item_row.incoming_rate,
"recalculate_rate": 1
})
else:
sle.update({
"outgoing_rate": item_row.incoming_rate
})
if item_row.warehouse:
sle.dependant_sle_voucher_detail_no = item_row.name
return sle
def set_po_nos(self, for_validate=False):
if self.doctype == 'Sales Invoice' and hasattr(self, "items"):
if for_validate and self.po_no:
@ -463,4 +487,4 @@ def set_default_income_account_for_item(obj):
for d in obj.get("items"):
if d.item_code:
if getattr(d, "income_account", None):
set_item_default(d.item_code, obj.company, 'income_account', d.income_account)
set_item_default(d.item_code, obj.company, 'income_account', d.income_account)

View File

@ -254,22 +254,26 @@ class StatusUpdater(Document):
if not args.get("second_source_extra_cond"):
args["second_source_extra_cond"] = ""
args['second_source_condition'] = """ + ifnull((select sum(%(second_source_field)s)
args['second_source_condition'] = frappe.db.sql(""" select ifnull((select sum(%(second_source_field)s)
from `tab%(second_source_dt)s`
where `%(second_join_field)s`="%(detail_id)s"
and (`tab%(second_source_dt)s`.docstatus=1) %(second_source_extra_cond)s FOR UPDATE), 0)""" % args
and (`tab%(second_source_dt)s`.docstatus=1)
%(second_source_extra_cond)s), 0) """ % args)[0][0]
if args['detail_id']:
if not args.get("extra_cond"): args["extra_cond"] = ""
frappe.db.sql("""update `tab%(target_dt)s`
set %(target_field)s = (
args["source_dt_value"] = frappe.db.sql("""
(select ifnull(sum(%(source_field)s), 0)
from `tab%(source_dt)s` where `%(join_field)s`="%(detail_id)s"
and (docstatus=1 %(cond)s) %(extra_cond)s)
%(second_source_condition)s
)
%(update_modified)s
""" % args)[0][0] or 0.0
if args['second_source_condition']:
args["source_dt_value"] += flt(args['second_source_condition'])
frappe.db.sql("""update `tab%(target_dt)s`
set %(target_field)s = %(source_dt_value)s %(update_modified)s
where name='%(detail_id)s'""" % args)
def _update_percent_field_in_targets(self, args, update_modified=True):

View File

@ -6,7 +6,7 @@ import frappe, erpnext
from frappe.utils import cint, flt, cstr, get_link_to_form, today, getdate
from frappe import _
import frappe.defaults
from erpnext.accounts.utils import get_fiscal_year
from erpnext.accounts.utils import get_fiscal_year, check_if_stock_and_account_balance_synced
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries, process_gl_map
from erpnext.controllers.accounts_controller import AccountsController
from erpnext.stock.stock_ledger import get_valuation_rate
@ -24,7 +24,7 @@ class StockController(AccountsController):
self.validate_serialized_batch()
self.validate_customer_provided_item()
def make_gl_entries(self, gl_entries=None):
def make_gl_entries(self, gl_entries=None, from_repost=False):
if self.docstatus == 2:
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
@ -34,12 +34,12 @@ class StockController(AccountsController):
if self.docstatus==1:
if not gl_entries:
gl_entries = self.get_gl_entries(warehouse_account)
make_gl_entries(gl_entries)
make_gl_entries(gl_entries, from_repost=from_repost)
elif self.doctype in ['Purchase Receipt', 'Purchase Invoice'] and self.docstatus == 1:
gl_entries = []
gl_entries = self.get_asset_gl_entry(gl_entries)
make_gl_entries(gl_entries)
make_gl_entries(gl_entries, from_repost=from_repost)
def validate_serialized_batch(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@ -70,14 +70,13 @@ class StockController(AccountsController):
gl_list = []
warehouse_with_no_account = []
precision = frappe.get_precision("GL Entry", "debit_in_account_currency")
for item_row in voucher_details:
sle_list = sle_map.get(item_row.name)
if sle_list:
for sle in sle_list:
if warehouse_account.get(sle.warehouse):
# from warehouse account/ target warehouse account
# from warehouse account
self.check_expense_account(item_row)
@ -92,9 +91,16 @@ class StockController(AccountsController):
sle = self.update_stock_ledger_entries(sle)
# expense account/ target_warehouse / source_warehouse
if item_row.get('target_warehouse'):
warehouse = item_row.get('target_warehouse')
expense_account = warehouse_account[warehouse]["account"]
else:
expense_account = item_row.expense_account
gl_list.append(self.get_gl_dict({
"account": warehouse_account[sle.warehouse]["account"],
"against": item_row.expense_account,
"against": expense_account,
"cost_center": item_row.cost_center,
"project": item_row.project or self.get('project'),
"remarks": self.get("remarks") or "Accounting Entry for Stock",
@ -102,9 +108,8 @@ class StockController(AccountsController):
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
}, warehouse_account[sle.warehouse]["account_currency"], item=item_row))
# expense account
gl_list.append(self.get_gl_dict({
"account": item_row.expense_account,
"account": expense_account,
"against": warehouse_account[sle.warehouse]["account"],
"cost_center": item_row.cost_center,
"project": item_row.project or self.get('project'),
@ -119,7 +124,7 @@ class StockController(AccountsController):
if warehouse_with_no_account:
for wh in warehouse_with_no_account:
if frappe.db.get_value("Warehouse", wh, "company"):
frappe.throw(_("Warehouse {0} is not linked to any account, please mention the account in the warehouse record or set default inventory account in company {1}.").format(wh, self.company))
frappe.throw(_("Warehouse {0} is not linked to any account, please mention the account in the warehouse record or set default inventory account in company {1}.").format(wh, self.company))
return process_gl_map(gl_list)
@ -303,23 +308,6 @@ class StockController(AccountsController):
return serialized_items
def get_incoming_rate_for_return(self, item_code, against_document, against_document_no=None):
incoming_rate = 0.0
cond = ''
if against_document and item_code:
if against_document_no:
cond = " and voucher_detail_no = %s" %(frappe.db.escape(against_document_no))
incoming_rate = frappe.db.sql("""select abs(stock_value_difference / actual_qty)
from `tabStock Ledger Entry`
where voucher_type = %s and voucher_no = %s
and item_code = %s {0} limit 1""".format(cond),
(self.doctype, against_document, item_code))
incoming_rate = incoming_rate[0][0] if incoming_rate else 0.0
return incoming_rate
def validate_warehouse(self):
from erpnext.stock.utils import validate_warehouse_company
@ -403,19 +391,72 @@ class StockController(AccountsController):
if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'):
d.allow_zero_valuation_rate = 1
def compare_existing_and_expected_gle(existing_gle, expected_gle):
matched = True
for entry in expected_gle:
account_existed = False
for e in existing_gle:
if entry.account == e.account:
account_existed = True
if entry.account == e.account and entry.against_account == e.against_account \
and (not entry.cost_center or not e.cost_center or entry.cost_center == e.cost_center) \
and (entry.debit != e.debit or entry.credit != e.credit):
matched = False
break
if not account_existed:
matched = False
def repost_future_sle_and_gle(self):
args = frappe._dict({
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"voucher_type": self.doctype,
"voucher_no": self.name,
"company": self.company
})
if check_if_future_sle_exists(args):
create_repost_item_valuation_entry(args)
elif not is_reposting_pending():
check_if_stock_and_account_balance_synced(self.posting_date,
self.company, self.doctype, self.name)
def is_reposting_pending():
return frappe.db.exists("Repost Item Valuation",
{'docstatus': 1, 'status': ['in', ['Queued','In Progress']]})
def check_if_future_sle_exists(args):
sl_entries = frappe.db.get_all("Stock Ledger Entry",
filters={"voucher_type": args.voucher_type, "voucher_no": args.voucher_no},
fields=["item_code", "warehouse"],
order_by="creation asc")
distinct_item_warehouses = list(set([(d.item_code, d.warehouse) for d in sl_entries]))
sle_exists = False
for item_code, warehouse in distinct_item_warehouses:
args.update({
"item_code": item_code,
"warehouse": warehouse
})
if get_sle(args):
sle_exists = True
break
return matched
return sle_exists
def get_sle(args):
return frappe.db.sql("""
select name
from `tabStock Ledger Entry`
where
item_code=%(item_code)s
and warehouse=%(warehouse)s
and timestamp(posting_date, posting_time) >= timestamp(%(posting_date)s, %(posting_time)s)
and voucher_no != %(voucher_no)s
and is_cancelled = 0
limit 1
""", args)
def create_repost_item_valuation_entry(args):
args = frappe._dict(args)
repost_entry = frappe.new_doc("Repost Item Valuation")
repost_entry.based_on = args.based_on
if not args.based_on:
repost_entry.based_on = 'Transaction' if args.voucher_no else "Item and Warehouse"
repost_entry.voucher_type = args.voucher_type
repost_entry.voucher_no = args.voucher_no
repost_entry.item_code = args.item_code
repost_entry.warehouse = args.warehouse
repost_entry.posting_date = args.posting_date
repost_entry.posting_time = args.posting_time
repost_entry.company = args.company
repost_entry.allow_zero_rate = args.allow_zero_rate
repost_entry.flags.ignore_links = True
repost_entry.save()
repost_entry.submit()

View File

@ -519,6 +519,17 @@ class calculate_taxes_and_totals(object):
if self.doc.docstatus == 0:
self.calculate_outstanding_amount()
def is_internal_invoice(self):
"""
Checks if its an internal transfer invoice
and decides if to calculate any out standing amount or not
"""
if self.doc.doctype in ('Sales Invoice', 'Purchase Invoice') and self.doc.is_internal_transfer():
return True
return False
def calculate_outstanding_amount(self):
# NOTE:
# write_off_amount is only for POS Invoice
@ -526,7 +537,8 @@ class calculate_taxes_and_totals(object):
if self.doc.doctype == "Sales Invoice":
self.calculate_paid_amount()
if self.doc.is_return and self.doc.return_against and not self.doc.get('is_pos'): return
if self.doc.is_return and self.doc.return_against and not self.doc.get('is_pos') or \
self.is_internal_invoice(): return
self.doc.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount"])
self._set_in_company_currency(self.doc, ['write_off_amount'])

View File

@ -126,7 +126,7 @@ class Appointment(Document):
add_assignemnt({
'doctype': self.doctype,
'name': self.name,
'assign_to': existing_assignee
'assign_to': [existing_assignee]
})
return
if self._assign:
@ -139,7 +139,7 @@ class Appointment(Document):
add_assignemnt({
'doctype': self.doctype,
'name': self.name,
'assign_to': agent
'assign_to': [agent]
})
break

View File

@ -1,23 +1,31 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
cur_frm.add_fetch("contract_template", "contract_terms", "contract_terms");
cur_frm.add_fetch("contract_template", "requires_fulfilment", "requires_fulfilment");
// Add fulfilment terms from contract template into contract
frappe.ui.form.on("Contract", {
contract_template: function (frm) {
// Populate the fulfilment terms table from a contract template, if any
if (frm.doc.contract_template) {
frappe.model.with_doc("Contract Template", frm.doc.contract_template, function () {
var tabletransfer = frappe.model.get_doc("Contract Template", frm.doc.contract_template);
frm.doc.fulfilment_terms = [];
$.each(tabletransfer.fulfilment_terms, function (index, row) {
var d = frm.add_child("fulfilment_terms");
d.requirement = row.requirement;
frm.refresh_field("fulfilment_terms");
});
frappe.call({
method: 'erpnext.crm.doctype.contract_template.contract_template.get_contract_template',
args: {
template_name: frm.doc.contract_template,
doc: frm.doc
},
callback: function(r) {
if (r && r.message) {
let contract_template = r.message.contract_template;
frm.set_value("contract_terms", r.message.contract_terms);
frm.set_value("requires_fulfilment", contract_template.requires_fulfilment);
if (frm.doc.requires_fulfilment) {
// Populate the fulfilment terms table from a contract template, if any
r.message.contract_template.fulfilment_terms.forEach(element => {
let d = frm.add_child("fulfilment_terms");
d.requirement = element.requirement;
});
frm.refresh_field("fulfilment_terms");
}
}
}
});
}
}

View File

@ -1,4 +1,5 @@
{
"actions": [],
"allow_import": 1,
"allow_rename": 1,
"creation": "2018-04-12 06:32:04.582486",
@ -247,7 +248,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2020-03-30 06:56:07.257932",
"modified": "2020-12-07 11:15:58.385521",
"modified_by": "Administrator",
"module": "CRM",
"name": "Contract",

View File

@ -11,7 +11,9 @@
"contract_terms",
"sb_fulfilment",
"requires_fulfilment",
"fulfilment_terms"
"fulfilment_terms",
"section_break_6",
"contract_template_help"
],
"fields": [
{
@ -41,10 +43,20 @@
"fieldtype": "Table",
"label": "Fulfilment Terms and Conditions",
"options": "Contract Template Fulfilment Terms"
},
{
"fieldname": "section_break_6",
"fieldtype": "Section Break"
},
{
"fieldname": "contract_template_help",
"fieldtype": "HTML",
"label": "Contract Template Help",
"options": "<h4>Contract Template Example</h4>\n\n<pre>Contract for Customer {{ party_name }}\n\n-Valid From : {{ start_date }} \n-Valid To : {{ end_date }}\n</pre>\n\n<h4>How to get fieldnames</h4>\n\n<p>The field names you can use in your Contract Template are the fields in the Contract for which you are creating the template. You can find out the fields of any documents via Setup &gt; Customize Form View and selecting the document type (e.g. Contract)</p>\n\n<h4>Templating</h4>\n\n<p>Templates are compiled using the Jinja Templating Language. To learn more about Jinja, <a class=\"strong\" href=\"http://jinja.pocoo.org/docs/dev/templates/\">read this documentation.</a></p>"
}
],
"links": [],
"modified": "2020-11-11 17:49:44.879363",
"modified": "2020-12-07 10:44:22.587047",
"modified_by": "Administrator",
"module": "CRM",
"name": "Contract Template",

View File

@ -5,6 +5,27 @@
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe.utils.jinja import validate_template
from six import string_types
import json
class ContractTemplate(Document):
pass
def validate(self):
if self.contract_terms:
validate_template(self.contract_terms)
@frappe.whitelist()
def get_contract_template(template_name, doc):
if isinstance(doc, string_types):
doc = json.loads(doc)
contract_template = frappe.get_doc("Contract Template", template_name)
contract_terms = None
if contract_template.contract_terms:
contract_terms = frappe.render_template(contract_template.contract_terms, doc)
return {
'contract_template': contract_template,
'contract_terms': contract_terms
}

View File

@ -260,6 +260,15 @@ def update_taxes_with_shipping_lines(taxes, shipping_lines, shopify_settings):
"""Shipping lines represents the shipping details,
each such shipping detail consists of a list of tax_lines"""
for shipping_charge in shipping_lines:
if shipping_charge.get("price"):
taxes.append({
"charge_type": _("Actual"),
"account_head": get_tax_account_head(shipping_charge),
"description": shipping_charge["title"],
"tax_amount": shipping_charge["price"],
"cost_center": shopify_settings.cost_center
})
for tax in shipping_charge.get("tax_lines"):
taxes.append({
"charge_type": _("Actual"),

View File

@ -30,6 +30,11 @@
"label": "Laboratory",
"links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test\",\n\t\t\"label\": \"Lab Test\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Sample Collection\",\n\t\t\"label\": \"Sample Collection\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Dosage Form\",\n\t\t\"label\": \"Dosage Form\"\n\t}\n]"
},
{
"hidden": 0,
"label": "Inpatient",
"links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Record\",\n\t\t\"label\": \"Inpatient Record\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Medication Order\",\n\t\t\"label\": \"Inpatient Medication Order\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Medication Entry\",\n\t\t\"label\": \"Inpatient Medication Entry\"\n\t}\n]"
},
{
"hidden": 0,
"label": "Rehabilitation and Physiotherapy",
@ -38,7 +43,7 @@
{
"hidden": 0,
"label": "Records and History",
"links": "[\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient_history\",\n\t\t\"label\": \"Patient History\"\n\t},\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient-progress\",\n\t\t\"label\": \"Patient Progress\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Medical Record\",\n\t\t\"label\": \"Patient Medical Record\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Record\",\n\t\t\"label\": \"Inpatient Record\"\n\t}\n]"
"links": "[\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient_history\",\n\t\t\"label\": \"Patient History\"\n\t},\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient-progress\",\n\t\t\"label\": \"Patient Progress\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Medical Record\",\n\t\t\"label\": \"Patient Medical Record\"\n\t}\n]"
},
{
"hidden": 0,
@ -64,7 +69,7 @@
"idx": 0,
"is_standard": 1,
"label": "Healthcare",
"modified": "2020-11-23 23:00:48.764377",
"modified": "2020-11-26 22:09:09.164584",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Healthcare",

View File

@ -29,6 +29,29 @@ frappe.ui.form.on('Inpatient Medication Entry', {
}
};
});
if (frm.doc.__islocal || frm.doc.docstatus !== 0 || !frm.doc.update_stock)
return;
frm.add_custom_button(__('Make Stock Entry'), function() {
frappe.call({
method: 'erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry.make_difference_stock_entry',
args: { docname: frm.doc.name },
freeze: true,
callback: function(r) {
if (r.message) {
var doclist = frappe.model.sync(r.message);
frappe.set_route('Form', doclist[0].doctype, doclist[0].name);
} else {
frappe.msgprint({
title: __('No Drug Shortage'),
message: __('All the drugs are available with sufficient qty to process this Inpatient Medication Entry.'),
indicator: 'green'
});
}
}
});
});
},
patient: function(frm) {

View File

@ -142,25 +142,32 @@ class InpatientMedicationEntry(Document):
return orders, order_entry_map
def check_stock_qty(self):
from erpnext.stock.stock_ledger import NegativeStockError
drug_shortage = get_drug_shortage_map(self.medication_orders, self.warehouse)
drug_availability = dict()
for d in self.medication_orders:
if not drug_availability.get(d.drug_code):
drug_availability[d.drug_code] = 0
drug_availability[d.drug_code] += flt(d.dosage)
if drug_shortage:
message = _('Quantity not available for the following items in warehouse {0}. ').format(frappe.bold(self.warehouse))
message += _('Please enable Allow Negative Stock in Stock Settings or create Stock Entry to proceed.')
for drug, dosage in drug_availability.items():
available_qty = get_latest_stock_qty(drug, self.warehouse)
formatted_item_rows = ''
# validate qty
if flt(available_qty) < flt(dosage):
frappe.throw(_('Quantity not available for {0} in warehouse {1}').format(
frappe.bold(drug), frappe.bold(self.warehouse))
+ '<br><br>' + _('Available quantity is {0}, you need {1}').format(
frappe.bold(available_qty), frappe.bold(dosage))
+ '<br><br>' + _('Please enable Allow Negative Stock in Stock Settings or create Stock Entry to proceed.'),
NegativeStockError, title=_('Insufficient Stock'))
for drug, shortage_qty in drug_shortage.items():
item_link = get_link_to_form('Item', drug)
formatted_item_rows += """
<td>{0}</td>
<td>{1}</td>
</tr>""".format(item_link, frappe.bold(shortage_qty))
message += """
<table class='table'>
<thead>
<th>{0}</th>
<th>{1}</th>
</thead>
{2}
</table>
""".format(_('Drug Code'), _('Shortage Qty'), formatted_item_rows)
frappe.throw(message, title=_('Insufficient Stock'), is_minimizable=True, wide=True)
def make_stock_entry(self):
stock_entry = frappe.new_doc('Stock Entry')
@ -223,7 +230,8 @@ def get_pending_medication_orders(entry):
for doc in data:
inpatient_record = doc.inpatient_record
doc['service_unit'] = get_current_healthcare_service_unit(inpatient_record)
if inpatient_record:
doc['service_unit'] = get_current_healthcare_service_unit(inpatient_record)
if entry.service_unit and doc.service_unit != entry.service_unit:
to_remove.append(doc)
@ -276,4 +284,55 @@ def get_current_healthcare_service_unit(inpatient_record):
ip_record = frappe.get_doc('Inpatient Record', inpatient_record)
if ip_record.inpatient_occupancies:
return ip_record.inpatient_occupancies[-1].service_unit
return
return
def get_drug_shortage_map(medication_orders, warehouse):
"""
Returns a dict like { drug_code: shortage_qty }
"""
drug_requirement = dict()
for d in medication_orders:
if not drug_requirement.get(d.drug_code):
drug_requirement[d.drug_code] = 0
drug_requirement[d.drug_code] += flt(d.dosage)
drug_shortage = dict()
for drug, required_qty in drug_requirement.items():
available_qty = get_latest_stock_qty(drug, warehouse)
if flt(required_qty) > flt(available_qty):
drug_shortage[drug] = flt(flt(required_qty) - flt(available_qty))
return drug_shortage
@frappe.whitelist()
def make_difference_stock_entry(docname):
doc = frappe.get_doc('Inpatient Medication Entry', docname)
drug_shortage = get_drug_shortage_map(doc.medication_orders, doc.warehouse)
if not drug_shortage:
return None
stock_entry = frappe.new_doc('Stock Entry')
stock_entry.purpose = 'Material Transfer'
stock_entry.set_stock_entry_type()
stock_entry.to_warehouse = doc.warehouse
stock_entry.company = doc.company
cost_center = frappe.get_cached_value('Company', doc.company, 'cost_center')
expense_account = get_account(None, 'expense_account', 'Healthcare Settings', doc.company)
for drug, shortage_qty in drug_shortage.items():
se_child = stock_entry.append('items')
se_child.item_code = drug
se_child.item_name = frappe.db.get_value('Item', drug, 'stock_uom')
se_child.uom = frappe.db.get_value('Item', drug, 'stock_uom')
se_child.stock_uom = se_child.uom
se_child.qty = flt(shortage_qty)
se_child.t_warehouse = doc.warehouse
# in stock uom
se_child.conversion_factor = 1
se_child.cost_center = cost_center
se_child.expense_account = expense_account
return stock_entry

View File

@ -9,6 +9,7 @@ from frappe.utils import add_days, getdate, now_datetime
from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import create_patient, create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy
from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
from erpnext.healthcare.doctype.inpatient_medication_order.test_inpatient_medication_order import create_ipmo, create_ipme
from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import get_drug_shortage_map, make_difference_stock_entry
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_account
class TestInpatientMedicationEntry(unittest.TestCase):
@ -82,6 +83,39 @@ class TestInpatientMedicationEntry(unittest.TestCase):
self.assertEqual(stock_entry.items[0].patient, self.patient)
self.assertEqual(stock_entry.items[0].inpatient_medication_entry_child, ipme.medication_orders[0].name)
def test_drug_shortage_stock_entry(self):
ipmo = create_ipmo(self.patient)
ipmo.submit()
ipmo.reload()
date = add_days(getdate(), -1)
filters = frappe._dict(
from_date=date,
to_date=date,
from_time='',
to_time='',
item_code='Dextromethorphan',
patient=self.patient
)
# check drug shortage
ipme = create_ipme(filters, update_stock=1)
ipme.warehouse = 'Finished Goods - _TC'
ipme.save()
drug_shortage = get_drug_shortage_map(ipme.medication_orders, ipme.warehouse)
self.assertEqual(drug_shortage.get('Dextromethorphan'), 3)
# check material transfer for drug shortage
make_stock_entry()
stock_entry = make_difference_stock_entry(ipme.name)
self.assertEqual(stock_entry.items[0].item_code, 'Dextromethorphan')
self.assertEqual(stock_entry.items[0].qty, 3)
stock_entry.from_warehouse = 'Stores - _TC'
stock_entry.submit()
ipme.reload()
ipme.submit()
def tearDown(self):
# cleanup - Discharge
schedule_discharge(frappe.as_json({'patient': self.patient}))
@ -94,15 +128,12 @@ class TestInpatientMedicationEntry(unittest.TestCase):
for entry in frappe.get_all('Inpatient Medication Entry'):
doc = frappe.get_doc('Inpatient Medication Entry', entry.name)
doc.cancel()
frappe.db.delete('Stock Entry', {'inpatient_medication_entry': doc.name})
doc.delete()
for entry in frappe.get_all('Inpatient Medication Order'):
doc = frappe.get_doc('Inpatient Medication Order', entry.name)
doc.cancel()
doc.delete()
def make_stock_entry():
def make_stock_entry(warehouse=None):
frappe.db.set_value('Company', '_Test Company', {
'stock_adjustment_account': 'Stock Adjustment - _TC',
'default_inventory_account': 'Stock In Hand - _TC'
@ -110,7 +141,7 @@ def make_stock_entry():
stock_entry = frappe.new_doc('Stock Entry')
stock_entry.stock_entry_type = 'Material Receipt'
stock_entry.company = '_Test Company'
stock_entry.to_warehouse = 'Stores - _TC'
stock_entry.to_warehouse = warehouse or 'Stores - _TC'
expense_account = get_account(None, 'expense_account', 'Healthcare Settings', '_Test Company')
se_child = stock_entry.append('items')
se_child.item_code = 'Dextromethorphan'

View File

@ -18,6 +18,10 @@ def get_data():
{
'label': _('Billing'),
'items': ['Sales Invoice']
},
{
'label': _('Orders'),
'items': ['Inpatient Medication Order']
}
]
}

View File

@ -22,6 +22,7 @@ frappe.ui.form.on('Patient Appointment', {
filters: {'status': 'Active'}
};
});
frm.set_query('practitioner', function() {
return {
filters: {
@ -29,6 +30,7 @@ frappe.ui.form.on('Patient Appointment', {
}
};
});
frm.set_query('service_unit', function(){
return {
filters: {
@ -39,6 +41,16 @@ frappe.ui.form.on('Patient Appointment', {
};
});
frm.set_query('therapy_plan', function() {
return {
filters: {
'patient': frm.doc.patient
}
};
});
frm.trigger('set_therapy_type_filter');
if (frm.is_new()) {
frm.page.set_primary_action(__('Check Availability'), function() {
if (!frm.doc.patient) {
@ -136,6 +148,24 @@ frappe.ui.form.on('Patient Appointment', {
}
},
therapy_plan: function(frm) {
frm.trigger('set_therapy_type_filter');
},
set_therapy_type_filter: function(frm) {
if (frm.doc.therapy_plan) {
frm.call('get_therapy_types').then(r => {
frm.set_query('therapy_type', function() {
return {
filters: {
'name': ['in', r.message]
}
};
});
});
}
},
therapy_type: function(frm) {
if (frm.doc.therapy_type) {
frappe.db.get_value('Therapy Type', frm.doc.therapy_type, 'default_duration', (r) => {

View File

@ -23,9 +23,9 @@
"procedure_template",
"get_procedure_from_encounter",
"procedure_prescription",
"therapy_plan",
"therapy_type",
"get_prescribed_therapies",
"therapy_plan",
"practitioner",
"practitioner_name",
"department",
@ -284,7 +284,7 @@
"report_hide": 1
},
{
"depends_on": "eval:doc.patient;",
"depends_on": "eval:doc.patient && doc.therapy_plan;",
"fieldname": "therapy_type",
"fieldtype": "Link",
"label": "Therapy",
@ -292,17 +292,16 @@
"set_only_once": 1
},
{
"depends_on": "eval:doc.patient && doc.__islocal;",
"depends_on": "eval:doc.patient && doc.therapy_plan && doc.__islocal;",
"fieldname": "get_prescribed_therapies",
"fieldtype": "Button",
"label": "Get Prescribed Therapies"
},
{
"depends_on": "eval: doc.patient && doc.therapy_type",
"depends_on": "eval: doc.patient;",
"fieldname": "therapy_plan",
"fieldtype": "Link",
"label": "Therapy Plan",
"mandatory_depends_on": "eval: doc.patient && doc.therapy_type",
"options": "Therapy Plan"
},
{
@ -348,7 +347,7 @@
}
],
"links": [],
"modified": "2020-05-21 03:04:21.400893",
"modified": "2020-12-16 13:16:58.578503",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Patient Appointment",

View File

@ -91,6 +91,17 @@ class PatientAppointment(Document):
if fee_validity:
frappe.msgprint(_('{0} has fee validity till {1}').format(self.patient, fee_validity.valid_till))
def get_therapy_types(self):
if not self.therapy_plan:
return
therapy_types = []
doc = frappe.get_doc('Therapy Plan', self.therapy_plan)
for entry in doc.therapy_plan_details:
therapy_types.append(entry.therapy_type)
return therapy_types
@frappe.whitelist()
def check_payment_fields_reqd(patient):
@ -145,7 +156,7 @@ def invoice_appointment(appointment_doc):
sales_invoice.flags.ignore_mandatory = True
sales_invoice.save(ignore_permissions=True)
sales_invoice.submit()
frappe.msgprint(_('Sales Invoice {0} created'.format(sales_invoice.name)), alert=True)
frappe.msgprint(_('Sales Invoice {0} created').format(sales_invoice.name), alert=True)
frappe.db.set_value('Patient Appointment', appointment_doc.name, 'invoiced', 1)
frappe.db.set_value('Patient Appointment', appointment_doc.name, 'ref_sales_invoice', sales_invoice.name)

View File

@ -397,7 +397,8 @@ regional_overrides = {
'erpnext.accounts.party.get_regional_address_details': 'erpnext.regional.india.utils.get_regional_address_details',
'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption',
'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period',
'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries'
'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries',
'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields'
},
'United Arab Emirates': {
'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data',

View File

@ -135,7 +135,7 @@ class Employee(NestedSet):
try:
frappe.get_doc({
"doctype": "File",
"file_name": self.image,
"file_url": self.image,
"attached_to_doctype": "User",
"attached_to_name": self.user_id
}).insert()

View File

@ -18,13 +18,18 @@ frappe.ui.form.on('Employee Advance', {
if (!frm.doc.employee) {
frappe.msgprint(__("Please select employee first"));
}
var company_currency = erpnext.get_currency(frm.doc.company);
let company_currency = erpnext.get_currency(frm.doc.company);
let currencies = [company_currency];
if (frm.doc.currency && (frm.doc.currency != company_currency)) {
currencies.push(frm.doc.currency);
}
return {
filters: {
"root_type": "Asset",
"is_group": 0,
"company": frm.doc.company,
"account_currency": ["in", [frm.doc.currency, company_currency]],
"account_currency": ["in", currencies],
}
};
});
@ -181,21 +186,23 @@ frappe.ui.form.on('Employee Advance', {
},
currency: function(frm) {
var from_currency = frm.doc.currency;
var company_currency;
if (!frm.doc.company) {
company_currency = erpnext.get_currency(frappe.defaults.get_default("Company"));
} else {
company_currency = erpnext.get_currency(frm.doc.company);
if (frm.doc.currency) {
var from_currency = frm.doc.currency;
var company_currency;
if (!frm.doc.company) {
company_currency = erpnext.get_currency(frappe.defaults.get_default("Company"));
} else {
company_currency = erpnext.get_currency(frm.doc.company);
}
if (from_currency != company_currency) {
frm.events.set_exchange_rate(frm, from_currency, company_currency);
} else {
frm.set_value("exchange_rate", 1.0);
frm.set_df_property('exchange_rate', 'hidden', 1);
frm.set_df_property("exchange_rate", "description", "" );
}
frm.refresh_fields();
}
if (from_currency != company_currency) {
frm.events.set_exchange_rate(frm, from_currency, company_currency);
} else {
frm.set_value("exchange_rate", 1.0);
frm.set_df_property('exchange_rate', 'hidden', 1);
frm.set_df_property("exchange_rate", "description", "" );
}
frm.refresh_fields();
},
set_exchange_rate: function(frm, from_currency, company_currency) {

View File

@ -4,22 +4,10 @@ from frappe import _
def get_data():
return {
'fieldname': 'leave_policy',
'non_standard_fieldnames': {
'Employee Grade': 'default_leave_policy'
},
'transactions': [
{
'label': _('Employees'),
'items': ['Employee', 'Employee Grade']
},
{
'label': _('Leaves'),
'items': ['Leave Allocation']
},
]
}
}

View File

@ -111,13 +111,14 @@
],
"is_submittable": 1,
"links": [],
"modified": "2020-10-15 15:18:15.227848",
"modified": "2020-12-31 16:43:30.695206",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Policy Assignment",
"owner": "Administrator",
"permissions": [
{
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
@ -127,9 +128,11 @@
"report": 1,
"role": "HR Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
@ -139,9 +142,11 @@
"report": 1,
"role": "HR User",
"share": 1,
"submit": 1,
"write": 1
},
{
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
@ -151,6 +156,7 @@
"report": 1,
"role": "System Manager",
"share": 1,
"submit": 1,
"write": 1
}
],

View File

@ -6,6 +6,7 @@ from __future__ import unicode_literals
import frappe, math, json
import erpnext
from frappe import _
from six import string_types
from frappe.utils import flt, rounded, add_months, nowdate, getdate, now_datetime
from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty
from erpnext.controllers.accounts_controller import AccountsController
@ -280,10 +281,13 @@ def make_loan_write_off(loan, company=None, posting_date=None, amount=0, as_dict
return write_off
@frappe.whitelist()
def unpledge_security(loan=None, loan_security_pledge=None, as_dict=0, save=0, submit=0, approve=0):
# if loan is passed it will be considered as full unpledge
def unpledge_security(loan=None, loan_security_pledge=None, security_map=None, as_dict=0, save=0, submit=0, approve=0):
# if no security_map is passed it will be considered as full unpledge
if security_map and isinstance(security_map, string_types):
security_map = json.loads(security_map)
if loan:
pledge_qty_map = get_pledged_security_qty(loan)
pledge_qty_map = security_map or get_pledged_security_qty(loan)
loan_doc = frappe.get_doc('Loan', loan)
unpledge_request = create_loan_security_unpledge(pledge_qty_map, loan_doc.name, loan_doc.company,
loan_doc.applicant_type, loan_doc.applicant)

View File

@ -45,7 +45,7 @@ class TestLoan(unittest.TestCase):
create_loan_security_price("Test Security 2", 250, "Nos", get_datetime() , get_datetime(add_to_date(nowdate(), hours=24)))
self.applicant1 = make_employee("robert_loan@loan.com")
make_salary_structure("Test Salary Structure Loan", "Monthly", employee=self.applicant1, currency='INR')
make_salary_structure("Test Salary Structure Loan", "Monthly", employee=self.applicant1, currency='INR', company="_Test Company")
if not frappe.db.exists("Customer", "_Test Loan Customer"):
frappe.get_doc(get_customer_dict('_Test Loan Customer')).insert(ignore_permissions=True)
@ -325,6 +325,43 @@ class TestLoan(unittest.TestCase):
self.assertEquals(amounts['payable_principal_amount'], 0.0)
self.assertEqual(amounts['interest_amount'], 0)
def test_partial_loan_security_unpledge(self):
pledge = [{
"loan_security": "Test Security 1",
"qty": 2000.00
},
{
"loan_security": "Test Security 2",
"qty": 4000.00
}]
loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
create_pledge(loan_application)
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
loan.submit()
self.assertEquals(loan.loan_amount, 1000000)
first_date = '2019-10-01'
last_date = '2019-10-30'
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5), 600000)
repayment_entry.submit()
unpledge_map = {'Test Security 2': 2000}
unpledge_request = unpledge_security(loan=loan.name, security_map = unpledge_map, save=1)
unpledge_request.submit()
unpledge_request.status = 'Approved'
unpledge_request.save()
unpledge_request.submit()
unpledge_request.load_from_db()
self.assertEqual(unpledge_request.docstatus, 1)
def test_disbursal_check_with_shortfall(self):
pledges = [{
"loan_security": "Test Security 2",

View File

@ -81,7 +81,6 @@ def check_for_ltv_shortfall(process_loan_security_shortfall):
process_loan_security_shortfall)
def create_loan_security_shortfall(loan, loan_amount, security_value, shortfall_amount, process_loan_security_shortfall):
existing_shortfall = frappe.db.get_value("Loan Security Shortfall", {"loan": loan, "status": "Pending"}, "name")
if existing_shortfall:

View File

@ -30,6 +30,8 @@ class LoanSecurityUnpledge(Document):
d.idx, frappe.bold(d.loan_security)))
def validate_unpledge_qty(self):
from erpnext.loan_management.doctype.loan_security_shortfall.loan_security_shortfall import get_ltv_ratio
pledge_qty_map = get_pledged_security_qty(self.loan)
ltv_ratio_map = frappe._dict(frappe.get_all("Loan Security Type",
@ -47,6 +49,8 @@ class LoanSecurityUnpledge(Document):
pending_principal_amount = flt(total_payment) - flt(interest_payable) - flt(principal_paid) - flt(written_off_amount)
security_value = 0
unpledge_qty_map = {}
ltv_ratio = 0
for security in self.securities:
pledged_qty = pledge_qty_map.get(security.loan_security, 0)
@ -57,13 +61,15 @@ class LoanSecurityUnpledge(Document):
msg += _("You are trying to unpledge more.")
frappe.throw(msg, title=_("Loan Security Unpledge Error"))
qty_after_unpledge = pledged_qty - security.qty
ltv_ratio = ltv_ratio_map.get(security.loan_security_type)
unpledge_qty_map.setdefault(security.loan_security, 0)
unpledge_qty_map[security.loan_security] += security.qty
current_price = loan_security_price_map.get(security.loan_security)
if not current_price:
frappe.throw(_("No valid Loan Security Price found for {0}").format(frappe.bold(security.loan_security)))
for security in pledge_qty_map:
if not ltv_ratio:
ltv_ratio = get_ltv_ratio(security)
qty_after_unpledge = pledge_qty_map.get(security, 0) - unpledge_qty_map.get(security, 0)
current_price = loan_security_price_map.get(security)
security_value += qty_after_unpledge * current_price
if not security_value and flt(pending_principal_amount, 2) > 0:

View File

@ -17,6 +17,7 @@ class OverlapError(frappe.ValidationError): pass
class OperationMismatchError(frappe.ValidationError): pass
class OperationSequenceError(frappe.ValidationError): pass
class JobCardCancelError(frappe.ValidationError): pass
class JobCard(Document):
def validate(self):
@ -217,33 +218,49 @@ class JobCard(Document):
field = "operation_id"
data = self.get_current_operation_data()
if data and len(data) > 0:
for_quantity = data[0].completed_qty
time_in_mins = data[0].time_in_mins
for_quantity = flt(data[0].completed_qty)
time_in_mins = flt(data[0].time_in_mins)
if self.get(field):
time_data = frappe.db.sql("""
wo = frappe.get_doc('Work Order', self.work_order)
if self.operation_id:
self.validate_produced_quantity(for_quantity, wo)
self.update_work_order_data(for_quantity, time_in_mins, wo)
def validate_produced_quantity(self, for_quantity, wo):
if self.docstatus < 2: return
if wo.produced_qty > for_quantity:
first_part_msg = (_("The {0} {1} is used to calculate the valuation cost for the finished good {2}.")
.format(frappe.bold(_("Job Card")), frappe.bold(self.name), frappe.bold(self.production_item)))
second_part_msg = (_("Kindly cancel the Manufacturing Entries first against the work order {0}.")
.format(frappe.bold(get_link_to_form("Work Order", self.work_order))))
frappe.throw(_("{0} {1}").format(first_part_msg, second_part_msg),
JobCardCancelError, title = _("Error"))
def update_work_order_data(self, for_quantity, time_in_mins, wo):
time_data = frappe.db.sql("""
SELECT
min(from_time) as start_time, max(to_time) as end_time
FROM `tabJob Card` jc, `tabJob Card Time Log` jctl
WHERE
jctl.parent = jc.name and jc.work_order = %s
and jc.{0} = %s and jc.docstatus = 1
""".format(field), (self.work_order, self.get(field)), as_dict=1)
and jc.operation_id = %s and jc.docstatus = 1
""", (self.work_order, self.operation_id), as_dict=1)
wo = frappe.get_doc('Work Order', self.work_order)
for data in wo.operations:
if data.get("name") == self.operation_id:
data.completed_qty = for_quantity
data.actual_operation_time = time_in_mins
data.actual_start_time = time_data[0].start_time if time_data else None
data.actual_end_time = time_data[0].end_time if time_data else None
for data in wo.operations:
if data.get("name") == self.get(field):
data.completed_qty = for_quantity
data.actual_operation_time = time_in_mins
data.actual_start_time = time_data[0].start_time if time_data else None
data.actual_end_time = time_data[0].end_time if time_data else None
wo.flags.ignore_validate_update_after_submit = True
wo.update_operation_status()
wo.calculate_operating_cost()
wo.set_actual_dates()
wo.save()
wo.flags.ignore_validate_update_after_submit = True
wo.update_operation_status()
wo.calculate_operating_cost()
wo.set_actual_dates()
wo.save()
def get_current_operation_data(self):
return frappe.get_all('Job Card',

View File

@ -8,7 +8,17 @@ frappe.views.calendar["Job Card"] = {
"allDay": "allDay",
"progress": "progress"
},
gantt: true,
gantt: {
field_map: {
"start": "started_time",
"end": "started_time",
"id": "name",
"title": "subject",
"color": "color",
"allDay": "allDay",
"progress": "progress"
}
},
filters: [
{
"fieldtype": "Link",

View File

@ -5,8 +5,7 @@
from __future__ import unicode_literals
import unittest
import frappe
from frappe.utils import flt, time_diff_in_hours, now, add_months, cint, today
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
from frappe.utils import flt, now, add_months, cint, today, add_to_date
from erpnext.manufacturing.doctype.work_order.work_order import (make_stock_entry,
ItemHasVariantError, stop_unstop, StockOverProductionError, OverProductionError, CapacityError)
from erpnext.stock.doctype.stock_entry import test_stock_entry
@ -15,10 +14,10 @@ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_orde
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
from erpnext.manufacturing.doctype.job_card.job_card import JobCardCancelError
class TestWorkOrder(unittest.TestCase):
def setUp(self):
set_perpetual_inventory(0)
self.warehouse = '_Test Warehouse 2 - _TC'
self.item = '_Test Item'
@ -371,21 +370,49 @@ class TestWorkOrder(unittest.TestCase):
self.assertEqual(ste.total_additional_costs, 1000)
def test_job_card(self):
stock_entries = []
data = frappe.get_cached_value('BOM',
{'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item'])
if data:
frappe.db.set_value("Manufacturing Settings",
None, "disable_capacity_planning", 0)
bom, bom_item = data
bom, bom_item = data
bom_doc = frappe.get_doc('BOM', bom)
work_order = make_wo_order_test_record(item=bom_item, qty=1,
bom_no=bom, source_warehouse="_Test Warehouse - _TC")
bom_doc = frappe.get_doc('BOM', bom)
work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom)
self.assertTrue(work_order.planned_end_date)
for row in work_order.required_items:
stock_entry_doc = test_stock_entry.make_stock_entry(item_code=row.item_code,
target="_Test Warehouse - _TC", qty=row.required_qty, basic_rate=100)
stock_entries.append(stock_entry_doc)
job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order.name})
self.assertEqual(len(job_cards), len(bom_doc.operations))
ste = frappe.get_doc(make_stock_entry(work_order.name, "Material Transfer for Manufacture", 1))
ste.submit()
stock_entries.append(ste)
job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order.name})
self.assertEqual(len(job_cards), len(bom_doc.operations))
for i, job_card in enumerate(job_cards):
doc = frappe.get_doc("Job Card", job_card)
doc.append("time_logs", {
"from_time": now(),
"hours": i,
"to_time": add_to_date(now(), i),
"completed_qty": doc.for_quantity
})
doc.submit()
ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1))
ste1.submit()
stock_entries.append(ste1)
for job_card in job_cards:
doc = frappe.get_doc("Job Card", job_card)
self.assertRaises(JobCardCancelError, doc.cancel)
stock_entries.reverse()
for stock_entry in stock_entries:
stock_entry.cancel()
def test_capcity_planning(self):
frappe.db.set_value("Manufacturing Settings", None, {
@ -491,6 +518,38 @@ class TestWorkOrder(unittest.TestCase):
work_order1.save()
self.assertEqual(work_order1.operations[0].time_in_mins, 40.0)
def test_partial_material_consumption(self):
frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 1)
wo_order = make_wo_order_test_record(planned_start_date=now(), qty=4)
ste_cancel_list = []
ste1 = test_stock_entry.make_stock_entry(item_code="_Test Item",
target="_Test Warehouse - _TC", qty=20, basic_rate=5000.0)
ste2 = test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
target="_Test Warehouse - _TC", qty=20, basic_rate=1000.0)
ste_cancel_list.extend([ste1, ste2])
s = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 4))
s.submit()
ste_cancel_list.append(s)
ste1 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2))
ste1.submit()
ste_cancel_list.append(ste1)
ste3 = frappe.get_doc(make_stock_entry(wo_order.name, "Material Consumption for Manufacture", 2))
self.assertEquals(ste3.fg_completed_qty, 2)
expected_qty = {"_Test Item": 2, "_Test Item Home Desktop 100": 4}
for row in ste3.items:
self.assertEquals(row.qty, expected_qty.get(row.item_code))
for ste_doc in ste_cancel_list:
ste_doc.cancel()
frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 0)
def get_scrap_item_details(bom_no):
scrap_items = {}
for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item`

View File

@ -545,7 +545,8 @@ erpnext.work_order = {
var tbl = frm.doc.required_items || [];
var tbl_lenght = tbl.length;
for (var i = 0, len = tbl_lenght; i < len; i++) {
if (flt(frm.doc.required_items[i].required_qty) > flt(frm.doc.required_items[i].consumed_qty)) {
let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty;
if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) {
counter += 1;
}
}

View File

@ -124,7 +124,7 @@ class ProductionPlanReport(object):
if self.filters.include_subassembly_raw_materials else "(bom_item.qty / bom.quantity)")
raw_materials = frappe.db.sql(""" SELECT bom_item.parent, bom_item.item_code,
bom_item.item_name as raw_material_name, {0} as required_qty
bom_item.item_name as raw_material_name, {0} as required_qty_per_unit
FROM
`tabBOM` as bom, `tab{1}` as bom_item
WHERE
@ -208,7 +208,7 @@ class ProductionPlanReport(object):
warehouses = self.mrp_warehouses or []
for d in self.raw_materials_dict.get(key):
if self.filters.based_on != "Work Order":
d.required_qty = d.required_qty * data.qty_to_manufacture
d.required_qty = d.required_qty_per_unit * data.qty_to_manufacture
if not warehouses:
warehouses = [data.warehouse]

View File

@ -59,7 +59,7 @@ class Member(Document):
frappe.msgprint(_("A customer is already linked to this Member"))
cust = create_customer(frappe._dict({
'fullname': self.member_name,
'email': self.email_id or self.user,
'email': self.email_id or self.email,
'phone': None
}))
@ -177,4 +177,4 @@ def register_member(fullname, email, rzpay_plan_id, subscription_id, pan=None, m
mobile=mobile
))
return member.name
return member.name

View File

@ -450,7 +450,6 @@ erpnext.patches.v8_9.set_member_party_type
erpnext.patches.v9_0.add_user_to_child_table_in_pos_profile
erpnext.patches.v9_0.set_schedule_date_for_material_request_and_purchase_order
erpnext.patches.v9_0.student_admission_childtable_migrate
erpnext.patches.v9_0.fix_subscription_next_date #2017-10-23
erpnext.patches.v9_0.add_healthcare_domain
erpnext.patches.v9_0.set_variant_item_description
erpnext.patches.v9_0.set_uoms_in_variant_field
@ -712,6 +711,7 @@ erpnext.patches.v13_0.delete_old_sales_reports
execute:frappe.delete_doc_if_exists("DocType", "Bank Reconciliation")
erpnext.patches.v13_0.move_doctype_reports_and_notification_from_hr_to_payroll #22-06-2020
erpnext.patches.v13_0.move_payroll_setting_separately_from_hr_settings #22-06-2020
execute:frappe.reload_doc("regional", "doctype", "e_invoice_settings")
erpnext.patches.v13_0.check_is_income_tax_component #22-06-2020
erpnext.patches.v13_0.loyalty_points_entry_for_pos_invoice #22-07-2020
erpnext.patches.v12_0.add_taxjar_integration_field
@ -733,6 +733,7 @@ erpnext.patches.v13_0.set_youtube_video_id
erpnext.patches.v13_0.print_uom_after_quantity_patch
erpnext.patches.v13_0.set_payment_channel_in_payment_gateway_account
erpnext.patches.v13_0.create_healthcare_custom_fields_in_stock_entry_detail
erpnext.patches.v12_0.setup_einvoice_fields #2020-12-02
erpnext.patches.v13_0.updates_for_multi_currency_payroll
erpnext.patches.v13_0.update_reason_for_resignation_in_employee
erpnext.patches.v13_0.update_custom_fields_for_shopify

View File

@ -0,0 +1,56 @@
from __future__ import unicode_literals
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from erpnext.regional.india.setup import add_permissions, add_print_formats
def execute():
company = frappe.get_all('Company', filters = {'country': 'India'})
if not company:
return
frappe.reload_doc("custom", "doctype", "custom_field")
frappe.reload_doc("regional", "doctype", "e_invoice_settings")
custom_fields = {
'Sales Invoice': [
dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1,
depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'),
dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='irn', no_copy=1, print_hide=1),
dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1),
dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
dict(fieldname='signed_einvoice', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1),
dict(fieldname='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1),
dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, no_copy=1, print_hide=1, read_only=1)
]
}
create_custom_fields(custom_fields, update=True)
add_permissions()
add_print_formats()
einvoice_cond = 'in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category)'
t = {
'mode_of_transport': [{'default': None}],
'distance': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.transporter'}],
'gst_vehicle_type': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.mode_of_transport == "Road"'}],
'lr_date': [{'mandatory_depends_on': f'eval:{einvoice_cond} && in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)'}],
'lr_no': [{'mandatory_depends_on': f'eval:{einvoice_cond} && in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)'}],
'vehicle_no': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.mode_of_transport == "Road"'}],
'ewaybill': [
{'read_only_depends_on': 'eval:doc.irn && doc.ewaybill'},
{'depends_on': 'eval:((doc.docstatus === 1 || doc.ewaybill) && doc.eway_bill_cancelled === 0)'}
]
}
for field, conditions in t.items():
for c in conditions:
[(prop, value)] = c.items()
frappe.db.set_value('Custom Field', { 'fieldname': field }, prop, value)

View File

@ -1,10 +1,12 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import nowdate
from frappe.utils import nowdate, flt
from erpnext.accounts.doctype.account.test_account import create_account
from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans
from erpnext.loan_management.doctype.loan.loan import make_repayment_entry
from erpnext.loan_management.doctype.loan_repayment.loan_repayment import get_accrued_interest_entries
from frappe.model.naming import make_autoname
def execute():
@ -18,15 +20,29 @@ def execute():
frappe.reload_doc('loan_management', 'doctype', 'loan_repayment_detail')
frappe.reload_doc('loan_management', 'doctype', 'loan_interest_accrual')
frappe.reload_doc('accounts', 'doctype', 'gl_entry')
frappe.reload_doc('accounts', 'doctype', 'journal_entry_account')
updated_loan_types = []
loans_to_close = []
# Update old loan status as closed
if frappe.db.has_column('Repayment Schedule', 'paid'):
loans_list = frappe.db.sql("""SELECT distinct parent from `tabRepayment Schedule`
where paid = 0 and docstatus = 1""", as_dict=1)
loans_to_close = [d.parent for d in loans_list]
if loans_to_close:
frappe.db.sql("UPDATE `tabLoan` set status = 'Closed' where name not in (%s)" % (', '.join(['%s'] * len(loans_to_close))), tuple(loans_to_close))
loans = frappe.get_all('Loan', fields=['name', 'loan_type', 'company', 'status', 'mode_of_payment',
'applicant_type', 'applicant', 'loan_account', 'payment_account', 'interest_income_account'])
'applicant_type', 'applicant', 'loan_account', 'payment_account', 'interest_income_account'],
filters={'docstatus': 1, 'status': ('!=', 'Closed')})
for loan in loans:
# Update details in Loan Types and Loan
loan_type_company = frappe.db.get_value('Loan Type', loan.loan_type, 'company')
loan_type = loan.loan_type
group_income_account = frappe.get_value('Account', {'company': loan.company,
'is_group': 1, 'root_type': 'Income', 'account_name': _('Indirect Income')})
@ -38,7 +54,26 @@ def execute():
penalty_account = create_account(company=loan.company, account_type='Income Account',
account_name='Penalty Account', parent_account=group_income_account)
if not loan_type_company:
# Same loan type used for multiple companies
if loan_type_company and loan_type_company != loan.company:
# get loan type for appropriate company
loan_type_name = frappe.get_value('Loan Type', {'company': loan.company,
'mode_of_payment': loan.mode_of_payment, 'loan_account': loan.loan_account,
'payment_account': loan.payment_account, 'interest_income_account': loan.interest_income_account,
'penalty_income_account': loan.penalty_income_account}, 'name')
if not loan_type_name:
loan_type_name = create_loan_type(loan, loan_type_name, penalty_account)
# update loan type in loan
frappe.db.sql("UPDATE `tabLoan` set loan_type = %s where name = %s", (loan_type_name,
loan.name))
loan_type = loan_type_name
if loan_type_name not in updated_loan_types:
updated_loan_types.append(loan_type_name)
elif not loan_type_company:
loan_type_doc = frappe.get_doc('Loan Type', loan.loan_type)
loan_type_doc.is_term_loan = 1
loan_type_doc.company = loan.company
@ -49,8 +84,9 @@ def execute():
loan_type_doc.penalty_income_account = penalty_account
loan_type_doc.submit()
updated_loan_types.append(loan.loan_type)
loan_type = loan.loan_type
if loan.loan_type in updated_loan_types:
if loan_type in updated_loan_types:
if loan.status == 'Fully Disbursed':
status = 'Disbursed'
elif loan.status == 'Repaid/Closed':
@ -64,25 +100,48 @@ def execute():
'status': status
})
process_loan_interest_accrual_for_term_loans(posting_date=nowdate(), loan_type=loan.loan_type,
process_loan_interest_accrual_for_term_loans(posting_date=nowdate(), loan_type=loan_type,
loan=loan.name)
payments = frappe.db.sql(''' SELECT j.name, a.debit, a.debit_in_account_currency, j.posting_date
FROM `tabJournal Entry` j, `tabJournal Entry Account` a
WHERE a.parent = j.name and a.reference_type='Loan' and a.reference_name = %s
and account = %s
''', (loan.name, loan.loan_account), as_dict=1)
for payment in payments:
repayment_entry = make_repayment_entry(loan.name, loan.loan_applicant_type, loan.applicant,
loan.loan_type, loan.company)
if frappe.db.has_column('Repayment Schedule', 'paid'):
total_principal, total_interest = frappe.db.get_value('Repayment Schedule', {'paid': 1, 'parent': loan.name},
['sum(principal_amount) as total_principal', 'sum(interest_amount) as total_interest'])
repayment_entry.amount_paid = payment.debit_in_account_currency
repayment_entry.posting_date = payment.posting_date
repayment_entry.save()
repayment_entry.submit()
accrued_entries = get_accrued_interest_entries(loan.name)
for entry in accrued_entries:
interest_paid = 0
principal_paid = 0
jv = frappe.get_doc('Journal Entry', payment.name)
jv.flags.ignore_links = True
jv.cancel()
if flt(total_interest) > flt(entry.interest_amount):
interest_paid = flt(entry.interest_amount)
else:
interest_paid = flt(total_interest)
if flt(total_principal) > flt(entry.payable_principal_amount):
principal_paid = flt(entry.payable_principal_amount)
else:
principal_paid = flt(total_principal)
frappe.db.sql(""" UPDATE `tabLoan Interest Accrual`
SET paid_principal_amount = `paid_principal_amount` + %s,
paid_interest_amount = `paid_interest_amount` + %s
WHERE name = %s""",
(principal_paid, interest_paid, entry.name))
total_principal = flt(total_principal) - principal_paid
total_interest = flt(total_interest) - interest_paid
def create_loan_type(loan, loan_type_name, penalty_account):
loan_type_doc = frappe.new_doc('Loan Type')
loan_type_doc.loan_name = make_autoname("Loan Type-.####")
loan_type_doc.is_term_loan = 1
loan_type_doc.company = loan.company
loan_type_doc.mode_of_payment = loan.mode_of_payment
loan_type_doc.payment_account = loan.payment_account
loan_type_doc.loan_account = loan.loan_account
loan_type_doc.interest_income_account = loan.interest_income_account
loan_type_doc.penalty_income_account = penalty_account
loan_type_doc.submit()
return loan_type_doc.name

View File

@ -1,48 +0,0 @@
# Copyright (c) 2017, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from frappe.utils import getdate
from frappe.automation.doctype.auto_repeat.auto_repeat import get_next_schedule_date
def execute():
frappe.reload_doc('accounts', 'doctype', 'subscription')
fields = ["name", "reference_doctype", "reference_document",
"start_date", "frequency", "repeat_on_day"]
for d in fields:
if not frappe.db.has_column('Subscription', d):
return
doctypes = ('Purchase Order', 'Sales Order', 'Purchase Invoice', 'Sales Invoice')
for data in frappe.get_all('Subscription',
fields = fields,
filters = {'reference_doctype': ('in', doctypes), 'docstatus': 1}):
recurring_id = frappe.db.get_value(data.reference_doctype, data.reference_document, "recurring_id")
if recurring_id:
frappe.db.sql("update `tab{0}` set subscription=%s where recurring_id=%s"
.format(data.reference_doctype), (data.name, recurring_id))
date_field = 'transaction_date'
if data.reference_doctype in ['Sales Invoice', 'Purchase Invoice']:
date_field = 'posting_date'
start_date = frappe.db.get_value(data.reference_doctype, data.reference_document, date_field)
if start_date and getdate(start_date) != getdate(data.start_date):
last_ref_date = frappe.db.sql("""
select {0}
from `tab{1}`
where subscription=%s and docstatus < 2
order by creation desc
limit 1
""".format(date_field, data.reference_doctype), data.name)[0][0]
next_schedule_date = get_next_schedule_date(last_ref_date, data.frequency, data.repeat_on_day)
frappe.db.set_value("Subscription", data.name, {
"start_date": start_date,
"next_schedule_date": next_schedule_date
}, None)

View File

@ -12,14 +12,6 @@ frappe.ui.form.on('Additional Salary', {
}
};
});
if (!frm.doc.currency) return;
frm.set_query("salary_component", function() {
return {
query: "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components",
filters: {currency: frm.doc.currency, company: frm.doc.company}
};
});
},
employee: function(frm) {

View File

@ -23,6 +23,7 @@
"employee_benefits",
"totals",
"total_amount",
"column_break",
"pro_rata_dispensed_amount"
],
"fields": [
@ -139,11 +140,15 @@
"label": "Company",
"options": "Company",
"reqd": 1
},
{
"fieldname": "column_break",
"fieldtype": "Column Break"
}
],
"is_submittable": 1,
"links": [],
"modified": "2020-11-25 11:49:05.095101",
"modified": "2020-12-14 15:52:08.566418",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Employee Benefit Application",

View File

@ -11,11 +11,11 @@ frappe.ui.form.on('Employee Incentive', {
};
});
if (!frm.doc.currency) return;
if (!frm.doc.company) return;
frm.set_query("salary_component", function() {
return {
query: "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components",
filters: {type: "earning", currency: frm.doc.currency, company: frm.doc.company}
filters: {type: "earning", company: frm.doc.company}
};
});

View File

@ -86,19 +86,21 @@ class TestEmployeeTaxExemptionDeclaration(unittest.TestCase):
self.assertEqual(declaration.total_exemption_amount, 100000)
def create_payroll_period():
if not frappe.db.exists("Payroll Period", "_Test Payroll Period"):
def create_payroll_period(**args):
args = frappe._dict(args)
name = args.name or "_Test Payroll Period"
if not frappe.db.exists("Payroll Period", name):
from datetime import date
payroll_period = frappe.get_doc(dict(
doctype = 'Payroll Period',
name = "_Test Payroll Period",
company = erpnext.get_default_company(),
start_date = date(date.today().year, 1, 1),
end_date = date(date.today().year, 12, 31)
name = name,
company = args.company or erpnext.get_default_company(),
start_date = args.start_date or date(date.today().year, 1, 1),
end_date = args.end_date or date(date.today().year, 12, 31)
)).insert()
return payroll_period
else:
return frappe.get_doc("Payroll Period", "_Test Payroll Period")
return frappe.get_doc("Payroll Period", name)
def create_exemption_category():
if not frappe.db.exists("Employee Tax Exemption Category", "_Test Category"):

View File

@ -3,8 +3,11 @@
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
#import frappe
import erpnext
from frappe.model.document import Document
class IncomeTaxSlab(Document):
pass
def validate(self):
if self.company:
self.currency = erpnext.get_company_currency(self.company)

View File

@ -17,8 +17,7 @@
"fieldtype": "Link",
"in_list_view": 1,
"label": "Employee",
"options": "Employee",
"read_only": 1
"options": "Employee"
},
{
"fetch_from": "employee.employee_name",
@ -52,7 +51,7 @@
],
"istable": 1,
"links": [],
"modified": "2020-09-30 12:40:07.999878",
"modified": "2020-12-17 15:43:29.542977",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Payroll Employee Detail",

View File

@ -10,15 +10,22 @@ frappe.ui.form.on('Payroll Entry', {
}
frm.toggle_reqd(['payroll_frequency'], !frm.doc.salary_slip_based_on_timesheet);
frm.set_query("department", function() {
frm.events.department_filters(frm);
frm.events.payroll_payable_account_filters(frm);
},
department_filters: function (frm) {
frm.set_query("department", function () {
return {
"filters": {
"company": frm.doc.company,
}
};
});
},
frm.set_query("payroll_payable_account", function() {
payroll_payable_account_filters: function (frm) {
frm.set_query("payroll_payable_account", function () {
return {
filters: {
"company": frm.doc.company,
@ -29,12 +36,12 @@ frappe.ui.form.on('Payroll Entry', {
});
},
refresh: function(frm) {
refresh: function (frm) {
if (frm.doc.docstatus == 0) {
if(!frm.is_new()) {
if (!frm.is_new()) {
frm.page.clear_primary_action();
frm.add_custom_button(__("Get Employees"),
function() {
function () {
frm.events.get_employee_details(frm);
}
).toggleClass('btn-primary', !(frm.doc.employees || []).length);
@ -42,7 +49,7 @@ frappe.ui.form.on('Payroll Entry', {
if ((frm.doc.employees || []).length) {
frm.page.clear_primary_action();
frm.page.set_primary_action(__('Create Salary Slips'), () => {
frm.save('Submit').then(()=>{
frm.save('Submit').then(() => {
frm.page.clear_primary_action();
frm.refresh();
frm.events.refresh(frm);
@ -61,48 +68,48 @@ frappe.ui.form.on('Payroll Entry', {
doc: frm.doc,
method: 'fill_employee_details',
}).then(r => {
if (r.docs && r.docs[0].employees){
if (r.docs && r.docs[0].employees) {
frm.employees = r.docs[0].employees;
frm.dirty();
frm.save();
frm.refresh();
if(r.docs[0].validate_attendance){
if (r.docs[0].validate_attendance) {
render_employee_attendance(frm, r.message);
}
}
})
});
},
create_salary_slips: function(frm) {
create_salary_slips: function (frm) {
frm.call({
doc: frm.doc,
method: "create_salary_slips",
callback: function(r) {
callback: function () {
frm.refresh();
frm.toolbar.refresh();
}
})
});
},
add_context_buttons: function(frm) {
if(frm.doc.salary_slips_submitted || (frm.doc.__onload && frm.doc.__onload.submitted_ss)) {
add_context_buttons: function (frm) {
if (frm.doc.salary_slips_submitted || (frm.doc.__onload && frm.doc.__onload.submitted_ss)) {
frm.events.add_bank_entry_button(frm);
} else if(frm.doc.salary_slips_created) {
frm.add_custom_button(__("Submit Salary Slip"), function() {
} else if (frm.doc.salary_slips_created) {
frm.add_custom_button(__("Submit Salary Slip"), function () {
submit_salary_slip(frm);
}).addClass("btn-primary");
}
},
add_bank_entry_button: function(frm) {
add_bank_entry_button: function (frm) {
frappe.call({
method: 'erpnext.payroll.doctype.payroll_entry.payroll_entry.payroll_entry_has_bank_entries',
args: {
'name': frm.doc.name
},
callback: function(r) {
callback: function (r) {
if (r.message && !r.message.submitted) {
frm.add_custom_button("Make Bank Entry", function() {
frm.add_custom_button("Make Bank Entry", function () {
make_bank_entry(frm);
}).addClass("btn-primary");
}
@ -141,8 +148,37 @@ frappe.ui.form.on('Payroll Entry', {
},
payroll_frequency: function (frm) {
frm.trigger("set_start_end_dates");
frm.events.clear_employee_table(frm);
frm.trigger("set_start_end_dates").then( ()=> {
frm.events.clear_employee_table(frm);
frm.events.get_employee_with_salary_slip_and_set_query(frm);
});
},
employee_filters: function (frm, emp_list) {
frm.set_query('employee', 'employees', () => {
return {
filters: {
name: ["not in", emp_list]
}
};
});
},
get_employee_with_salary_slip_and_set_query: function (frm) {
frappe.db.get_list('Salary Slip', {
filters: {
start_date: frm.doc.start_date,
end_date: frm.doc.end_date,
docstatus: 1,
},
fields: ['employee']
}).then((emp) => {
var emp_list = [];
emp.forEach((employee_data) => {
emp_list.push(Object.values(employee_data)[0]);
});
frm.events.employee_filters(frm, emp_list);
});
},
company: function (frm) {
@ -164,17 +200,17 @@ frappe.ui.form.on('Payroll Entry', {
from_currency: frm.doc.currency,
to_currency: company_currency,
},
callback: function(r) {
callback: function (r) {
frm.set_value("exchange_rate", flt(r.message));
frm.set_df_property('exchange_rate', 'hidden', 0);
frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency
+ " = [?] " + company_currency);
frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency +
" = [?] " + company_currency);
}
});
} else {
frm.set_value("exchange_rate", 1.0);
frm.set_df_property('exchange_rate', 'hidden', 1);
frm.set_df_property("exchange_rate", "description", "" );
frm.set_df_property("exchange_rate", "description", "");
}
}
},
@ -192,9 +228,9 @@ frappe.ui.form.on('Payroll Entry', {
},
start_date: function (frm) {
if(!in_progress && frm.doc.start_date){
if (!in_progress && frm.doc.start_date) {
frm.trigger("set_end_date");
}else{
} else {
// reset flag
in_progress = false;
}
@ -228,7 +264,7 @@ frappe.ui.form.on('Payroll Entry', {
}
},
set_end_date: function(frm){
set_end_date: function (frm) {
frappe.call({
method: 'erpnext.payroll.doctype.payroll_entry.payroll_entry.get_end_date',
args: {
@ -243,19 +279,19 @@ frappe.ui.form.on('Payroll Entry', {
});
},
validate_attendance: function(frm){
if(frm.doc.validate_attendance && frm.doc.employees){
validate_attendance: function (frm) {
if (frm.doc.validate_attendance && frm.doc.employees) {
frappe.call({
method: 'validate_employee_attendance',
args: {},
callback: function(r) {
callback: function (r) {
render_employee_attendance(frm, r.message);
},
doc: frm.doc,
freeze: true,
freeze_message: __('Validating Employee Attendance...')
});
}else{
} else {
frm.fields_dict.attendance_detail_html.html("");
}
},
@ -270,18 +306,20 @@ frappe.ui.form.on('Payroll Entry', {
const submit_salary_slip = function (frm) {
frappe.confirm(__('This will submit Salary Slips and create accrual Journal Entry. Do you want to proceed?'),
function() {
function () {
frappe.call({
method: 'submit_salary_slips',
args: {},
callback: function() {frm.events.refresh(frm);},
callback: function () {
frm.events.refresh(frm);
},
doc: frm.doc,
freeze: true,
freeze_message: __('Submitting Salary Slips and creating Journal Entry...')
});
},
function() {
if(frappe.dom.freeze_count) {
function () {
if (frappe.dom.freeze_count) {
frappe.dom.unfreeze();
frm.events.refresh(frm);
}
@ -295,9 +333,11 @@ let make_bank_entry = function (frm) {
return frappe.call({
doc: cur_frm.doc,
method: "make_payment_entry",
callback: function() {
callback: function () {
frappe.set_route(
'List', 'Journal Entry', {"Journal Entry Account.reference_name": frm.doc.name}
'List', 'Journal Entry', {
"Journal Entry Account.reference_name": frm.doc.name
}
);
},
freeze: true,
@ -309,11 +349,19 @@ let make_bank_entry = function (frm) {
}
};
let render_employee_attendance = function(frm, data) {
let render_employee_attendance = function (frm, data) {
frm.fields_dict.attendance_detail_html.html(
frappe.render_template('employees_to_mark_attendance', {
data: data
})
);
}
};
frappe.ui.form.on('Payroll Employee Detail', {
employee: function(frm) {
frm.events.clear_employee_table(frm);
if (!frm.doc.payroll_frequency) {
frappe.throw(__("Please set a Payroll Frequency"));
}
}
});

View File

@ -129,8 +129,7 @@
"fieldname": "employees",
"fieldtype": "Table",
"label": "Employee Details",
"options": "Payroll Employee Detail",
"read_only": 1
"options": "Payroll Employee Detail"
},
{
"fieldname": "section_break_13",
@ -290,7 +289,7 @@
"icon": "fa fa-cog",
"is_submittable": 1,
"links": [],
"modified": "2020-10-23 13:00:33.753228",
"modified": "2020-12-17 15:13:17.766210",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Payroll Entry",

View File

@ -6,7 +6,7 @@ from __future__ import unicode_literals
import frappe, erpnext
from frappe.model.document import Document
from dateutil.relativedelta import relativedelta
from frappe.utils import cint, flt, nowdate, add_days, getdate, fmt_money, add_to_date, DATE_FORMAT, date_diff
from frappe.utils import cint, flt, add_days, getdate, add_to_date, DATE_FORMAT, date_diff, comma_and
from frappe import _
from erpnext.accounts.utils import get_fiscal_year
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
@ -19,16 +19,26 @@ class PayrollEntry(Document):
# check if salary slips were manually submitted
entries = frappe.db.count("Salary Slip", {'payroll_entry': self.name, 'docstatus': 1}, ['name'])
if cint(entries) == len(self.employees):
self.set_onload("submitted_ss", True)
self.set_onload("submitted_ss", True)
def on_submit(self):
self.create_salary_slips()
def before_submit(self):
self.validate_employee_details()
if self.validate_attendance:
if self.validate_employee_attendance():
frappe.throw(_("Cannot Submit, Employees left to mark attendance"))
def validate_employee_details(self):
emp_with_sal_slip = []
for employee_details in self.employees:
if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": self.start_date, "end_date": self.end_date, "docstatus": 1}):
emp_with_sal_slip.append(employee_details.employee)
if len(emp_with_sal_slip):
frappe.throw(_("Salary Slip already exists for {0} ").format(comma_and(emp_with_sal_slip)))
def on_cancel(self):
frappe.delete_doc("Salary Slip", frappe.db.sql_list("""select name from `tabSalary Slip`
where payroll_entry=%s """, (self.name)))
@ -71,8 +81,17 @@ class PayrollEntry(Document):
and t2.docstatus = 1
%s order by t2.from_date desc
""" % cond, {"sal_struct": tuple(sal_struct), "from_date": self.end_date, "payroll_payable_account": self.payroll_payable_account}, as_dict=True)
emp_list = self.remove_payrolled_employees(emp_list)
return emp_list
def remove_payrolled_employees(self, emp_list):
for employee_details in emp_list:
if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": self.start_date, "end_date": self.end_date, "docstatus": 1}):
emp_list.remove(employee_details)
return emp_list
def fill_employee_details(self):
self.set('employees', [])
employees = self.get_emp_list()
@ -542,7 +561,7 @@ def create_salary_slips_for_employees(employees, args, publish_progress=True):
title = _("Creating Salary Slips..."))
else:
salary_slip_name = frappe.db.sql(
'''SELECT
'''SELECT
name
FROM `tabSalary Slip`
WHERE company=%s

View File

@ -4,9 +4,13 @@
frappe.ui.form.on('Retention Bonus', {
setup: function(frm) {
frm.set_query("employee", function() {
if (!frm.doc.company) {
frappe.msgprint(__("Please Select Company First"));
}
return {
filters: {
"status": "Active"
"status": "Active",
"company": frm.doc.company
}
};
});

View File

@ -125,15 +125,15 @@ frappe.ui.form.on("Salary Slip", {
change_form_labels: function(frm, company_currency) {
frm.set_currency_labels(["base_hour_rate", "base_gross_pay", "base_total_deduction",
"base_net_pay", "base_rounded_total", "base_total_in_words"],
"base_net_pay", "base_rounded_total", "base_total_in_words", "base_year_to_date", "base_month_to_date"],
company_currency);
frm.set_currency_labels(["hour_rate", "gross_pay", "total_deduction", "net_pay", "rounded_total", "total_in_words"],
frm.set_currency_labels(["hour_rate", "gross_pay", "total_deduction", "net_pay", "rounded_total", "total_in_words", "year_to_date", "month_to_date"],
frm.doc.currency);
// toggle fields
frm.toggle_display(["exchange_rate", "base_hour_rate", "base_gross_pay", "base_total_deduction",
"base_net_pay", "base_rounded_total", "base_total_in_words"],
"base_net_pay", "base_rounded_total", "base_total_in_words", "base_year_to_date", "base_month_to_date"],
frm.doc.currency != company_currency);
},
@ -214,14 +214,16 @@ frappe.ui.form.on('Salary Slip Timesheet', {
});
var calculate_totals = function(frm) {
if (frm.doc.earnings || frm.doc.deductions) {
frappe.call({
method: "set_totals",
doc: frm.doc,
callback: function() {
frm.refresh_fields();
}
});
if (frm.doc.docstatus === 0) {
if (frm.doc.earnings || frm.doc.deductions) {
frappe.call({
method: "set_totals",
doc: frm.doc,
callback: function() {
frm.refresh_fields();
}
});
}
}
};

View File

@ -69,9 +69,13 @@
"net_pay_info",
"net_pay",
"base_net_pay",
"year_to_date",
"base_year_to_date",
"column_break_53",
"rounded_total",
"base_rounded_total",
"month_to_date",
"base_month_to_date",
"section_break_55",
"total_in_words",
"column_break_69",
@ -578,13 +582,41 @@
{
"fieldname": "column_break_69",
"fieldtype": "Column Break"
},
{
"fieldname": "year_to_date",
"fieldtype": "Currency",
"label": "Year To Date",
"options": "currency",
"read_only": 1
},
{
"fieldname": "month_to_date",
"fieldtype": "Currency",
"label": "Month To Date",
"options": "currency",
"read_only": 1
},
{
"fieldname": "base_year_to_date",
"fieldtype": "Currency",
"label": "Year To Date(Company Currency)",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "base_month_to_date",
"fieldtype": "Currency",
"label": "Month To Date(Company Currency)",
"options": "Company:company:default_currency",
"read_only": 1
}
],
"icon": "fa fa-file-text",
"idx": 9,
"is_submittable": 1,
"links": [],
"modified": "2020-10-21 23:02:59.400249",
"modified": "2020-12-21 23:43:44.959840",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Salary Slip",

View File

@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe, erpnext
import datetime, math
from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words, formatdate
from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words, formatdate, get_first_day
from frappe.model.naming import make_autoname
from frappe import msgprint, _
@ -18,6 +18,7 @@ from erpnext.payroll.doctype.payroll_period.payroll_period import get_period_fac
from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_benefit_component_amount
from erpnext.payroll.doctype.employee_benefit_claim.employee_benefit_claim import get_benefit_claim_amount, get_last_payroll_period_benefits
from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts, create_repayment_entry
from erpnext.accounts.utils import get_fiscal_year
class SalarySlip(TransactionBase):
def __init__(self, *args, **kwargs):
@ -49,6 +50,8 @@ class SalarySlip(TransactionBase):
self.get_working_days_details(lwp = self.leave_without_pay)
self.calculate_net_pay()
self.compute_year_to_date()
self.compute_month_to_date()
if frappe.db.get_single_value("Payroll Settings", "max_working_hours_against_timesheet"):
max_working_hours = frappe.db.get_single_value("Payroll Settings", "max_working_hours_against_timesheet")
@ -1125,6 +1128,46 @@ class SalarySlip(TransactionBase):
self.gross_pay += self.earnings[i].amount
self.net_pay = flt(self.gross_pay) - flt(self.total_deduction)
def compute_year_to_date(self):
year_to_date = 0
payroll_period = get_payroll_period(self.start_date, self.end_date, self.company)
if payroll_period:
period_start_date = payroll_period.start_date
period_end_date = payroll_period.end_date
else:
# get dates based on fiscal year if no payroll period exists
fiscal_year = get_fiscal_year(date=self.start_date, company=self.company, as_dict=1)
period_start_date = fiscal_year.year_start_date
period_end_date = fiscal_year.year_end_date
salary_slip_sum = frappe.get_list('Salary Slip',
fields = ['sum(net_pay) as sum'],
filters = {'employee_name' : self.employee_name,
'start_date' : ['>=', period_start_date],
'end_date' : ['<', period_end_date]})
year_to_date = flt(salary_slip_sum[0].sum) if salary_slip_sum else 0.0
year_to_date += self.net_pay
self.year_to_date = year_to_date
def compute_month_to_date(self):
month_to_date = 0
first_day_of_the_month = get_first_day(self.start_date)
salary_slip_sum = frappe.get_list('Salary Slip',
fields = ['sum(net_pay) as sum'],
filters = {'employee_name' : self.employee_name,
'start_date' : ['>=', first_day_of_the_month],
'end_date' : ['<', self.start_date]
})
month_to_date = flt(salary_slip_sum[0].sum) if salary_slip_sum else 0.0
month_to_date += self.net_pay
self.month_to_date = month_to_date
def unlink_ref_doc_from_salary_slip(ref_no):
linked_ss = frappe.db.sql_list("""select name from `tabSalary Slip`
where journal_entry=%s and docstatus < 2""", (ref_no))
@ -1135,4 +1178,4 @@ def unlink_ref_doc_from_salary_slip(ref_no):
def generate_password_for_pdf(policy_template, employee):
employee = frappe.get_doc("Employee", employee)
return policy_template.format(**employee.as_dict())
return policy_template.format(**employee.as_dict())

View File

@ -9,7 +9,7 @@ import calendar
import random
from erpnext.accounts.utils import get_fiscal_year
from frappe.utils.make_random import get_random
from frappe.utils import getdate, nowdate, add_days, add_months, flt, get_first_day, get_last_day
from frappe.utils import getdate, nowdate, add_days, add_months, flt, get_first_day, get_last_day, cstr
from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip
from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_month_details
from erpnext.hr.doctype.employee.test_employee import make_employee
@ -240,7 +240,11 @@ class TestSalarySlip(unittest.TestCase):
interest_income_account='Interest Income Account - _TC',
penalty_income_account='Penalty Income Account - _TC')
make_salary_structure("Test Loan Repayment Salary Structure", "Monthly", employee=applicant, currency='INR')
payroll_period = create_payroll_period(name="_Test Payroll Period 1", company="_Test Company")
make_salary_structure("Test Loan Repayment Salary Structure", "Monthly", employee=applicant, currency='INR',
payroll_period=payroll_period)
frappe.db.sql("""delete from `tabLoan""")
loan = create_loan(applicant, "Car Loan", 11000, "Repay Over Number of Periods", 20, posting_date=add_months(nowdate(), -1))
loan.repay_from_salary = 1
@ -290,6 +294,33 @@ class TestSalarySlip(unittest.TestCase):
self.assertEqual(salary_slip.gross_pay, 78000)
self.assertEqual(salary_slip.base_gross_pay, 78000*70)
def test_year_to_date_computation(self):
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
applicant = make_employee("test_ytd@salary.com", company="_Test Company")
payroll_period = create_payroll_period(name="_Test Payroll Period 1", company="_Test Company")
create_tax_slab(payroll_period, allow_tax_exemption=True, currency="INR", effective_date=getdate("2019-04-01"),
company="_Test Company")
salary_structure = make_salary_structure("Monthly Salary Structure Test for Salary Slip YTD",
"Monthly", employee=applicant, company="_Test Company", currency="INR", payroll_period=payroll_period)
# clear salary slip for this employee
frappe.db.sql("DELETE FROM `tabSalary Slip` where employee_name = 'test_ytd@salary.com'")
create_salary_slips_for_payroll_period(applicant, salary_structure.name,
payroll_period, deduct_random=False)
salary_slips = frappe.get_all('Salary Slip', fields=['year_to_date', 'net_pay'], filters={'employee_name':
'test_ytd@salary.com'}, order_by = 'posting_date')
year_to_date = 0
for slip in salary_slips:
year_to_date += slip.net_pay
self.assertEqual(slip.year_to_date, year_to_date)
def test_tax_for_payroll_period(self):
data = {}
# test the impact of tax exemption declaration, tax exemption proof submission
@ -410,10 +441,7 @@ def make_employee_salary_slip(user, payroll_frequency, salary_structure=None):
salary_structure = payroll_frequency + " Salary Structure Test for Salary Slip"
employee = frappe.db.get_value("Employee", {"user_id": user})
if not frappe.db.exists('Salary Structure', salary_structure):
salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee)
else:
salary_structure_doc = frappe.get_doc('Salary Structure', salary_structure)
salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee=employee)
salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})})
if not salary_slip_name:
@ -631,8 +659,13 @@ def create_benefit_claim(employee, payroll_period, amount, component):
}).submit()
return claim_date
def create_tax_slab(payroll_period, effective_date = None, allow_tax_exemption = False, dont_submit = False, currency=erpnext.get_default_currency()):
frappe.db.sql("""delete from `tabIncome Tax Slab`""")
def create_tax_slab(payroll_period, effective_date = None, allow_tax_exemption = False, dont_submit = False, currency=None,
company=None):
if not currency:
currency = erpnext.get_default_currency()
if company:
currency = erpnext.get_company_currency(company)
slabs = [
{
@ -652,26 +685,33 @@ def create_tax_slab(payroll_period, effective_date = None, allow_tax_exemption =
}
]
income_tax_slab = frappe.new_doc("Income Tax Slab")
income_tax_slab.name = "Tax Slab: " + payroll_period.name
income_tax_slab.effective_from = effective_date or add_days(payroll_period.start_date, -2)
income_tax_slab.currency = currency
income_tax_slab_name = frappe.db.get_value("Income Tax Slab", {"currency": currency})
if not income_tax_slab_name:
income_tax_slab = frappe.new_doc("Income Tax Slab")
income_tax_slab.name = "Tax Slab: " + payroll_period.name + " " + cstr(currency)
income_tax_slab.effective_from = effective_date or add_days(payroll_period.start_date, -2)
income_tax_slab.company = company or ''
income_tax_slab.currency = currency
if allow_tax_exemption:
income_tax_slab.allow_tax_exemption = 1
income_tax_slab.standard_tax_exemption_amount = 50000
if allow_tax_exemption:
income_tax_slab.allow_tax_exemption = 1
income_tax_slab.standard_tax_exemption_amount = 50000
for item in slabs:
income_tax_slab.append("slabs", item)
for item in slabs:
income_tax_slab.append("slabs", item)
income_tax_slab.append("other_taxes_and_charges", {
"description": "cess",
"percent": 4
})
income_tax_slab.append("other_taxes_and_charges", {
"description": "cess",
"percent": 4
})
income_tax_slab.save()
if not dont_submit:
income_tax_slab.submit()
income_tax_slab.save()
if not dont_submit:
income_tax_slab.submit()
return income_tax_slab.name
else:
return income_tax_slab_name
def create_salary_slips_for_payroll_period(employee, salary_structure, payroll_period, deduct_random=True):
deducted_dates = []

View File

@ -55,17 +55,17 @@ frappe.ui.form.on('Salary Structure', {
},
set_earning_deduction_component: function(frm) {
if(!frm.doc.currency && !frm.doc.company) return;
if(!frm.doc.company) return;
frm.set_query("salary_component", "earnings", function() {
return {
query : "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components",
filters: {type: "earning", currency: frm.doc.currency, company: frm.doc.company}
filters: {type: "earning", company: frm.doc.company}
};
});
frm.set_query("salary_component", "deductions", function() {
return {
query : "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components",
filters: {type: "deduction", currency: frm.doc.currency, company: frm.doc.company}
filters: {type: "deduction", company: frm.doc.company}
};
});
},
@ -74,7 +74,6 @@ frappe.ui.form.on('Salary Structure', {
currency: function(frm) {
calculate_totals(frm.doc);
frm.trigger("set_dynamic_labels")
frm.trigger('set_earning_deduction_component');
frm.refresh()
},

View File

@ -210,7 +210,7 @@ def get_employees(salary_structure):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_earning_deduction_components(doctype, txt, searchfield, start, page_len, filters):
if len(filters) < 3:
if len(filters) < 2:
return {}
return frappe.db.sql("""

View File

@ -114,7 +114,7 @@ class TestSalaryStructure(unittest.TestCase):
self.assertEqual(sal_struct.currency, 'USD')
def make_salary_structure(salary_structure, payroll_frequency, employee=None, dont_submit=False, other_details=None,
test_tax=False, company=None, currency=erpnext.get_default_currency()):
test_tax=False, company=None, currency=erpnext.get_default_currency(), payroll_period=None):
if test_tax:
frappe.db.sql("""delete from `tabSalary Structure` where name=%s""",(salary_structure))
@ -141,16 +141,24 @@ def make_salary_structure(salary_structure, payroll_frequency, employee=None, do
if employee and not frappe.db.get_value("Salary Structure Assignment",
{'employee':employee, 'docstatus': 1}) and salary_structure_doc.docstatus==1:
create_salary_structure_assignment(employee, salary_structure, company=company, currency=currency)
create_salary_structure_assignment(employee, salary_structure, company=company, currency=currency,
payroll_period=payroll_period)
return salary_structure_doc
def create_salary_structure_assignment(employee, salary_structure, from_date=None, company=None, currency=erpnext.get_default_currency()):
def create_salary_structure_assignment(employee, salary_structure, from_date=None, company=None, currency=erpnext.get_default_currency(),
payroll_period=None):
if frappe.db.exists("Salary Structure Assignment", {"employee": employee}):
frappe.db.sql("""delete from `tabSalary Structure Assignment` where employee=%s""",(employee))
payroll_period = create_payroll_period()
create_tax_slab(payroll_period, allow_tax_exemption=True, currency=currency)
if not payroll_period:
payroll_period = create_payroll_period()
income_tax_slab = frappe.db.get_value("Income Tax Slab", {"currency": currency})
if not income_tax_slab:
income_tax_slab = create_tax_slab(payroll_period, allow_tax_exemption=True, currency=currency)
salary_structure_assignment = frappe.new_doc("Salary Structure Assignment")
salary_structure_assignment.employee = employee
@ -162,7 +170,7 @@ def create_salary_structure_assignment(employee, salary_structure, from_date=Non
salary_structure_assignment.payroll_payable_account = get_payable_account(company)
salary_structure_assignment.company = company or erpnext.get_default_company()
salary_structure_assignment.save(ignore_permissions=True)
salary_structure_assignment.income_tax_slab = "Tax Slab: _Test Payroll Period"
salary_structure_assignment.income_tax_slab = income_tax_slab
salary_structure_assignment.submit()
return salary_structure_assignment

View File

@ -43,7 +43,7 @@ class SalaryStructureAssignment(Document):
def set_payroll_payable_account(self):
if not self.payroll_payable_account:
payroll_payable_account = frappe.db.get_value('Company', self.company, 'default_payable_account')
payroll_payable_account = frappe.db.get_value('Company', self.company, 'default_payroll_payable_account')
if not payroll_payable_account:
payroll_payable_account = frappe.db.get_value(
"Account", {

View File

@ -609,6 +609,15 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
this.calculate_outstanding_amount(update_paid_amount);
},
is_internal_invoice: function() {
if (['Sales Invoice', 'Purchase Invoice'].includes(this.frm.doc.doctype)) {
if (this.frm.doc.company === this.frm.doc.represents_company) {
return true;
}
}
return false;
},
calculate_outstanding_amount: function(update_paid_amount) {
// NOTE:
// paid_amount and write_off_amount is only for POS/Loyalty Point Redemption Invoice
@ -617,7 +626,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
this.calculate_paid_amount();
}
if(this.frm.doc.is_return || this.frm.doc.docstatus > 0) return;
if (this.frm.doc.is_return || (this.frm.doc.docstatus > 0) || this.is_internal_invoice()) return;
frappe.model.round_floats_in(this.frm.doc, ["grand_total", "total_advance", "write_off_amount"]);

View File

@ -408,7 +408,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
show_description(row_to_modify.idx, row_to_modify.item_code);
this.frm.from_barcode = true;
this.frm.from_barcode = this.frm.from_barcode ? this.frm.from_barcode + 1 : 1;
frappe.model.set_value(row_to_modify.doctype, row_to_modify.name, {
item_code: data.item_code,
qty: (row_to_modify.qty || 0) + 1
@ -492,7 +492,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
d.item_code = "";
}
this.frm.from_barcode = true;
this.frm.from_barcode = this.frm.from_barcode ? this.frm.from_barcode + 1 : 1;
this.item_code(doc, cdt, cdn);
},
@ -509,11 +509,12 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
show_batch_dialog = 1;
}
// clear barcode if setting item (else barcode will take priority)
if(!this.frm.from_barcode) {
if (this.frm.from_barcode == 0) {
item.barcode = null;
}
this.frm.from_barcode = this.frm.from_barcode - 1 >= 0 ? this.frm.from_barcode - 1 : 0;
this.frm.from_barcode = false;
if(item.item_code || item.barcode || item.serial_no) {
if(!this.validate_company_and_party()) {
this.frm.fields_dict["items"].grid.grid_rows[item.idx - 1].remove();

View File

@ -20,4 +20,4 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlData.extend( {
});
}
}
});
});

Some files were not shown because too many files have changed in this diff Show More