Fixed merge conflict
12
.travis.yml
@ -51,11 +51,17 @@ before_script:
|
|||||||
- bench start &
|
- bench start &
|
||||||
- sleep 10
|
- sleep 10
|
||||||
|
|
||||||
script:
|
jobs:
|
||||||
|
include:
|
||||||
|
- stage: test
|
||||||
|
script:
|
||||||
- set -e
|
- set -e
|
||||||
- bench run-tests
|
- bench run-tests
|
||||||
- sleep 5
|
env: Server Side Test
|
||||||
- bench reinstall --yes
|
- # stage
|
||||||
|
script:
|
||||||
- bench --verbose run-setup-wizard-ui-test
|
- bench --verbose run-setup-wizard-ui-test
|
||||||
- bench execute erpnext.setup.utils.enable_all_roles_and_domains
|
- bench execute erpnext.setup.utils.enable_all_roles_and_domains
|
||||||
- bench run-ui-tests --app erpnext
|
- bench run-ui-tests --app erpnext
|
||||||
|
env: Client Side Test
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ class GLEntry(Document):
|
|||||||
validate_balance_type(self.account, adv_adj)
|
validate_balance_type(self.account, adv_adj)
|
||||||
|
|
||||||
# Update outstanding amt on against voucher
|
# Update outstanding amt on against voucher
|
||||||
if self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice'] \
|
if self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees'] \
|
||||||
and self.against_voucher and update_outstanding == 'Yes' and not from_repost:
|
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,
|
update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type,
|
||||||
self.against_voucher)
|
self.against_voucher)
|
||||||
@ -196,7 +196,7 @@ def update_outstanding_amt(account, party_type, party, against_voucher_type, aga
|
|||||||
frappe.throw(_("Outstanding for {0} cannot be less than zero ({1})").format(against_voucher, fmt_money(bal)))
|
frappe.throw(_("Outstanding for {0} cannot be less than zero ({1})").format(against_voucher, fmt_money(bal)))
|
||||||
|
|
||||||
# Update outstanding amt on against voucher
|
# Update outstanding amt on against voucher
|
||||||
if against_voucher_type in ["Sales Invoice", "Purchase Invoice"]:
|
if against_voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"]:
|
||||||
ref_doc = frappe.get_doc(against_voucher_type, against_voucher)
|
ref_doc = frappe.get_doc(against_voucher_type, against_voucher)
|
||||||
ref_doc.db_set('outstanding_amount', bal)
|
ref_doc.db_set('outstanding_amount', bal)
|
||||||
ref_doc.set_status(update=True)
|
ref_doc.set_status(update=True)
|
||||||
|
@ -12,7 +12,8 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
|
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
frm.set_query("paid_from", function() {
|
frm.set_query("paid_from", function() {
|
||||||
var party_account_type = frm.doc.party_type=="Customer" ? "Receivable" : "Payable";
|
var party_account_type = in_list(["Customer", "Student"], frm.doc.party_type) ?
|
||||||
|
"Receivable" : "Payable";
|
||||||
var account_types = in_list(["Pay", "Internal Transfer"], frm.doc.payment_type) ?
|
var account_types = in_list(["Pay", "Internal Transfer"], frm.doc.payment_type) ?
|
||||||
["Bank", "Cash"] : party_account_type;
|
["Bank", "Cash"] : party_account_type;
|
||||||
|
|
||||||
@ -28,13 +29,14 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
frm.set_query("party_type", function() {
|
frm.set_query("party_type", function() {
|
||||||
return{
|
return{
|
||||||
"filters": {
|
"filters": {
|
||||||
"name": ["in",["Customer","Supplier", "Employee"]],
|
"name": ["in",["Customer","Supplier", "Employee", "Student"]],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("paid_to", function() {
|
frm.set_query("paid_to", function() {
|
||||||
var party_account_type = frm.doc.party_type=="Customer" ? "Receivable" : "Payable";
|
var party_account_type = in_list(["Customer", "Student"], frm.doc.party_type) ?
|
||||||
|
"Receivable" : "Payable";
|
||||||
var account_types = in_list(["Receive", "Internal Transfer"], frm.doc.payment_type) ?
|
var account_types = in_list(["Receive", "Internal Transfer"], frm.doc.payment_type) ?
|
||||||
["Bank", "Cash"] : party_account_type;
|
["Bank", "Cash"] : party_account_type;
|
||||||
|
|
||||||
@ -72,6 +74,8 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
var doctypes = ["Purchase Order", "Purchase Invoice", "Journal Entry"];
|
var doctypes = ["Purchase Order", "Purchase Invoice", "Journal Entry"];
|
||||||
} else if (frm.doc.party_type=="Employee") {
|
} else if (frm.doc.party_type=="Employee") {
|
||||||
var doctypes = ["Expense Claim", "Journal Entry"];
|
var doctypes = ["Expense Claim", "Journal Entry"];
|
||||||
|
} else if (frm.doc.party_type=="Student") {
|
||||||
|
var doctypes = ["Fees"];
|
||||||
} else {
|
} else {
|
||||||
var doctypes = ["Journal Entry"];
|
var doctypes = ["Journal Entry"];
|
||||||
}
|
}
|
||||||
@ -85,7 +89,7 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
child = locals[cdt][cdn];
|
child = locals[cdt][cdn];
|
||||||
filters = {"docstatus": 1, "company": doc.company};
|
filters = {"docstatus": 1, "company": doc.company};
|
||||||
party_type_doctypes = ['Sales Invoice', 'Sales Order', 'Purchase Invoice',
|
party_type_doctypes = ['Sales Invoice', 'Sales Order', 'Purchase Invoice',
|
||||||
'Purchase Order', 'Expense Claim'];
|
'Purchase Order', 'Expense Claim', 'Fees'];
|
||||||
|
|
||||||
if (in_list(party_type_doctypes, child.reference_doctype)) {
|
if (in_list(party_type_doctypes, child.reference_doctype)) {
|
||||||
filters[doc.party_type.toLowerCase()] = doc.party;
|
filters[doc.party_type.toLowerCase()] = doc.party;
|
||||||
@ -207,20 +211,14 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
frm.set_value(field, null);
|
frm.set_value(field, null);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if(!frm.doc.party)
|
if(frm.doc.party) {
|
||||||
{
|
|
||||||
if (frm.doc.payment_type=="Receive"){
|
|
||||||
frm.set_value("party_type", "Customer");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
frm.events.party(frm);
|
frm.events.party(frm);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(frm.doc.mode_of_payment)
|
if(frm.doc.mode_of_payment) {
|
||||||
frm.events.mode_of_payment(frm);
|
frm.events.mode_of_payment(frm);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
party_type: function(frm) {
|
party_type: function(frm) {
|
||||||
@ -254,6 +252,7 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
date: frm.doc.posting_date
|
date: frm.doc.posting_date
|
||||||
},
|
},
|
||||||
callback: function(r, rt) {
|
callback: function(r, rt) {
|
||||||
|
console.log(r, rt);
|
||||||
if(r.message) {
|
if(r.message) {
|
||||||
if(frm.doc.payment_type == "Receive") {
|
if(frm.doc.payment_type == "Receive") {
|
||||||
frm.set_value("paid_from", r.message.party_account);
|
frm.set_value("paid_from", r.message.party_account);
|
||||||
@ -502,6 +501,8 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
c.due_date = d.due_date
|
c.due_date = d.due_date
|
||||||
c.total_amount = d.invoice_amount;
|
c.total_amount = d.invoice_amount;
|
||||||
c.outstanding_amount = d.outstanding_amount;
|
c.outstanding_amount = d.outstanding_amount;
|
||||||
|
c.bill_no = d.bill_no;
|
||||||
|
|
||||||
if(!in_list(["Sales Order", "Purchase Order", "Expense Claim"], d.voucher_type)) {
|
if(!in_list(["Sales Order", "Purchase Order", "Expense Claim"], d.voucher_type)) {
|
||||||
if(flt(d.outstanding_amount) > 0)
|
if(flt(d.outstanding_amount) > 0)
|
||||||
total_positive_outstanding += flt(d.outstanding_amount);
|
total_positive_outstanding += flt(d.outstanding_amount);
|
||||||
|
@ -100,8 +100,8 @@ class PaymentEntry(AccountsController):
|
|||||||
if not self.party:
|
if not self.party:
|
||||||
frappe.throw(_("Party is mandatory"))
|
frappe.throw(_("Party is mandatory"))
|
||||||
|
|
||||||
self.party_name = frappe.db.get_value(self.party_type, self.party,
|
_party_name = "title" if self.party_type == "Student" else self.party_type.lower() + "_name"
|
||||||
self.party_type.lower() + "_name")
|
self.party_name = frappe.db.get_value(self.party_type, self.party, _party_name)
|
||||||
|
|
||||||
if self.party:
|
if self.party:
|
||||||
if not self.party_balance:
|
if not self.party_balance:
|
||||||
@ -149,7 +149,7 @@ class PaymentEntry(AccountsController):
|
|||||||
frappe.throw(_("Invalid {0}: {1}").format(self.party_type, self.party))
|
frappe.throw(_("Invalid {0}: {1}").format(self.party_type, self.party))
|
||||||
|
|
||||||
if self.party_account:
|
if self.party_account:
|
||||||
party_account_type = "Receivable" if self.party_type=="Customer" else "Payable"
|
party_account_type = "Receivable" if self.party_type in ("Customer", "Student") else "Payable"
|
||||||
self.validate_account_type(self.party_account, [party_account_type])
|
self.validate_account_type(self.party_account, [party_account_type])
|
||||||
|
|
||||||
def validate_bank_accounts(self):
|
def validate_bank_accounts(self):
|
||||||
@ -182,7 +182,9 @@ class PaymentEntry(AccountsController):
|
|||||||
frappe.throw(_("{0} is mandatory").format(self.meta.get_label(field)))
|
frappe.throw(_("{0} is mandatory").format(self.meta.get_label(field)))
|
||||||
|
|
||||||
def validate_reference_documents(self):
|
def validate_reference_documents(self):
|
||||||
if self.party_type == "Customer":
|
if self.party_type == "Student":
|
||||||
|
valid_reference_doctypes = ("Fees")
|
||||||
|
elif self.party_type == "Customer":
|
||||||
valid_reference_doctypes = ("Sales Order", "Sales Invoice", "Journal Entry")
|
valid_reference_doctypes = ("Sales Order", "Sales Invoice", "Journal Entry")
|
||||||
elif self.party_type == "Supplier":
|
elif self.party_type == "Supplier":
|
||||||
valid_reference_doctypes = ("Purchase Order", "Purchase Invoice", "Journal Entry")
|
valid_reference_doctypes = ("Purchase Order", "Purchase Invoice", "Journal Entry")
|
||||||
@ -209,9 +211,11 @@ class PaymentEntry(AccountsController):
|
|||||||
else:
|
else:
|
||||||
self.validate_journal_entry()
|
self.validate_journal_entry()
|
||||||
|
|
||||||
if d.reference_doctype in ("Sales Invoice", "Purchase Invoice", "Expense Claim"):
|
if d.reference_doctype in ("Sales Invoice", "Purchase Invoice", "Expense Claim", "Fees"):
|
||||||
if self.party_type=="Customer":
|
if self.party_type == "Customer":
|
||||||
ref_party_account = ref_doc.debit_to
|
ref_party_account = ref_doc.debit_to
|
||||||
|
elif self.party_type == "Student":
|
||||||
|
ref_party_account = ref_doc.receivable_account
|
||||||
elif self.party_type=="Supplier":
|
elif self.party_type=="Supplier":
|
||||||
ref_party_account = ref_doc.credit_to
|
ref_party_account = ref_doc.credit_to
|
||||||
elif self.party_type=="Employee":
|
elif self.party_type=="Employee":
|
||||||
@ -398,7 +402,7 @@ class PaymentEntry(AccountsController):
|
|||||||
"account_currency": self.party_account_currency
|
"account_currency": self.party_account_currency
|
||||||
})
|
})
|
||||||
|
|
||||||
dr_or_cr = "credit" if self.party_type == "Customer" else "debit"
|
dr_or_cr = "credit" if self.party_type in ["Customer", "Student"] else "debit"
|
||||||
|
|
||||||
for d in self.get("references"):
|
for d in self.get("references"):
|
||||||
gle = party_gl_dict.copy()
|
gle = party_gl_dict.copy()
|
||||||
@ -498,10 +502,12 @@ def get_outstanding_reference_documents(args):
|
|||||||
# Get negative outstanding sales /purchase invoices
|
# Get negative outstanding sales /purchase invoices
|
||||||
total_field = "base_grand_total" if party_account_currency == company_currency else "grand_total"
|
total_field = "base_grand_total" if party_account_currency == company_currency else "grand_total"
|
||||||
|
|
||||||
|
negative_outstanding_invoices = []
|
||||||
|
if (args.get("party_type") != "Student"):
|
||||||
negative_outstanding_invoices = get_negative_outstanding_invoices(args.get("party_type"),
|
negative_outstanding_invoices = get_negative_outstanding_invoices(args.get("party_type"),
|
||||||
args.get("party"), args.get("party_account"), total_field)
|
args.get("party"), args.get("party_account"), total_field)
|
||||||
|
|
||||||
# Get positive outstanding sales /purchase invoices
|
# Get positive outstanding sales /purchase invoices/ Fees
|
||||||
outstanding_invoices = get_outstanding_invoices(args.get("party_type"), args.get("party"),
|
outstanding_invoices = get_outstanding_invoices(args.get("party_type"), args.get("party"),
|
||||||
args.get("party_account"))
|
args.get("party_account"))
|
||||||
|
|
||||||
@ -514,10 +520,14 @@ def get_outstanding_reference_documents(args):
|
|||||||
d["exchange_rate"] = get_exchange_rate(
|
d["exchange_rate"] = get_exchange_rate(
|
||||||
party_account_currency, company_currency, d.posting_date
|
party_account_currency, company_currency, d.posting_date
|
||||||
)
|
)
|
||||||
|
if d.voucher_type in ("Purchase Invoice"):
|
||||||
|
d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no")
|
||||||
|
|
||||||
# Get all SO / PO which are not fully billed or aginst which full advance not paid
|
# Get all SO / PO which are not fully billed or aginst which full advance not paid
|
||||||
orders_to_be_billed = get_orders_to_be_billed(args.get("posting_date"),args.get("party_type"), args.get("party"),
|
orders_to_be_billed = []
|
||||||
party_account_currency, company_currency)
|
if (args.get("party_type") != "Student"):
|
||||||
|
orders_to_be_billed = get_orders_to_be_billed(args.get("posting_date"),args.get("party_type"),
|
||||||
|
args.get("party"), party_account_currency, company_currency)
|
||||||
|
|
||||||
return negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed
|
return negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed
|
||||||
|
|
||||||
@ -632,7 +642,11 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
|||||||
total_amount = outstanding_amount = exchange_rate = None
|
total_amount = outstanding_amount = exchange_rate = None
|
||||||
ref_doc = frappe.get_doc(reference_doctype, reference_name)
|
ref_doc = frappe.get_doc(reference_doctype, reference_name)
|
||||||
|
|
||||||
if reference_doctype != "Journal Entry":
|
if reference_doctype == "Fees":
|
||||||
|
total_amount = ref_doc.get("grand_total")
|
||||||
|
exchange_rate = 1
|
||||||
|
outstanding_amount = ref_doc.get("outstanding_amount")
|
||||||
|
elif reference_doctype != "Journal Entry":
|
||||||
if party_account_currency == ref_doc.company_currency:
|
if party_account_currency == ref_doc.company_currency:
|
||||||
if ref_doc.doctype == "Expense Claim":
|
if ref_doc.doctype == "Expense Claim":
|
||||||
total_amount = ref_doc.total_sanctioned_amount
|
total_amount = ref_doc.total_sanctioned_amount
|
||||||
@ -675,19 +689,23 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
|
|||||||
party_type = "Supplier"
|
party_type = "Supplier"
|
||||||
elif dt in ("Expense Claim"):
|
elif dt in ("Expense Claim"):
|
||||||
party_type = "Employee"
|
party_type = "Employee"
|
||||||
|
elif dt in ("Fees"):
|
||||||
|
party_type = "Student"
|
||||||
|
|
||||||
# party account
|
# party account
|
||||||
if dt == "Sales Invoice":
|
if dt == "Sales Invoice":
|
||||||
party_account = doc.debit_to
|
party_account = doc.debit_to
|
||||||
elif dt == "Purchase Invoice":
|
elif dt == "Purchase Invoice":
|
||||||
party_account = doc.credit_to
|
party_account = doc.credit_to
|
||||||
|
elif dt == "Fees":
|
||||||
|
party_account = doc.receivable_account
|
||||||
else:
|
else:
|
||||||
party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
|
party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
|
||||||
|
|
||||||
party_account_currency = doc.get("party_account_currency") or get_account_currency(party_account)
|
party_account_currency = doc.get("party_account_currency") or get_account_currency(party_account)
|
||||||
|
|
||||||
# payment type
|
# payment type
|
||||||
if (dt == "Sales Order" or (dt=="Sales Invoice" and doc.outstanding_amount > 0)) \
|
if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees") and doc.outstanding_amount > 0)) \
|
||||||
or (dt=="Purchase Invoice" and doc.outstanding_amount < 0):
|
or (dt=="Purchase Invoice" and doc.outstanding_amount < 0):
|
||||||
payment_type = "Receive"
|
payment_type = "Receive"
|
||||||
else:
|
else:
|
||||||
@ -703,6 +721,9 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
|
|||||||
elif dt in ("Expense Claim"):
|
elif dt in ("Expense Claim"):
|
||||||
grand_total = doc.total_sanctioned_amount
|
grand_total = doc.total_sanctioned_amount
|
||||||
outstanding_amount = doc.total_sanctioned_amount - doc.total_amount_reimbursed
|
outstanding_amount = doc.total_sanctioned_amount - doc.total_amount_reimbursed
|
||||||
|
elif dt == "Fees":
|
||||||
|
grand_total = doc.grand_total
|
||||||
|
outstanding_amount = doc.outstanding_amount
|
||||||
else:
|
else:
|
||||||
total_field = "base_grand_total" if party_account_currency == doc.company_currency else "grand_total"
|
total_field = "base_grand_total" if party_account_currency == doc.company_currency else "grand_total"
|
||||||
grand_total = flt(doc.get(total_field))
|
grand_total = flt(doc.get(total_field))
|
||||||
|
@ -9,9 +9,9 @@ QUnit.test("test payment entry", function(assert) {
|
|||||||
{customer: 'Test Customer 1'},
|
{customer: 'Test Customer 1'},
|
||||||
{items: [
|
{items: [
|
||||||
[
|
[
|
||||||
|
{'item_code': 'Test Product 1'},
|
||||||
{'qty': 1},
|
{'qty': 1},
|
||||||
{'rate': 101},
|
{'rate': 101},
|
||||||
{'item_code': 'Test Product 1'},
|
|
||||||
]
|
]
|
||||||
]}
|
]}
|
||||||
]);
|
]);
|
||||||
@ -19,11 +19,12 @@ QUnit.test("test payment entry", function(assert) {
|
|||||||
() => cur_frm.save(),
|
() => cur_frm.save(),
|
||||||
() => frappe.tests.click_button('Submit'),
|
() => frappe.tests.click_button('Submit'),
|
||||||
() => frappe.tests.click_button('Yes'),
|
() => frappe.tests.click_button('Yes'),
|
||||||
() => frappe.timeout(0.5),
|
() => frappe.timeout(1),
|
||||||
() => frappe.tests.click_button('Close'),
|
() => frappe.tests.click_button('Close'),
|
||||||
() => frappe.timeout(0.5),
|
() => frappe.timeout(1),
|
||||||
() => frappe.click_button('Make'),
|
() => frappe.click_button('Make'),
|
||||||
() => frappe.click_link('Payment', 1),
|
() => frappe.timeout(1),
|
||||||
|
() => frappe.click_link('Payment'),
|
||||||
() => frappe.timeout(2),
|
() => frappe.timeout(2),
|
||||||
() => {
|
() => {
|
||||||
assert.equal(frappe.get_route()[1], 'Payment Entry',
|
assert.equal(frappe.get_route()[1], 'Payment Entry',
|
||||||
@ -35,16 +36,19 @@ QUnit.test("test payment entry", function(assert) {
|
|||||||
assert.equal(cur_frm.doc.references[0].allocated_amount, 101,
|
assert.equal(cur_frm.doc.references[0].allocated_amount, 101,
|
||||||
'amount allocated against sales invoice');
|
'amount allocated against sales invoice');
|
||||||
},
|
},
|
||||||
|
() => frappe.timeout(1),
|
||||||
() => cur_frm.set_value('paid_amount', 100),
|
() => cur_frm.set_value('paid_amount', 100),
|
||||||
|
() => frappe.timeout(1),
|
||||||
() => {
|
() => {
|
||||||
cur_frm.doc.references[0].allocated_amount = 101;
|
frappe.model.set_value("Payment Entry Reference", cur_frm.doc.references[0].name,
|
||||||
|
"allocated_amount", 101);
|
||||||
},
|
},
|
||||||
|
() => frappe.timeout(1),
|
||||||
() => frappe.click_button('Write Off Difference Amount'),
|
() => frappe.click_button('Write Off Difference Amount'),
|
||||||
|
() => frappe.timeout(1),
|
||||||
() => {
|
() => {
|
||||||
assert.equal(cur_frm.doc.difference_amount, 0,
|
assert.equal(cur_frm.doc.difference_amount, 0, 'difference amount is zero');
|
||||||
'difference amount is zero');
|
assert.equal(cur_frm.doc.deductions[0].amount, 1, 'Write off amount = 1');
|
||||||
assert.equal(cur_frm.doc.deductions[0].amount, 1,
|
|
||||||
'Write off amount = 1');
|
|
||||||
},
|
},
|
||||||
() => done()
|
() => done()
|
||||||
]);
|
]);
|
||||||
|
@ -7,7 +7,7 @@ QUnit.test("test payment entry", function(assert) {
|
|||||||
() => {
|
() => {
|
||||||
return frappe.tests.make('Sales Invoice', [
|
return frappe.tests.make('Sales Invoice', [
|
||||||
{customer: 'Test Customer 1'},
|
{customer: 'Test Customer 1'},
|
||||||
{company: '_Test Company'},
|
{company: 'For Testing'},
|
||||||
{currency: 'INR'},
|
{currency: 'INR'},
|
||||||
{selling_price_list: '_Test Price List'},
|
{selling_price_list: '_Test Price List'},
|
||||||
{items: [
|
{items: [
|
||||||
@ -29,12 +29,12 @@ QUnit.test("test payment entry", function(assert) {
|
|||||||
() => frappe.timeout(1),
|
() => frappe.timeout(1),
|
||||||
() => frappe.click_link('Payment'),
|
() => frappe.click_link('Payment'),
|
||||||
() => frappe.timeout(2),
|
() => frappe.timeout(2),
|
||||||
() => cur_frm.set_value("paid_to", "_Test Cash - _TC"),
|
() => cur_frm.set_value("paid_to", "_Test Cash - FT"),
|
||||||
() => frappe.timeout(0.5),
|
() => frappe.timeout(0.5),
|
||||||
() => {
|
() => {
|
||||||
assert.equal(frappe.get_route()[1], 'Payment Entry', 'made payment entry');
|
assert.equal(frappe.get_route()[1], 'Payment Entry', 'made payment entry');
|
||||||
assert.equal(cur_frm.doc.party, 'Test Customer 1', 'customer set in payment entry');
|
assert.equal(cur_frm.doc.party, 'Test Customer 1', 'customer set in payment entry');
|
||||||
assert.equal(cur_frm.doc.paid_from, 'Debtors - _TC', 'customer account set in payment entry');
|
assert.equal(cur_frm.doc.paid_from, 'Debtors - FT', 'customer account set in payment entry');
|
||||||
assert.equal(cur_frm.doc.paid_amount, 100, 'paid amount set in payment entry');
|
assert.equal(cur_frm.doc.paid_amount, 100, 'paid amount set in payment entry');
|
||||||
assert.equal(cur_frm.doc.references[0].allocated_amount, 100,
|
assert.equal(cur_frm.doc.references[0].allocated_amount, 100,
|
||||||
'amount allocated against sales invoice');
|
'amount allocated against sales invoice');
|
||||||
@ -50,10 +50,10 @@ QUnit.test("test payment entry", function(assert) {
|
|||||||
assert.equal(cur_frm.doc.difference_amount, 5, 'difference amount is 5');
|
assert.equal(cur_frm.doc.difference_amount, 5, 'difference amount is 5');
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
frappe.db.set_value("Company", "_Test Company", "write_off_account", "_Test Write Off - _TC");
|
frappe.db.set_value("Company", "For Testing", "write_off_account", "_Test Write Off - FT");
|
||||||
frappe.timeout(1);
|
frappe.timeout(1);
|
||||||
frappe.db.set_value("Company", "_Test Company",
|
frappe.db.set_value("Company", "For Testing",
|
||||||
"exchange_gain_loss_account", "_Test Exchange Gain/Loss - _TC");
|
"exchange_gain_loss_account", "_Test Exchange Gain/Loss - FT");
|
||||||
},
|
},
|
||||||
() => frappe.timeout(1),
|
() => frappe.timeout(1),
|
||||||
() => frappe.click_button('Write Off Difference Amount'),
|
() => frappe.click_button('Write Off Difference Amount'),
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"allow_copy": 0,
|
||||||
|
"allow_guest_to_view": 0,
|
||||||
"allow_import": 0,
|
"allow_import": 0,
|
||||||
"allow_rename": 0,
|
"allow_rename": 0,
|
||||||
"beta": 0,
|
"beta": 0,
|
||||||
@ -12,6 +13,7 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@ -42,6 +44,7 @@
|
|||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@ -72,6 +75,7 @@
|
|||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@ -101,6 +105,38 @@
|
|||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"depends_on": "",
|
||||||
|
"fieldname": "bill_no",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"label": "Supplier Invoice No",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 1,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 1,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@ -129,6 +165,7 @@
|
|||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@ -158,6 +195,7 @@
|
|||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@ -187,6 +225,7 @@
|
|||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@ -216,10 +255,12 @@
|
|||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"depends_on": "eval:(doc.reference_doctype=='Purchase Invoice')",
|
||||||
"fieldname": "exchange_rate",
|
"fieldname": "exchange_rate",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@ -245,17 +286,17 @@
|
|||||||
"unique": 0
|
"unique": 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"has_web_view": 0,
|
||||||
"hide_heading": 0,
|
"hide_heading": 0,
|
||||||
"hide_toolbar": 0,
|
"hide_toolbar": 0,
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"image_view": 0,
|
"image_view": 0,
|
||||||
"in_create": 0,
|
"in_create": 0,
|
||||||
"in_dialog": 0,
|
|
||||||
"is_submittable": 0,
|
"is_submittable": 0,
|
||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2017-02-17 16:47:17.156256",
|
"modified": "2017-09-04 17:37:01.192312",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Entry Reference",
|
"name": "Payment Entry Reference",
|
||||||
|
@ -35,7 +35,6 @@ class PaymentRequest(Document):
|
|||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
send_mail = True
|
send_mail = True
|
||||||
self.make_communication_entry()
|
|
||||||
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
|
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
|
||||||
|
|
||||||
if (hasattr(ref_doc, "order_type") and getattr(ref_doc, "order_type") == "Shopping Cart") \
|
if (hasattr(ref_doc, "order_type") and getattr(ref_doc, "order_type") == "Shopping Cart") \
|
||||||
@ -45,6 +44,7 @@ class PaymentRequest(Document):
|
|||||||
if send_mail:
|
if send_mail:
|
||||||
self.set_payment_request_url()
|
self.set_payment_request_url()
|
||||||
self.send_email()
|
self.send_email()
|
||||||
|
self.make_communication_entry()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.check_if_payment_entry_exists()
|
self.check_if_payment_entry_exists()
|
||||||
@ -69,8 +69,11 @@ class PaymentRequest(Document):
|
|||||||
self.db_set('status', 'Initiated')
|
self.db_set('status', 'Initiated')
|
||||||
|
|
||||||
def get_payment_url(self):
|
def get_payment_url(self):
|
||||||
data = frappe.db.get_value(self.reference_doctype, self.reference_name,
|
if self.reference_doctype != "Fees":
|
||||||
["company", "customer_name"], as_dict=1)
|
data = frappe.db.get_value(self.reference_doctype, self.reference_name, ["company", "customer_name"], as_dict=1)
|
||||||
|
else:
|
||||||
|
data = frappe.db.get_value(self.reference_doctype, self.reference_name, ["student_name"], as_dict=1)
|
||||||
|
data.update({"company": frappe.defaults.get_defaults().company})
|
||||||
|
|
||||||
controller = get_payment_gateway_controller(self.payment_gateway)
|
controller = get_payment_gateway_controller(self.payment_gateway)
|
||||||
controller.validate_transaction_currency(self.currency)
|
controller.validate_transaction_currency(self.currency)
|
||||||
@ -277,6 +280,9 @@ def get_amount(ref_doc, dt):
|
|||||||
else:
|
else:
|
||||||
grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate
|
grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate
|
||||||
|
|
||||||
|
if dt == "Fees":
|
||||||
|
grand_total = ref_doc.outstanding_amount
|
||||||
|
|
||||||
if grand_total > 0 :
|
if grand_total > 0 :
|
||||||
return grand_total
|
return grand_total
|
||||||
|
|
||||||
|
@ -2072,6 +2072,37 @@
|
|||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "base_rounding_adjustment",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"label": "Rounding Adjustment (Company Currency)",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 1,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 1,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
@ -2166,6 +2197,37 @@
|
|||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "rounding_adjustment",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"label": "Rounding Adjustment",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "currency",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 1,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 1,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
@ -3858,7 +3920,7 @@
|
|||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"menu_index": 0,
|
"menu_index": 0,
|
||||||
"modified": "2017-08-31 11:22:47.074420",
|
"modified": "2017-09-19 11:22:47.074420",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice",
|
"name": "Purchase Invoice",
|
||||||
|
@ -15,6 +15,7 @@ from erpnext.stock import get_warehouse_account_map
|
|||||||
from erpnext.accounts.general_ledger import make_gl_entries, merge_similar_entries, delete_gl_entries
|
from erpnext.accounts.general_ledger import make_gl_entries, merge_similar_entries, delete_gl_entries
|
||||||
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
|
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
|
||||||
from erpnext.buying.utils import check_for_closed_status
|
from erpnext.buying.utils import check_for_closed_status
|
||||||
|
from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center
|
||||||
|
|
||||||
form_grid_templates = {
|
form_grid_templates = {
|
||||||
"items": "templates/form_grid/item_grid.html"
|
"items": "templates/form_grid/item_grid.html"
|
||||||
@ -353,6 +354,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
self.make_payment_gl_entries(gl_entries)
|
self.make_payment_gl_entries(gl_entries)
|
||||||
self.make_write_off_gl_entry(gl_entries)
|
self.make_write_off_gl_entry(gl_entries)
|
||||||
|
self.make_gle_for_rounding_adjustment(gl_entries)
|
||||||
|
|
||||||
return gl_entries
|
return gl_entries
|
||||||
|
|
||||||
@ -584,6 +586,21 @@ class PurchaseInvoice(BuyingController):
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def make_gle_for_rounding_adjustment(self, gl_entries):
|
||||||
|
if self.rounding_adjustment:
|
||||||
|
round_off_account, round_off_cost_center = \
|
||||||
|
get_round_off_account_and_cost_center(self.company)
|
||||||
|
|
||||||
|
gl_entries.append(
|
||||||
|
self.get_gl_dict({
|
||||||
|
"account": round_off_account,
|
||||||
|
"against": self.supplier,
|
||||||
|
"debit_in_account_currency": self.rounding_adjustment,
|
||||||
|
"debit": self.base_rounding_adjustment,
|
||||||
|
"cost_center": round_off_cost_center,
|
||||||
|
}
|
||||||
|
))
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.check_for_closed_status()
|
self.check_for_closed_status()
|
||||||
|
|
||||||
|
@ -1670,36 +1670,6 @@
|
|||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "net_total",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Net Total",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "currency",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 1,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
@ -1731,6 +1701,36 @@
|
|||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "net_total",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"label": "Net Total",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"options": "currency",
|
||||||
|
"permlevel": 0,
|
||||||
|
"print_hide": 1,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 1,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
@ -2337,6 +2337,37 @@
|
|||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "base_rounding_adjustment",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"label": "Rounding Adjustment (Company Currency)",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 1,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 1,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
@ -2463,6 +2494,37 @@
|
|||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "rounding_adjustment",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"label": "Rounding Adjustment",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "currency",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 1,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 1,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
@ -4749,7 +4811,7 @@
|
|||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"menu_index": 0,
|
"menu_index": 0,
|
||||||
"modified": "2017-08-31 11:23:08.675028",
|
"modified": "2017-09-19 11:23:08.675028",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice",
|
"name": "Sales Invoice",
|
||||||
|
@ -20,6 +20,7 @@ from erpnext.accounts.doctype.asset.depreciation \
|
|||||||
from erpnext.stock.doctype.batch.batch import set_batch_nos
|
from erpnext.stock.doctype.batch.batch import set_batch_nos
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, get_delivery_note_serial_no
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, get_delivery_note_serial_no
|
||||||
from erpnext.setup.doctype.company.company import update_company_current_month_sales
|
from erpnext.setup.doctype.company.company import update_company_current_month_sales
|
||||||
|
from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center
|
||||||
|
|
||||||
form_grid_templates = {
|
form_grid_templates = {
|
||||||
"items": "templates/form_grid/item_grid.html"
|
"items": "templates/form_grid/item_grid.html"
|
||||||
@ -625,6 +626,7 @@ class SalesInvoice(SellingController):
|
|||||||
self.make_gle_for_change_amount(gl_entries)
|
self.make_gle_for_change_amount(gl_entries)
|
||||||
|
|
||||||
self.make_write_off_gl_entry(gl_entries)
|
self.make_write_off_gl_entry(gl_entries)
|
||||||
|
self.make_gle_for_rounding_adjustment(gl_entries)
|
||||||
|
|
||||||
return gl_entries
|
return gl_entries
|
||||||
|
|
||||||
@ -784,6 +786,21 @@ class SalesInvoice(SellingController):
|
|||||||
}, write_off_account_currency)
|
}, write_off_account_currency)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def make_gle_for_rounding_adjustment(self, gl_entries):
|
||||||
|
if self.rounding_adjustment:
|
||||||
|
round_off_account, round_off_cost_center = \
|
||||||
|
get_round_off_account_and_cost_center(self.company)
|
||||||
|
|
||||||
|
gl_entries.append(
|
||||||
|
self.get_gl_dict({
|
||||||
|
"account": round_off_account,
|
||||||
|
"against": self.customer,
|
||||||
|
"credit_in_account_currency": self.rounding_adjustment,
|
||||||
|
"credit": self.base_rounding_adjustment,
|
||||||
|
"cost_center": round_off_cost_center,
|
||||||
|
}
|
||||||
|
))
|
||||||
|
|
||||||
def update_billing_status_in_dn(self, update_modified=True):
|
def update_billing_status_in_dn(self, update_modified=True):
|
||||||
updated_delivery_notes = []
|
updated_delivery_notes = []
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
|
@ -215,12 +215,12 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
si.save()
|
si.save()
|
||||||
|
|
||||||
# with inclusive tax and additional discount
|
# with inclusive tax and additional discount
|
||||||
self.assertEquals(si.net_total, 4298.24)
|
self.assertEquals(si.net_total, 4298.25)
|
||||||
self.assertEquals(si.grand_total, 4900.00)
|
self.assertEquals(si.grand_total, 4900.00)
|
||||||
|
|
||||||
def test_sales_invoice_discount_amount(self):
|
def test_sales_invoice_discount_amount(self):
|
||||||
si = frappe.copy_doc(test_records[3])
|
si = frappe.copy_doc(test_records[3])
|
||||||
si.discount_amount = 104.95
|
si.discount_amount = 104.94
|
||||||
si.append("taxes", {
|
si.append("taxes", {
|
||||||
"charge_type": "On Previous Row Amount",
|
"charge_type": "On Previous Row Amount",
|
||||||
"account_head": "_Test Account Service Tax - _TC",
|
"account_head": "_Test Account Service Tax - _TC",
|
||||||
@ -285,7 +285,7 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
"_Test Account Customs Duty - _TC": [125, 116.35, 1585.40],
|
"_Test Account Customs Duty - _TC": [125, 116.35, 1585.40],
|
||||||
"_Test Account Shipping Charges - _TC": [100, 100, 1685.40],
|
"_Test Account Shipping Charges - _TC": [100, 100, 1685.40],
|
||||||
"_Test Account Discount - _TC": [-180.33, -168.54, 1516.86],
|
"_Test Account Discount - _TC": [-180.33, -168.54, 1516.86],
|
||||||
"_Test Account Service Tax - _TC": [-18.03, -16.86, 1500]
|
"_Test Account Service Tax - _TC": [-18.03, -16.85, 1500.01]
|
||||||
}
|
}
|
||||||
|
|
||||||
for d in si.get("taxes"):
|
for d in si.get("taxes"):
|
||||||
@ -294,10 +294,12 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEquals(si.base_grand_total, 1500)
|
self.assertEquals(si.base_grand_total, 1500)
|
||||||
self.assertEquals(si.grand_total, 1500)
|
self.assertEquals(si.grand_total, 1500)
|
||||||
|
self.assertEquals(si.rounding_adjustment, -0.01)
|
||||||
|
|
||||||
def test_discount_amount_gl_entry(self):
|
def test_discount_amount_gl_entry(self):
|
||||||
|
frappe.db.set_value("Company", "_Test Company", "round_off_account", "Round Off - _TC")
|
||||||
si = frappe.copy_doc(test_records[3])
|
si = frappe.copy_doc(test_records[3])
|
||||||
si.discount_amount = 104.95
|
si.discount_amount = 104.94
|
||||||
si.append("taxes", {
|
si.append("taxes", {
|
||||||
"doctype": "Sales Taxes and Charges",
|
"doctype": "Sales Taxes and Charges",
|
||||||
"charge_type": "On Previous Row Amount",
|
"charge_type": "On Previous Row Amount",
|
||||||
@ -327,7 +329,8 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
[test_records[3]["taxes"][5]["account_head"], 0.0, 116.35],
|
[test_records[3]["taxes"][5]["account_head"], 0.0, 116.35],
|
||||||
[test_records[3]["taxes"][6]["account_head"], 0.0, 100],
|
[test_records[3]["taxes"][6]["account_head"], 0.0, 100],
|
||||||
[test_records[3]["taxes"][7]["account_head"], 168.54, 0.0],
|
[test_records[3]["taxes"][7]["account_head"], 168.54, 0.0],
|
||||||
["_Test Account Service Tax - _TC", 16.86, 0.0]
|
["_Test Account Service Tax - _TC", 16.85, 0.0],
|
||||||
|
["Round Off - _TC", 0.01, 0.0]
|
||||||
])
|
])
|
||||||
|
|
||||||
for gle in gl_entries:
|
for gle in gl_entries:
|
||||||
@ -423,13 +426,12 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
expected_values = {
|
expected_values = {
|
||||||
"keys": ["price_list_rate", "discount_percentage", "rate", "amount",
|
"keys": ["price_list_rate", "discount_percentage", "rate", "amount",
|
||||||
"base_price_list_rate", "base_rate", "base_amount", "net_rate", "net_amount"],
|
"base_price_list_rate", "base_rate", "base_amount", "net_rate", "net_amount"],
|
||||||
"_Test Item Home Desktop 100": [62.5, 0, 62.5, 625.0, 62.5, 62.5, 625.0, 50, 499.98],
|
"_Test Item Home Desktop 100": [62.5, 0, 62.5, 625.0, 62.5, 62.5, 625.0, 50, 499.97600115194473],
|
||||||
"_Test Item Home Desktop 200": [190.66, 0, 190.66, 953.3, 190.66, 190.66, 953.3, 150, 750],
|
"_Test Item Home Desktop 200": [190.66, 0, 190.66, 953.3, 190.66, 190.66, 953.3, 150, 749.9968530500239],
|
||||||
}
|
}
|
||||||
|
|
||||||
# check if children are saved
|
# check if children are saved
|
||||||
self.assertEquals(len(si.get("items")),
|
self.assertEquals(len(si.get("items")), len(expected_values)-1)
|
||||||
len(expected_values)-1)
|
|
||||||
|
|
||||||
# check if item values are calculated
|
# check if item values are calculated
|
||||||
for d in si.get("items"):
|
for d in si.get("items"):
|
||||||
@ -437,28 +439,28 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
self.assertEquals(d.get(k), expected_values[d.item_code][i])
|
self.assertEquals(d.get(k), expected_values[d.item_code][i])
|
||||||
|
|
||||||
# check net total
|
# check net total
|
||||||
self.assertEquals(si.base_net_total, 1249.98)
|
self.assertEquals(si.net_total, 1249.97)
|
||||||
self.assertEquals(si.total, 1578.3)
|
self.assertEquals(si.total, 1578.3)
|
||||||
|
|
||||||
# check tax calculation
|
# check tax calculation
|
||||||
expected_values = {
|
expected_values = {
|
||||||
"keys": ["tax_amount", "total"],
|
"keys": ["tax_amount", "total"],
|
||||||
"_Test Account Excise Duty - _TC": [140, 1389.98],
|
"_Test Account Excise Duty - _TC": [140, 1389.97],
|
||||||
"_Test Account Education Cess - _TC": [2.8, 1392.78],
|
"_Test Account Education Cess - _TC": [2.8, 1392.77],
|
||||||
"_Test Account S&H Education Cess - _TC": [1.4, 1394.18],
|
"_Test Account S&H Education Cess - _TC": [1.4, 1394.17],
|
||||||
"_Test Account CST - _TC": [27.88, 1422.06],
|
"_Test Account CST - _TC": [27.88, 1422.05],
|
||||||
"_Test Account VAT - _TC": [156.25, 1578.31],
|
"_Test Account VAT - _TC": [156.25, 1578.30],
|
||||||
"_Test Account Customs Duty - _TC": [125, 1703.31],
|
"_Test Account Customs Duty - _TC": [125, 1703.30],
|
||||||
"_Test Account Shipping Charges - _TC": [100, 1803.31],
|
"_Test Account Shipping Charges - _TC": [100, 1803.30],
|
||||||
"_Test Account Discount - _TC": [-180.33, 1622.98]
|
"_Test Account Discount - _TC": [-180.33, 1622.97]
|
||||||
}
|
}
|
||||||
|
|
||||||
for d in si.get("taxes"):
|
for d in si.get("taxes"):
|
||||||
for i, k in enumerate(expected_values["keys"]):
|
for i, k in enumerate(expected_values["keys"]):
|
||||||
self.assertEquals(d.get(k), expected_values[d.account_head][i])
|
self.assertEquals(d.get(k), expected_values[d.account_head][i])
|
||||||
|
|
||||||
self.assertEquals(si.base_grand_total, 1622.98)
|
self.assertEquals(si.base_grand_total, 1622.97)
|
||||||
self.assertEquals(si.grand_total, 1622.98)
|
self.assertEquals(si.grand_total, 1622.97)
|
||||||
|
|
||||||
def test_sales_invoice_calculation_export_currency_with_tax_inclusive_price(self):
|
def test_sales_invoice_calculation_export_currency_with_tax_inclusive_price(self):
|
||||||
# prepare
|
# prepare
|
||||||
@ -486,7 +488,7 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
"base_rate": 2500,
|
"base_rate": 2500,
|
||||||
"base_amount": 25000,
|
"base_amount": 25000,
|
||||||
"net_rate": 40,
|
"net_rate": 40,
|
||||||
"net_amount": 399.98,
|
"net_amount": 399.9808009215558,
|
||||||
"base_net_rate": 2000,
|
"base_net_rate": 2000,
|
||||||
"base_net_amount": 19999
|
"base_net_amount": 19999
|
||||||
},
|
},
|
||||||
@ -500,7 +502,7 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
"base_rate": 7500,
|
"base_rate": 7500,
|
||||||
"base_amount": 37500,
|
"base_amount": 37500,
|
||||||
"net_rate": 118.01,
|
"net_rate": 118.01,
|
||||||
"net_amount": 590.05,
|
"net_amount": 590.0531205155963,
|
||||||
"base_net_rate": 5900.5,
|
"base_net_rate": 5900.5,
|
||||||
"base_net_amount": 29502.5
|
"base_net_amount": 29502.5
|
||||||
}
|
}
|
||||||
@ -536,8 +538,11 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
for i, k in enumerate(expected_values["keys"]):
|
for i, k in enumerate(expected_values["keys"]):
|
||||||
self.assertEquals(d.get(k), expected_values[d.account_head][i])
|
self.assertEquals(d.get(k), expected_values[d.account_head][i])
|
||||||
|
|
||||||
self.assertEquals(si.base_grand_total, 60794.5)
|
self.assertEquals(si.base_grand_total, 60795)
|
||||||
self.assertEquals(si.grand_total, 1215.89)
|
self.assertEquals(si.grand_total, 1215.90)
|
||||||
|
self.assertEquals(si.rounding_adjustment, 0.01)
|
||||||
|
self.assertEquals(si.base_rounding_adjustment, 0.50)
|
||||||
|
|
||||||
|
|
||||||
def test_outstanding(self):
|
def test_outstanding(self):
|
||||||
w = self.make()
|
w = self.make()
|
||||||
@ -1286,6 +1291,40 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
current_month_sales = frappe.db.get_value("Company", "_Test Company", "total_monthly_sales")
|
current_month_sales = frappe.db.get_value("Company", "_Test Company", "total_monthly_sales")
|
||||||
self.assertEqual(current_month_sales, existing_current_month_sales)
|
self.assertEqual(current_month_sales, existing_current_month_sales)
|
||||||
|
|
||||||
|
def test_rounding_adjustment(self):
|
||||||
|
si = create_sales_invoice(rate=24900, do_not_save=True)
|
||||||
|
for tax in ["Tax 1", "Tax2"]:
|
||||||
|
si.append("taxes", {
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"account_head": "_Test Account Service Tax - _TC",
|
||||||
|
"description": tax,
|
||||||
|
"rate": 14,
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
"included_in_print_rate": 1
|
||||||
|
})
|
||||||
|
si.save()
|
||||||
|
|
||||||
|
self.assertEqual(si.net_total, 19453.13)
|
||||||
|
self.assertEqual(si.grand_total, 24900)
|
||||||
|
self.assertEqual(si.total_taxes_and_charges, 5446.88)
|
||||||
|
self.assertEqual(si.rounding_adjustment, -0.01)
|
||||||
|
|
||||||
|
expected_values = dict((d[0], d) for d in [
|
||||||
|
[si.debit_to, 24900, 0.0],
|
||||||
|
["_Test Account Service Tax - _TC", 0.0, 5446.88],
|
||||||
|
["Sales - _TC", 0.0, 19453.13],
|
||||||
|
["Round Off - _TC", 0.01, 0.0]
|
||||||
|
])
|
||||||
|
|
||||||
|
gl_entries = frappe.db.sql("""select account, debit, credit
|
||||||
|
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
|
||||||
|
order by account asc""", si.name, as_dict=1)
|
||||||
|
|
||||||
|
for gle in gl_entries:
|
||||||
|
self.assertEquals(expected_values[gle.account][0], gle.account)
|
||||||
|
self.assertEquals(expected_values[gle.account][1], gle.debit)
|
||||||
|
self.assertEquals(expected_values[gle.account][2], gle.credit)
|
||||||
|
|
||||||
def create_sales_invoice(**args):
|
def create_sales_invoice(**args):
|
||||||
si = frappe.new_doc("Sales Invoice")
|
si = frappe.new_doc("Sales Invoice")
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
@ -135,7 +135,8 @@ def get_tax_template(posting_date, args):
|
|||||||
for key, value in args.iteritems():
|
for key, value in args.iteritems():
|
||||||
if key=="use_for_shopping_cart":
|
if key=="use_for_shopping_cart":
|
||||||
conditions.append("use_for_shopping_cart = {0}".format(1 if value else 0))
|
conditions.append("use_for_shopping_cart = {0}".format(1 if value else 0))
|
||||||
if key == 'customer_group' and value:
|
if key == 'customer_group':
|
||||||
|
if not value: value = _("All Customer Groups")
|
||||||
customer_group_condition = get_customer_group_condition(value)
|
customer_group_condition = get_customer_group_condition(value)
|
||||||
conditions.append("ifnull({0}, '') in ('', {1})".format(key, customer_group_condition))
|
conditions.append("ifnull({0}, '') in ('', {1})".format(key, customer_group_condition))
|
||||||
else:
|
else:
|
||||||
|
@ -39,7 +39,7 @@ class TestTaxRule(unittest.TestCase):
|
|||||||
sales_tax_template = "_Test Sales Taxes and Charges Template", priority = 1, from_date = "2015-01-01")
|
sales_tax_template = "_Test Sales Taxes and Charges Template", priority = 1, from_date = "2015-01-01")
|
||||||
tax_rule1.save()
|
tax_rule1.save()
|
||||||
|
|
||||||
self.assertEquals(get_tax_template("2015-01-01", {"customer_group" : "Commercial"}),
|
self.assertEquals(get_tax_template("2015-01-01", {"customer_group" : "Commercial", "use_for_shopping_cart":0}),
|
||||||
"_Test Sales Taxes and Charges Template")
|
"_Test Sales Taxes and Charges Template")
|
||||||
|
|
||||||
def test_conflict_with_overlapping_dates(self):
|
def test_conflict_with_overlapping_dates(self):
|
||||||
|
@ -136,14 +136,7 @@ def round_off_debit_credit(gl_map):
|
|||||||
make_round_off_gle(gl_map, debit_credit_diff)
|
make_round_off_gle(gl_map, debit_credit_diff)
|
||||||
|
|
||||||
def make_round_off_gle(gl_map, debit_credit_diff):
|
def make_round_off_gle(gl_map, debit_credit_diff):
|
||||||
round_off_account, round_off_cost_center = frappe.db.get_value("Company", gl_map[0].company,
|
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(gl_map[0].company)
|
||||||
["round_off_account", "round_off_cost_center"]) or [None, None]
|
|
||||||
if not round_off_account:
|
|
||||||
frappe.throw(_("Please mention Round Off Account in Company"))
|
|
||||||
|
|
||||||
if not round_off_cost_center:
|
|
||||||
frappe.throw(_("Please mention Round Off Cost Center in Company"))
|
|
||||||
|
|
||||||
|
|
||||||
round_off_gle = frappe._dict()
|
round_off_gle = frappe._dict()
|
||||||
for k in ["voucher_type", "voucher_no", "company",
|
for k in ["voucher_type", "voucher_no", "company",
|
||||||
@ -165,6 +158,17 @@ def make_round_off_gle(gl_map, debit_credit_diff):
|
|||||||
|
|
||||||
gl_map.append(round_off_gle)
|
gl_map.append(round_off_gle)
|
||||||
|
|
||||||
|
def get_round_off_account_and_cost_center(company):
|
||||||
|
round_off_account, round_off_cost_center = frappe.db.get_value("Company", company,
|
||||||
|
["round_off_account", "round_off_cost_center"]) or [None, None]
|
||||||
|
if not round_off_account:
|
||||||
|
frappe.throw(_("Please mention Round Off Account in Company"))
|
||||||
|
|
||||||
|
if not round_off_cost_center:
|
||||||
|
frappe.throw(_("Please mention Round Off Cost Center in Company"))
|
||||||
|
|
||||||
|
return round_off_account, round_off_cost_center
|
||||||
|
|
||||||
def delete_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None,
|
def delete_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None,
|
||||||
adv_adj=False, update_outstanding="Yes"):
|
adv_adj=False, update_outstanding="Yes"):
|
||||||
|
|
||||||
|
@ -142,10 +142,16 @@ def get_data(company, root_type, balance_must_be, period_list, filters=None,
|
|||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
def calculate_values(accounts_by_name, gl_entries_by_account, period_list, accumulated_values, ignore_accumulated_values_for_fy):
|
def calculate_values(accounts_by_name, gl_entries_by_account, period_list, accumulated_values, ignore_accumulated_values_for_fy):
|
||||||
for entries in gl_entries_by_account.values():
|
for entries in gl_entries_by_account.values():
|
||||||
for entry in entries:
|
for entry in entries:
|
||||||
d = accounts_by_name.get(entry.account)
|
d = accounts_by_name.get(entry.account)
|
||||||
|
if not d:
|
||||||
|
frappe.msgprint(
|
||||||
|
_("Could not retrieve information for {0}.".format(entry.account)), title="Error",
|
||||||
|
raise_exception=1
|
||||||
|
)
|
||||||
for period in period_list:
|
for period in period_list:
|
||||||
# check if posting date is within the period
|
# check if posting date is within the period
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@ def execute(filters=None):
|
|||||||
gross_profit_data = GrossProfitGenerator(filters)
|
gross_profit_data = GrossProfitGenerator(filters)
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
source = gross_profit_data.grouped_data if filters.get("group_by") != "Invoice" else gross_profit_data.data
|
|
||||||
|
|
||||||
group_wise_columns = frappe._dict({
|
group_wise_columns = frappe._dict({
|
||||||
"invoice": ["parent", "customer", "customer_group", "posting_date","item_code", "item_name","item_group", "brand", "description", \
|
"invoice": ["parent", "customer", "customer_group", "posting_date","item_code", "item_name","item_group", "brand", "description", \
|
||||||
@ -45,7 +44,7 @@ def execute(filters=None):
|
|||||||
|
|
||||||
columns = get_columns(group_wise_columns, filters)
|
columns = get_columns(group_wise_columns, filters)
|
||||||
|
|
||||||
for src in source:
|
for src in gross_profit_data.grouped_data:
|
||||||
row = []
|
row = []
|
||||||
for col in group_wise_columns.get(scrub(filters.group_by)):
|
for col in group_wise_columns.get(scrub(filters.group_by)):
|
||||||
row.append(src.get(col))
|
row.append(src.get(col))
|
||||||
@ -103,6 +102,7 @@ class GrossProfitGenerator(object):
|
|||||||
self.load_stock_ledger_entries()
|
self.load_stock_ledger_entries()
|
||||||
self.load_product_bundle()
|
self.load_product_bundle()
|
||||||
self.load_non_stock_items()
|
self.load_non_stock_items()
|
||||||
|
self.get_returned_invoice_items()
|
||||||
self.process()
|
self.process()
|
||||||
|
|
||||||
def process(self):
|
def process(self):
|
||||||
@ -143,20 +143,16 @@ class GrossProfitGenerator(object):
|
|||||||
row.gross_profit_percent = 0.0
|
row.gross_profit_percent = 0.0
|
||||||
|
|
||||||
# add to grouped
|
# add to grouped
|
||||||
if self.filters.group_by != "Invoice":
|
|
||||||
self.grouped.setdefault(row.get(scrub(self.filters.group_by)), []).append(row)
|
self.grouped.setdefault(row.get(scrub(self.filters.group_by)), []).append(row)
|
||||||
|
|
||||||
self.data.append(row)
|
|
||||||
|
|
||||||
if self.grouped:
|
if self.grouped:
|
||||||
self.get_average_rate_based_on_group_by()
|
self.get_average_rate_based_on_group_by()
|
||||||
else:
|
|
||||||
self.grouped_data = []
|
|
||||||
|
|
||||||
def get_average_rate_based_on_group_by(self):
|
def get_average_rate_based_on_group_by(self):
|
||||||
# sum buying / selling totals for group
|
# sum buying / selling totals for group
|
||||||
self.grouped_data = []
|
self.grouped_data = []
|
||||||
for key in self.grouped.keys():
|
for key in self.grouped.keys():
|
||||||
|
if self.filters.get("group_by") != "Invoice":
|
||||||
for i, row in enumerate(self.grouped[key]):
|
for i, row in enumerate(self.grouped[key]):
|
||||||
if i==0:
|
if i==0:
|
||||||
new_row = row
|
new_row = row
|
||||||
@ -164,19 +160,51 @@ class GrossProfitGenerator(object):
|
|||||||
new_row.qty += row.qty
|
new_row.qty += row.qty
|
||||||
new_row.buying_amount += row.buying_amount
|
new_row.buying_amount += row.buying_amount
|
||||||
new_row.base_amount += row.base_amount
|
new_row.base_amount += row.base_amount
|
||||||
|
new_row = self.set_average_rate(new_row)
|
||||||
|
self.grouped_data.append(new_row)
|
||||||
|
else:
|
||||||
|
for i, row in enumerate(self.grouped[key]):
|
||||||
|
if row.parent in self.returned_invoices \
|
||||||
|
and row.item_code in self.returned_invoices[row.parent]:
|
||||||
|
returned_item_rows = self.returned_invoices[row.parent][row.item_code]
|
||||||
|
for returned_item_row in returned_item_rows:
|
||||||
|
row.qty += returned_item_row.qty
|
||||||
|
row.base_amount += returned_item_row.base_amount
|
||||||
|
row.buying_amount = row.qty * row.buying_rate
|
||||||
|
if row.qty:
|
||||||
|
row = self.set_average_rate(row)
|
||||||
|
self.grouped_data.append(row)
|
||||||
|
|
||||||
|
def set_average_rate(self, new_row):
|
||||||
new_row.gross_profit = new_row.base_amount - new_row.buying_amount
|
new_row.gross_profit = new_row.base_amount - new_row.buying_amount
|
||||||
new_row.gross_profit_percent = ((new_row.gross_profit / new_row.base_amount) * 100.0) \
|
new_row.gross_profit_percent = ((new_row.gross_profit / new_row.base_amount) * 100.0) \
|
||||||
if new_row.base_amount else 0
|
if new_row.base_amount else 0
|
||||||
new_row.buying_rate = (new_row.buying_amount / new_row.qty) \
|
new_row.buying_rate = (new_row.buying_amount / new_row.qty) if new_row.qty else 0
|
||||||
if new_row.qty else 0
|
new_row.base_rate = (new_row.base_amount / new_row.qty) if new_row.qty else 0
|
||||||
new_row.base_rate = (new_row.base_amount / new_row.qty) \
|
return new_row
|
||||||
if new_row.qty else 0
|
|
||||||
|
|
||||||
self.grouped_data.append(new_row)
|
def get_returned_invoice_items(self):
|
||||||
|
returned_invoices = frappe.db.sql("""
|
||||||
|
select
|
||||||
|
si.name, si_item.item_code, si_item.qty, si_item.base_amount, si.return_against
|
||||||
|
from
|
||||||
|
`tabSales Invoice` si, `tabSales Invoice Item` si_item
|
||||||
|
where
|
||||||
|
si.name = si_item.parent
|
||||||
|
and si.docstatus = 1
|
||||||
|
and si.is_return = 1
|
||||||
|
""", as_dict=1)
|
||||||
|
|
||||||
|
self.returned_invoices = frappe._dict()
|
||||||
|
for inv in returned_invoices:
|
||||||
|
self.returned_invoices.setdefault(inv.return_against, frappe._dict())\
|
||||||
|
.setdefault(inv.item_code, []).append(inv)
|
||||||
|
|
||||||
def skip_row(self, row, product_bundles):
|
def skip_row(self, row, product_bundles):
|
||||||
if self.filters.get("group_by") != "Invoice" and not row.get(scrub(self.filters.get("group_by"))):
|
if self.filters.get("group_by") != "Invoice":
|
||||||
|
if not row.get(scrub(self.filters.get("group_by"))):
|
||||||
|
return True
|
||||||
|
elif row.get("is_return") == 1:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get_buying_amount_from_product_bundle(self, row, product_bundle):
|
def get_buying_amount_from_product_bundle(self, row, product_bundle):
|
||||||
@ -268,20 +296,26 @@ class GrossProfitGenerator(object):
|
|||||||
sales_person_cols = ""
|
sales_person_cols = ""
|
||||||
sales_team_table = ""
|
sales_team_table = ""
|
||||||
|
|
||||||
self.si_list = frappe.db.sql("""select `tabSales Invoice Item`.parenttype, `tabSales Invoice Item`.parent,
|
self.si_list = frappe.db.sql("""
|
||||||
`tabSales Invoice`.posting_date, `tabSales Invoice`.posting_time, `tabSales Invoice`.project, `tabSales Invoice`.update_stock,
|
select
|
||||||
`tabSales Invoice`.customer, `tabSales Invoice`.customer_group, `tabSales Invoice`.territory,
|
`tabSales Invoice Item`.parenttype, `tabSales Invoice Item`.parent,
|
||||||
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.item_name, `tabSales Invoice Item`.description,
|
`tabSales Invoice`.posting_date, `tabSales Invoice`.posting_time,
|
||||||
`tabSales Invoice Item`.warehouse, `tabSales Invoice Item`.item_group, `tabSales Invoice Item`.brand,
|
`tabSales Invoice`.project, `tabSales Invoice`.update_stock,
|
||||||
`tabSales Invoice Item`.dn_detail, `tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.stock_qty as qty,
|
`tabSales Invoice`.customer, `tabSales Invoice`.customer_group,
|
||||||
`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount, `tabSales Invoice Item`.name as "item_row"
|
`tabSales Invoice`.territory, `tabSales Invoice Item`.item_code,
|
||||||
|
`tabSales Invoice Item`.item_name, `tabSales Invoice Item`.description,
|
||||||
|
`tabSales Invoice Item`.warehouse, `tabSales Invoice Item`.item_group,
|
||||||
|
`tabSales Invoice Item`.brand, `tabSales Invoice Item`.dn_detail,
|
||||||
|
`tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.stock_qty as qty,
|
||||||
|
`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
|
||||||
|
`tabSales Invoice Item`.name as "item_row", `tabSales Invoice`.is_return
|
||||||
{sales_person_cols}
|
{sales_person_cols}
|
||||||
from
|
from
|
||||||
`tabSales Invoice`
|
`tabSales Invoice` inner join `tabSales Invoice Item`
|
||||||
inner join `tabSales Invoice Item` on `tabSales Invoice Item`.parent = `tabSales Invoice`.name
|
on `tabSales Invoice Item`.parent = `tabSales Invoice`.name
|
||||||
{sales_team_table}
|
{sales_team_table}
|
||||||
where
|
where
|
||||||
`tabSales Invoice`.docstatus = 1 {conditions} {match_cond}
|
`tabSales Invoice`.docstatus=1 {conditions} {match_cond}
|
||||||
order by
|
order by
|
||||||
`tabSales Invoice`.posting_date desc, `tabSales Invoice`.posting_time desc"""
|
`tabSales Invoice`.posting_date desc, `tabSales Invoice`.posting_time desc"""
|
||||||
.format(conditions=conditions, sales_person_cols=sales_person_cols,
|
.format(conditions=conditions, sales_person_cols=sales_person_cols,
|
||||||
|
@ -2102,6 +2102,37 @@
|
|||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "base_rounding_adjustment",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"label": "Rounding Adjustment (Company Currency)",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 1,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 1,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
@ -2227,6 +2258,37 @@
|
|||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "rounding_adjustment",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"label": "Rounding Adjustment",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "currency",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 1,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 1,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
@ -3396,7 +3458,7 @@
|
|||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2017-08-31 11:22:30.190589",
|
"modified": "2017-09-19 11:22:30.190589",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Order",
|
"name": "Purchase Order",
|
||||||
|
@ -65,7 +65,7 @@ QUnit.test("test: request_for_quotation", function(assert) {
|
|||||||
assert.ok(cur_frm.doc.docstatus == 1, "Quotation request submitted");
|
assert.ok(cur_frm.doc.docstatus == 1, "Quotation request submitted");
|
||||||
},
|
},
|
||||||
() => frappe.click_button('Send Supplier Emails'),
|
() => frappe.click_button('Send Supplier Emails'),
|
||||||
() => frappe.timeout(3),
|
() => frappe.timeout(4),
|
||||||
() => {
|
() => {
|
||||||
assert.ok($('div.modal.fade.in > div.modal-dialog > div > div.modal-body.ui-front > div.msgprint').text().includes("Email sent to supplier Test Supplier"), "Send emails working");
|
assert.ok($('div.modal.fade.in > div.modal-dialog > div > div.modal-body.ui-front > div.msgprint').text().includes("Email sent to supplier Test Supplier"), "Send emails working");
|
||||||
},
|
},
|
||||||
|
@ -12,7 +12,7 @@ QUnit.test("Test: Request for Quotation", function (assert) {
|
|||||||
() => frappe.new_doc("Request for Quotation"),
|
() => frappe.new_doc("Request for Quotation"),
|
||||||
() => frappe.timeout(1),
|
() => frappe.timeout(1),
|
||||||
() => cur_frm.set_value("transaction_date", "04-04-2017"),
|
() => cur_frm.set_value("transaction_date", "04-04-2017"),
|
||||||
() => cur_frm.set_value("company", "_Test Company"),
|
() => cur_frm.set_value("company", "For Testing"),
|
||||||
// Add Suppliers
|
// Add Suppliers
|
||||||
() => {
|
() => {
|
||||||
cur_frm.fields_dict.suppliers.grid.grid_rows[0].toggle_view();
|
cur_frm.fields_dict.suppliers.grid.grid_rows[0].toggle_view();
|
||||||
@ -62,7 +62,7 @@ QUnit.test("Test: Request for Quotation", function (assert) {
|
|||||||
},
|
},
|
||||||
() => frappe.timeout(2),
|
() => frappe.timeout(2),
|
||||||
() => {
|
() => {
|
||||||
cur_frm.fields_dict.items.grid.grid_rows[0].doc.warehouse = "_Test Warehouse - _TC";
|
cur_frm.fields_dict.items.grid.grid_rows[0].doc.warehouse = "_Test Warehouse - FT";
|
||||||
},
|
},
|
||||||
() => frappe.click_button('Save'),
|
() => frappe.click_button('Save'),
|
||||||
() => frappe.timeout(1),
|
() => frappe.timeout(1),
|
||||||
@ -104,7 +104,7 @@ QUnit.test("Test: Request for Quotation", function (assert) {
|
|||||||
() => frappe.timeout(1),
|
() => frappe.timeout(1),
|
||||||
() => frappe.click_button('Make Supplier Quotation'),
|
() => frappe.click_button('Make Supplier Quotation'),
|
||||||
() => frappe.timeout(1),
|
() => frappe.timeout(1),
|
||||||
() => cur_frm.set_value("company", "_Test Company"),
|
() => cur_frm.set_value("company", "For Testing"),
|
||||||
() => cur_frm.fields_dict.items.grid.grid_rows[0].doc.rate = 4.99,
|
() => cur_frm.fields_dict.items.grid.grid_rows[0].doc.rate = 4.99,
|
||||||
() => frappe.timeout(1),
|
() => frappe.timeout(1),
|
||||||
() => frappe.click_button('Save'),
|
() => frappe.click_button('Save'),
|
||||||
|
@ -16,7 +16,7 @@ class Supplier(TransactionBase):
|
|||||||
|
|
||||||
def onload(self):
|
def onload(self):
|
||||||
"""Load address and contacts in `__onload`"""
|
"""Load address and contacts in `__onload`"""
|
||||||
load_address_and_contact(self, "supplier")
|
load_address_and_contact(self)
|
||||||
self.load_dashboard_info()
|
self.load_dashboard_info()
|
||||||
|
|
||||||
def load_dashboard_info(self):
|
def load_dashboard_info(self):
|
||||||
|
@ -13,8 +13,8 @@ QUnit.test("test: supplier", function(assert) {
|
|||||||
{credit_days_based_on: 'Fixed Days'},
|
{credit_days_based_on: 'Fixed Days'},
|
||||||
{accounts: [
|
{accounts: [
|
||||||
[
|
[
|
||||||
{'company': "Test Company"},
|
{'company': "For Testing"},
|
||||||
{'account': "Creditors - TC"}
|
{'account': "Creditors - FT"}
|
||||||
]]
|
]]
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
@ -68,7 +68,7 @@ QUnit.test("test: supplier", function(assert) {
|
|||||||
assert.ok(cur_frm.doc.supplier_name == 'Test Supplier', "Name correct");
|
assert.ok(cur_frm.doc.supplier_name == 'Test Supplier', "Name correct");
|
||||||
assert.ok(cur_frm.doc.supplier_type == 'Hardware', "Type correct");
|
assert.ok(cur_frm.doc.supplier_type == 'Hardware', "Type correct");
|
||||||
assert.ok(cur_frm.doc.default_currency == 'INR', "Currency correct");
|
assert.ok(cur_frm.doc.default_currency == 'INR', "Currency correct");
|
||||||
assert.ok(cur_frm.doc.accounts[0].account == 'Creditors - '+frappe.get_abbr('Test Company'), " Account Head abbr correct");
|
assert.ok(cur_frm.doc.accounts[0].account == 'Creditors - '+frappe.get_abbr('For Testing'), " Account Head abbr correct");
|
||||||
assert.ok($('.address-box:nth-child(3) p').text().includes('Shipping City 3'), "Address correct");
|
assert.ok($('.address-box:nth-child(3) p').text().includes('Shipping City 3'), "Address correct");
|
||||||
assert.ok($('.col-sm-6+ .col-sm-6 .h6').text().includes('Contact 3'), "Contact correct");
|
assert.ok($('.col-sm-6+ .col-sm-6 .h6').text().includes('Contact 3'), "Contact correct");
|
||||||
},
|
},
|
||||||
|
@ -1676,6 +1676,37 @@
|
|||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "base_rounding_adjustment",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"label": "Rounding Adjustment (Company Currency",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 1,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 1,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
@ -1801,6 +1832,37 @@
|
|||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "rounding_adjustment",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"label": "Rounding Adjustment",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "currency",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 1,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 1,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
@ -2308,7 +2370,7 @@
|
|||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"menu_index": 0,
|
"menu_index": 0,
|
||||||
"modified": "2017-08-31 11:23:25.268924",
|
"modified": "2017-09-19 11:23:25.268924",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Supplier Quotation",
|
"name": "Supplier Quotation",
|
||||||
|
@ -8,13 +8,13 @@ QUnit.test("test: supplier quotation with item wise discount", function(assert){
|
|||||||
() => {
|
() => {
|
||||||
return frappe.tests.make('Supplier Quotation', [
|
return frappe.tests.make('Supplier Quotation', [
|
||||||
{supplier: 'Test Supplier'},
|
{supplier: 'Test Supplier'},
|
||||||
{company: 'Test Company'},
|
{company: 'For Testing'},
|
||||||
{items: [
|
{items: [
|
||||||
[
|
[
|
||||||
{"item_code": 'Test Product 4'},
|
{"item_code": 'Test Product 4'},
|
||||||
{"qty": 5},
|
{"qty": 5},
|
||||||
{"uom": 'Unit'},
|
{"uom": 'Unit'},
|
||||||
{"warehouse": 'All Warehouses - TC'},
|
{"warehouse": 'All Warehouses - FT'},
|
||||||
{'discount_percentage': 10},
|
{'discount_percentage': 10},
|
||||||
]
|
]
|
||||||
]}
|
]}
|
||||||
|
@ -261,5 +261,12 @@ def get_data():
|
|||||||
"icon": "octicon octicon-mortar-board",
|
"icon": "octicon octicon-mortar-board",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"label": _("Schools")
|
"label": _("Schools")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "Healthcare",
|
||||||
|
"color": "#FF888B",
|
||||||
|
"icon": "octicon octicon-plus",
|
||||||
|
"type": "module",
|
||||||
|
"label": _("Healthcare")
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
157
erpnext/config/healthcare.py
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
def get_data():
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"label": _("Consultation"),
|
||||||
|
"icon": "icon-star",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Patient Appointment",
|
||||||
|
"description": _("Patient Appointment"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Consultation",
|
||||||
|
"label": _("Consultation"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Vital Signs",
|
||||||
|
"label": _("Vital Signs"),
|
||||||
|
"description": _("Record Patient Vitals"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "page",
|
||||||
|
"name": "medical_record",
|
||||||
|
"label": _("Patient Medical Record"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "page",
|
||||||
|
"name": "appointment-analytic",
|
||||||
|
"label": _("Appointment Analytics"),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Laboratory"),
|
||||||
|
"icon": "icon-list",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Lab Test",
|
||||||
|
"description": _("Results"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Sample Collection",
|
||||||
|
"label": _("Sample Collection"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "report",
|
||||||
|
"name": "Lab Test Report",
|
||||||
|
"is_query_report": True
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Masters"),
|
||||||
|
"icon": "icon-list",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Patient",
|
||||||
|
"label": _("Patient"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Physician",
|
||||||
|
"label": "Physician",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Physician Schedule",
|
||||||
|
"label": _("Physician Schedule"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Medical Code Standard",
|
||||||
|
"label": _("Medical Code Standard"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Medical Code",
|
||||||
|
"label": _("Medical Code"),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Setup"),
|
||||||
|
"icon": "icon-cog",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Healthcare Settings",
|
||||||
|
"label": _("Healthcare Settings"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Medical Department",
|
||||||
|
"label": "Medical Department"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Appointment Type",
|
||||||
|
"description": _("Appointment Type Master"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Prescription Dosage",
|
||||||
|
"description": _("Prescription Dosage")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Prescription Duration",
|
||||||
|
"description": _("Prescription Period")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Complaint",
|
||||||
|
"description": _("Complaint")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Diagnosis",
|
||||||
|
"description": _("Diagnosis")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Lab Test Sample",
|
||||||
|
"description": _("Test Sample Master."),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Lab Test UOM",
|
||||||
|
"description": _("Lab Test UOM.")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Antibiotic",
|
||||||
|
"description": _("Antibiotic.")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Sensitivity",
|
||||||
|
"description": _("Sensitivity Naming.")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Lab Test Template",
|
||||||
|
"description": _("Lab Test Configurations.")
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
@ -154,6 +154,10 @@ def get_data():
|
|||||||
"type": "doctype",
|
"type": "doctype",
|
||||||
"name": "Fees"
|
"name": "Fees"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Fee Schedule"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "doctype",
|
"type": "doctype",
|
||||||
"name": "Fee Structure"
|
"name": "Fee Structure"
|
||||||
|
@ -121,9 +121,10 @@ class calculate_taxes_and_totals(object):
|
|||||||
cumulated_tax_fraction += tax.tax_fraction_for_current_item
|
cumulated_tax_fraction += tax.tax_fraction_for_current_item
|
||||||
|
|
||||||
if cumulated_tax_fraction and not self.discount_amount_applied and item.qty:
|
if cumulated_tax_fraction and not self.discount_amount_applied and item.qty:
|
||||||
item.net_amount = flt(item.amount / (1 + cumulated_tax_fraction), item.precision("net_amount"))
|
item.net_amount = flt(item.amount / (1 + cumulated_tax_fraction))
|
||||||
item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate"))
|
item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate"))
|
||||||
item.discount_percentage = flt(item.discount_percentage, item.precision("discount_percentage"))
|
item.discount_percentage = flt(item.discount_percentage,
|
||||||
|
item.precision("discount_percentage"))
|
||||||
|
|
||||||
self._set_in_company_currency(item, ["net_rate", "net_amount"])
|
self._set_in_company_currency(item, ["net_rate", "net_amount"])
|
||||||
|
|
||||||
@ -173,6 +174,7 @@ class calculate_taxes_and_totals(object):
|
|||||||
self.doc.round_floats_in(self.doc, ["total", "base_total", "net_total", "base_net_total"])
|
self.doc.round_floats_in(self.doc, ["total", "base_total", "net_total", "base_net_total"])
|
||||||
|
|
||||||
def calculate_taxes(self):
|
def calculate_taxes(self):
|
||||||
|
self.doc.rounding_adjustment = 0
|
||||||
# maintain actual tax rate based on idx
|
# maintain actual tax rate based on idx
|
||||||
actual_tax_dict = dict([[tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
|
actual_tax_dict = dict([[tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
|
||||||
for tax in self.doc.get("taxes") if tax.charge_type == "Actual"])
|
for tax in self.doc.get("taxes") if tax.charge_type == "Actual"])
|
||||||
@ -222,7 +224,9 @@ class calculate_taxes_and_totals(object):
|
|||||||
# adjust Discount Amount loss in last tax iteration
|
# adjust Discount Amount loss in last tax iteration
|
||||||
if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
|
if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
|
||||||
and self.doc.discount_amount and self.doc.apply_discount_on == "Grand Total":
|
and self.doc.discount_amount and self.doc.apply_discount_on == "Grand Total":
|
||||||
self.adjust_discount_amount_loss(tax)
|
self.doc.rounding_adjustment = flt(self.doc.grand_total
|
||||||
|
- flt(self.doc.discount_amount) - tax.total,
|
||||||
|
self.doc.precision("rounding_adjustment"))
|
||||||
|
|
||||||
def get_tax_amount_if_for_valuation_or_deduction(self, tax_amount, tax):
|
def get_tax_amount_if_for_valuation_or_deduction(self, tax_amount, tax):
|
||||||
# if just for valuation, do not add the tax amount in total
|
# if just for valuation, do not add the tax amount in total
|
||||||
@ -277,36 +281,26 @@ class calculate_taxes_and_totals(object):
|
|||||||
tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount,
|
tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount,
|
||||||
tax.precision("tax_amount"))
|
tax.precision("tax_amount"))
|
||||||
|
|
||||||
def adjust_discount_amount_loss(self, tax):
|
|
||||||
discount_amount_loss = self.doc.grand_total - flt(self.doc.discount_amount) - tax.total
|
|
||||||
tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount +
|
|
||||||
discount_amount_loss, tax.precision("tax_amount"))
|
|
||||||
tax.total = flt(tax.total + discount_amount_loss, tax.precision("total"))
|
|
||||||
|
|
||||||
self._set_in_company_currency(tax, ["total", "tax_amount_after_discount_amount"])
|
|
||||||
|
|
||||||
def manipulate_grand_total_for_inclusive_tax(self):
|
def manipulate_grand_total_for_inclusive_tax(self):
|
||||||
# if fully inclusive taxes and diff
|
# if fully inclusive taxes and diff
|
||||||
if self.doc.get("taxes") and all(cint(t.included_in_print_rate) for t in self.doc.get("taxes")):
|
if self.doc.get("taxes") and any([cint(t.included_in_print_rate) for t in self.doc.get("taxes")]):
|
||||||
last_tax = self.doc.get("taxes")[-1]
|
last_tax = self.doc.get("taxes")[-1]
|
||||||
diff = self.doc.total - flt(last_tax.total, self.doc.precision("grand_total"))
|
non_inclusive_tax_amount = sum([flt(d.tax_amount_after_discount_amount)
|
||||||
|
for d in self.doc.get("taxes") if not d.included_in_print_rate])
|
||||||
if diff and abs(diff) <= (2.0 / 10**last_tax.precision("tax_amount")):
|
diff = self.doc.total + non_inclusive_tax_amount \
|
||||||
last_tax.tax_amount += diff
|
- flt(last_tax.total, last_tax.precision("total"))
|
||||||
last_tax.tax_amount_after_discount_amount += diff
|
if diff and abs(diff) <= (5.0 / 10**last_tax.precision("tax_amount")):
|
||||||
last_tax.total += diff
|
self.doc.rounding_adjustment = flt(flt(self.doc.rounding_adjustment) +
|
||||||
|
flt(diff), self.doc.precision("rounding_adjustment"))
|
||||||
self._set_in_company_currency(last_tax,
|
|
||||||
["total", "tax_amount", "tax_amount_after_discount_amount"])
|
|
||||||
|
|
||||||
def calculate_totals(self):
|
def calculate_totals(self):
|
||||||
self.doc.grand_total = flt(self.doc.get("taxes")[-1].total
|
self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(self.doc.rounding_adjustment) \
|
||||||
if self.doc.get("taxes") else self.doc.net_total)
|
if self.doc.get("taxes") else flt(self.doc.net_total)
|
||||||
|
|
||||||
self.doc.total_taxes_and_charges = flt(self.doc.grand_total - self.doc.net_total,
|
self.doc.total_taxes_and_charges = flt(self.doc.grand_total - self.doc.net_total
|
||||||
self.doc.precision("total_taxes_and_charges"))
|
- flt(self.doc.rounding_adjustment), self.doc.precision("total_taxes_and_charges"))
|
||||||
|
|
||||||
self._set_in_company_currency(self.doc, ["total_taxes_and_charges"])
|
self._set_in_company_currency(self.doc, ["total_taxes_and_charges", "rounding_adjustment"])
|
||||||
|
|
||||||
if self.doc.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]:
|
if self.doc.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]:
|
||||||
self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate) \
|
self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate) \
|
||||||
@ -326,13 +320,22 @@ class calculate_taxes_and_totals(object):
|
|||||||
if (self.doc.taxes_and_charges_added or self.doc.taxes_and_charges_deducted) \
|
if (self.doc.taxes_and_charges_added or self.doc.taxes_and_charges_deducted) \
|
||||||
else self.doc.base_net_total
|
else self.doc.base_net_total
|
||||||
|
|
||||||
self._set_in_company_currency(self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"])
|
self._set_in_company_currency(self.doc,
|
||||||
|
["taxes_and_charges_added", "taxes_and_charges_deducted"])
|
||||||
|
|
||||||
self.doc.round_floats_in(self.doc, ["grand_total", "base_grand_total"])
|
self.doc.round_floats_in(self.doc, ["grand_total", "base_grand_total"])
|
||||||
|
|
||||||
|
self.set_rounded_total()
|
||||||
|
|
||||||
|
def set_rounded_total(self):
|
||||||
|
if frappe.db.get_single_value("Global Defaults", "disable_rounded_total"):
|
||||||
|
self.doc.rounded_total = self.doc.base_rounded_total = 0
|
||||||
|
return
|
||||||
|
|
||||||
if self.doc.meta.get_field("rounded_total"):
|
if self.doc.meta.get_field("rounded_total"):
|
||||||
self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total,
|
self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total,
|
||||||
self.doc.currency, self.doc.precision("rounded_total"))
|
self.doc.currency, self.doc.precision("rounded_total"))
|
||||||
|
|
||||||
if self.doc.meta.get_field("base_rounded_total"):
|
if self.doc.meta.get_field("base_rounded_total"):
|
||||||
company_currency = erpnext.get_company_currency(self.doc.company)
|
company_currency = erpnext.get_company_currency(self.doc.company)
|
||||||
|
|
||||||
@ -525,7 +528,7 @@ def get_itemised_tax_breakup_html(doc):
|
|||||||
for tax in doc.taxes:
|
for tax in doc.taxes:
|
||||||
if getattr(tax, "category", None) and tax.category=="Valuation":
|
if getattr(tax, "category", None) and tax.category=="Valuation":
|
||||||
continue
|
continue
|
||||||
if tax.description not in tax_accounts:
|
if tax.description not in tax_accounts and tax.tax_amount_after_discount_amount:
|
||||||
tax_accounts.append(tax.description)
|
tax_accounts.append(tax.description)
|
||||||
|
|
||||||
headers = get_itemised_tax_breakup_header(doc.doctype + " Item", tax_accounts)
|
headers = get_itemised_tax_breakup_header(doc.doctype + " Item", tax_accounts)
|
||||||
@ -565,17 +568,12 @@ def get_itemised_tax(taxes):
|
|||||||
if getattr(tax, "category", None) and tax.category=="Valuation":
|
if getattr(tax, "category", None) and tax.category=="Valuation":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
tax_amount_precision = tax.precision("tax_amount")
|
|
||||||
tax_rate_precision = tax.precision("rate")
|
|
||||||
|
|
||||||
item_tax_map = json.loads(tax.item_wise_tax_detail) if tax.item_wise_tax_detail else {}
|
item_tax_map = json.loads(tax.item_wise_tax_detail) if tax.item_wise_tax_detail else {}
|
||||||
|
if item_tax_map:
|
||||||
for item_code, tax_data in item_tax_map.items():
|
for item_code, tax_data in item_tax_map.items():
|
||||||
itemised_tax.setdefault(item_code, frappe._dict())
|
itemised_tax.setdefault(item_code, frappe._dict())
|
||||||
|
|
||||||
if isinstance(tax_data, list):
|
if isinstance(tax_data, list):
|
||||||
precision = tax_amount_precision if tax.charge_type == "Actual" else tax_rate_precision
|
|
||||||
|
|
||||||
itemised_tax[item_code][tax.description] = frappe._dict(dict(
|
itemised_tax[item_code][tax.description] = frappe._dict(dict(
|
||||||
tax_rate=flt(tax_data[0]),
|
tax_rate=flt(tax_data[0]),
|
||||||
tax_amount=flt(tax_data[1])
|
tax_amount=flt(tax_data[1])
|
||||||
|
@ -19,7 +19,7 @@ QUnit.test("test: item", function (assert) {
|
|||||||
{is_stock_item: is_stock_item},
|
{is_stock_item: is_stock_item},
|
||||||
{standard_rate: keyboard_cost},
|
{standard_rate: keyboard_cost},
|
||||||
{opening_stock: no_of_items_to_stock},
|
{opening_stock: no_of_items_to_stock},
|
||||||
{default_warehouse: "Stores - RB"}
|
{default_warehouse: "Stores - FT"}
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
() => {
|
() => {
|
||||||
@ -45,7 +45,7 @@ QUnit.test("test: item", function (assert) {
|
|||||||
{is_stock_item: is_stock_item},
|
{is_stock_item: is_stock_item},
|
||||||
{standard_rate: screen_cost},
|
{standard_rate: screen_cost},
|
||||||
{opening_stock: no_of_items_to_stock},
|
{opening_stock: no_of_items_to_stock},
|
||||||
{default_warehouse: "Stores - RB"}
|
{default_warehouse: "Stores - FT"}
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ QUnit.test("test: item", function (assert) {
|
|||||||
{is_stock_item: is_stock_item},
|
{is_stock_item: is_stock_item},
|
||||||
{standard_rate: CPU_cost},
|
{standard_rate: CPU_cost},
|
||||||
{opening_stock: no_of_items_to_stock},
|
{opening_stock: no_of_items_to_stock},
|
||||||
{default_warehouse: "Stores - RB"}
|
{default_warehouse: "Stores - FT"}
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ QUnit.test("test: item", function (assert) {
|
|||||||
"Item", [
|
"Item", [
|
||||||
{item_code: "Laptop"},
|
{item_code: "Laptop"},
|
||||||
{item_group: "Products"},
|
{item_group: "Products"},
|
||||||
{default_warehouse: "Stores - RB"}
|
{default_warehouse: "Stores - FT"}
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
() => frappe.tests.make(
|
() => frappe.tests.make(
|
||||||
@ -85,7 +85,7 @@ QUnit.test("test: item", function (assert) {
|
|||||||
{is_stock_item: is_stock_item},
|
{is_stock_item: is_stock_item},
|
||||||
{standard_rate: scrap_cost},
|
{standard_rate: scrap_cost},
|
||||||
{opening_stock: no_of_items_to_stock},
|
{opening_stock: no_of_items_to_stock},
|
||||||
{default_warehouse: "Stores - RB"}
|
{default_warehouse: "Stores - FT"}
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
() => frappe.tests.make(
|
() => frappe.tests.make(
|
||||||
|
@ -20,7 +20,7 @@ class Lead(SellingController):
|
|||||||
def onload(self):
|
def onload(self):
|
||||||
customer = frappe.db.get_value("Customer", {"lead_name": self.name})
|
customer = frappe.db.get_value("Customer", {"lead_name": self.name})
|
||||||
self.get("__onload").is_customer = customer
|
self.get("__onload").is_customer = customer
|
||||||
load_address_and_contact(self, "lead")
|
load_address_and_contact(self)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self._prev = frappe._dict({
|
self._prev = frappe._dict({
|
||||||
|
5420
erpnext/demo/data/drug_list.json
Normal file
@ -167,6 +167,7 @@
|
|||||||
"item_group": "Products",
|
"item_group": "Products",
|
||||||
"item_name": "Wind Turbine-S",
|
"item_name": "Wind Turbine-S",
|
||||||
"variant_of": "Wind Turbine",
|
"variant_of": "Wind Turbine",
|
||||||
|
"valuation_rate": 300,
|
||||||
"attributes":[
|
"attributes":[
|
||||||
{
|
{
|
||||||
"attribute": "Size",
|
"attribute": "Size",
|
||||||
@ -183,6 +184,7 @@
|
|||||||
"item_group": "Products",
|
"item_group": "Products",
|
||||||
"item_name": "Wind Turbine-M",
|
"item_name": "Wind Turbine-M",
|
||||||
"variant_of": "Wind Turbine",
|
"variant_of": "Wind Turbine",
|
||||||
|
"valuation_rate": 300,
|
||||||
"attributes":[
|
"attributes":[
|
||||||
{
|
{
|
||||||
"attribute": "Size",
|
"attribute": "Size",
|
||||||
@ -199,6 +201,7 @@
|
|||||||
"item_group": "Products",
|
"item_group": "Products",
|
||||||
"item_name": "Wind Turbine-L",
|
"item_name": "Wind Turbine-L",
|
||||||
"variant_of": "Wind Turbine",
|
"variant_of": "Wind Turbine",
|
||||||
|
"valuation_rate": 300,
|
||||||
"attributes":[
|
"attributes":[
|
||||||
{
|
{
|
||||||
"attribute": "Size",
|
"attribute": "Size",
|
||||||
|
27
erpnext/demo/data/patient.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"patient_name": "lila",
|
||||||
|
"gender": "Female"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"patient_name": "charline",
|
||||||
|
"gender": "Female"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"patient_name": "soren",
|
||||||
|
"last_name": "le gall",
|
||||||
|
"gender": "Male"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"patient_name": "fanny",
|
||||||
|
"gender": "Female"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"patient_name": "julie",
|
||||||
|
"gender": "Female"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"patient_name": "louka",
|
||||||
|
"gender": "Male"
|
||||||
|
}
|
||||||
|
]
|
17
erpnext/demo/data/physician.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"doctype": "Physician",
|
||||||
|
"first_name": "Eddie Jessup",
|
||||||
|
"department": "Pathology"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "Physician",
|
||||||
|
"first_name": "Deepshi Garg",
|
||||||
|
"department": "ENT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "Physician",
|
||||||
|
"first_name": "Amit Jain",
|
||||||
|
"department": "Microbiology"
|
||||||
|
}
|
||||||
|
]
|
@ -4,7 +4,7 @@ import frappe, sys
|
|||||||
import erpnext
|
import erpnext
|
||||||
import frappe.utils
|
import frappe.utils
|
||||||
from erpnext.demo.user import hr, sales, purchase, manufacturing, stock, accounts, projects, fixed_asset, schools
|
from erpnext.demo.user import hr, sales, purchase, manufacturing, stock, accounts, projects, fixed_asset, schools
|
||||||
from erpnext.demo.setup import education, manufacture, setup_data
|
from erpnext.demo.setup import education, manufacture, setup_data, healthcare
|
||||||
"""
|
"""
|
||||||
Make a demo
|
Make a demo
|
||||||
|
|
||||||
@ -30,6 +30,8 @@ def make(domain='Manufacturing', days=100):
|
|||||||
manufacture.setup_data()
|
manufacture.setup_data()
|
||||||
elif domain== 'Education':
|
elif domain== 'Education':
|
||||||
education.setup_data()
|
education.setup_data()
|
||||||
|
elif domain== 'Healthcare':
|
||||||
|
healthcare.setup_data()
|
||||||
|
|
||||||
site = frappe.local.site
|
site = frappe.local.site
|
||||||
frappe.destroy()
|
frappe.destroy()
|
||||||
|
@ -15,5 +15,8 @@ data = {
|
|||||||
},
|
},
|
||||||
'Education': {
|
'Education': {
|
||||||
'company_name': 'Whitmore College'
|
'company_name': 'Whitmore College'
|
||||||
|
},
|
||||||
|
'Healthcare': {
|
||||||
|
'company_name': 'ABC Hospital Ltd.'
|
||||||
}
|
}
|
||||||
}
|
}
|
166
erpnext/demo/setup/healthcare.py
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
import frappe, json
|
||||||
|
from frappe.utils.make_random import get_random
|
||||||
|
import datetime
|
||||||
|
from erpnext.demo.setup.setup_data import import_json
|
||||||
|
from frappe.utils import getdate
|
||||||
|
from erpnext.healthcare.doctype.lab_test.lab_test import create_test_from_template
|
||||||
|
|
||||||
|
def setup_data():
|
||||||
|
frappe.flags.mute_emails = True
|
||||||
|
make_masters()
|
||||||
|
make_patient()
|
||||||
|
make_lab_test()
|
||||||
|
make_consulation()
|
||||||
|
make_appointment()
|
||||||
|
consulation_on_appointment()
|
||||||
|
lab_test_on_consultation()
|
||||||
|
frappe.db.commit()
|
||||||
|
frappe.clear_cache()
|
||||||
|
|
||||||
|
def make_masters():
|
||||||
|
import_json("Physician")
|
||||||
|
import_drug()
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
def make_patient():
|
||||||
|
file_path = get_json_path("Patient")
|
||||||
|
with open(file_path, "r") as open_file:
|
||||||
|
patient_data = json.loads(open_file.read())
|
||||||
|
count = 1
|
||||||
|
|
||||||
|
for d in enumerate(patient_data):
|
||||||
|
patient = frappe.new_doc("Patient")
|
||||||
|
patient.patient_name = d[1]['patient_name'].title()
|
||||||
|
patient.sex = d[1]['gender']
|
||||||
|
patient.blood_group = "A Positive"
|
||||||
|
patient.date_of_birth = datetime.datetime(1990, 3, 25)
|
||||||
|
patient.email_id = d[1]['patient_name'] + "_" + patient.date_of_birth.strftime('%m/%d/%Y') + "@example.com"
|
||||||
|
if count <5:
|
||||||
|
patient.insert()
|
||||||
|
frappe.db.commit()
|
||||||
|
count+=1
|
||||||
|
|
||||||
|
def make_appointment():
|
||||||
|
i = 1
|
||||||
|
while i <= 4:
|
||||||
|
physician = get_random("Physician")
|
||||||
|
department = frappe.get_value("Physician", physician, "department")
|
||||||
|
patient = get_random("Patient")
|
||||||
|
patient_sex = frappe.get_value("Patient", patient, "sex")
|
||||||
|
appointment = frappe.new_doc("Patient Appointment")
|
||||||
|
startDate = datetime.datetime.now()
|
||||||
|
for x in random_date(startDate,0):
|
||||||
|
appointment_datetime = x
|
||||||
|
appointment.appointment_datetime = appointment_datetime
|
||||||
|
appointment.appointment_time = appointment_datetime
|
||||||
|
appointment.appointment_date = appointment_datetime
|
||||||
|
appointment.patient = patient
|
||||||
|
appointment.patient_sex = patient_sex
|
||||||
|
appointment.physician = physician
|
||||||
|
appointment.department = department
|
||||||
|
appointment.save(ignore_permissions = True)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
def make_consulation():
|
||||||
|
for i in xrange(3):
|
||||||
|
physician = get_random("Physician")
|
||||||
|
department = frappe.get_value("Physician", physician, "department")
|
||||||
|
patient = get_random("Patient")
|
||||||
|
patient_sex = frappe.get_value("Patient", patient, "sex")
|
||||||
|
consultation = set_consultation(patient, patient_sex, physician, department, getdate(), i)
|
||||||
|
consultation.save(ignore_permissions=True)
|
||||||
|
|
||||||
|
def consulation_on_appointment():
|
||||||
|
for i in xrange(3):
|
||||||
|
appointment = get_random("Patient Appointment")
|
||||||
|
appointment = frappe.get_doc("Patient Appointment",appointment)
|
||||||
|
consultation = set_consultation(appointment.patient, appointment.patient_sex, appointment.physician, appointment.department, appointment.appointment_date, i)
|
||||||
|
consultation.appointment = appointment.name
|
||||||
|
consultation.save(ignore_permissions=True)
|
||||||
|
|
||||||
|
def set_consultation(patient, patient_sex, physician, department, consultation_date, i):
|
||||||
|
consultation = frappe.new_doc("Consultation")
|
||||||
|
consultation.patient = patient
|
||||||
|
consultation.patient_sex = patient_sex
|
||||||
|
consultation.physician = physician
|
||||||
|
consultation.visit_department = department
|
||||||
|
consultation.consultation_date = consultation_date
|
||||||
|
if i > 2 and patient_sex=='Female':
|
||||||
|
consultation.symptoms = "Having chest pains for the last week."
|
||||||
|
consultation.diagnosis = """This patient's description of dull, aching,
|
||||||
|
exertion related substernal chest pain is suggestive of ischemic
|
||||||
|
cardiac origin. Her findings of a FH of early ASCVD, hypertension,
|
||||||
|
and early surgical menopause are pertinent risk factors for development
|
||||||
|
of coronary artery disease. """
|
||||||
|
else:
|
||||||
|
consultation = append_drug_rx(consultation)
|
||||||
|
consultation = append_test_rx(consultation)
|
||||||
|
return consultation
|
||||||
|
|
||||||
|
def make_lab_test():
|
||||||
|
physician = get_random("Physician")
|
||||||
|
patient = get_random("Patient")
|
||||||
|
patient_sex = frappe.get_value("Patient", patient, "sex")
|
||||||
|
template = get_random("Lab Test Template")
|
||||||
|
set_lab_test(patient, patient_sex, physician, template)
|
||||||
|
|
||||||
|
def lab_test_on_consultation():
|
||||||
|
i = 1
|
||||||
|
while i <= 2:
|
||||||
|
test_rx = get_random("Lab Prescription", filters={'test_created': 0})
|
||||||
|
test_rx = frappe.get_doc("Lab Prescription", test_rx)
|
||||||
|
consultation = frappe.get_doc("Consultation", test_rx.parent)
|
||||||
|
set_lab_test(consultation.patient, consultation.patient_sex, consultation.physician, test_rx.test_code, test_rx.name)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
def set_lab_test(patient, patient_sex, physician, template, rx=None):
|
||||||
|
lab_test = frappe.new_doc("Lab Test")
|
||||||
|
lab_test.physician = physician
|
||||||
|
lab_test.patient = patient
|
||||||
|
lab_test.patient_sex = patient_sex
|
||||||
|
lab_test.template = template
|
||||||
|
lab_test.prescription = rx
|
||||||
|
create_test_from_template(lab_test)
|
||||||
|
|
||||||
|
def append_test_rx(consultation):
|
||||||
|
i = 1
|
||||||
|
while i <= 2:
|
||||||
|
test_rx = consultation.append("test_prescription")
|
||||||
|
test_rx.test_code = get_random("Lab Test Template")
|
||||||
|
i += 1
|
||||||
|
return consultation
|
||||||
|
|
||||||
|
def append_drug_rx(consultation):
|
||||||
|
i = 1
|
||||||
|
while i <= 3:
|
||||||
|
drug = get_random("Item", filters={"item_group":"Drug"})
|
||||||
|
drug = frappe.get_doc("Item", drug)
|
||||||
|
drug_rx = consultation.append("drug_prescription")
|
||||||
|
drug_rx.drug_code = drug.item_code
|
||||||
|
drug_rx.drug_name = drug.item_name
|
||||||
|
drug_rx.dosage = get_random("Prescription Dosage")
|
||||||
|
drug_rx.period = get_random("Prescription Duration")
|
||||||
|
i += 1
|
||||||
|
return consultation
|
||||||
|
|
||||||
|
def random_date(start,l):
|
||||||
|
current = start
|
||||||
|
while l >= 0:
|
||||||
|
curr = current + datetime.timedelta(minutes=60)
|
||||||
|
yield curr
|
||||||
|
l-=1
|
||||||
|
|
||||||
|
def import_drug():
|
||||||
|
frappe.flags.in_import = True
|
||||||
|
data = json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data', 'drug_list.json')).read())
|
||||||
|
for d in data:
|
||||||
|
doc = frappe.new_doc("Item")
|
||||||
|
doc.update(d)
|
||||||
|
doc.insert()
|
||||||
|
frappe.flags.in_import = False
|
||||||
|
|
||||||
|
def get_json_path(doctype):
|
||||||
|
return frappe.get_app_path('erpnext', 'demo', 'data', frappe.scrub(doctype) + '.json')
|
@ -184,7 +184,8 @@ def setup_user_roles():
|
|||||||
user.add_roles('HR User', 'HR Manager', 'Accounts User', 'Accounts Manager',
|
user.add_roles('HR User', 'HR Manager', 'Accounts User', 'Accounts Manager',
|
||||||
'Stock User', 'Stock Manager', 'Sales User', 'Sales Manager', 'Purchase User',
|
'Stock User', 'Stock Manager', 'Sales User', 'Sales Manager', 'Purchase User',
|
||||||
'Purchase Manager', 'Projects User', 'Manufacturing User', 'Manufacturing Manager',
|
'Purchase Manager', 'Projects User', 'Manufacturing User', 'Manufacturing Manager',
|
||||||
'Support Team', 'Academics User')
|
'Support Team', 'Academics User', 'Physician', 'Healthcare Administrator', 'Laboratory User',
|
||||||
|
'Nursing User', 'Patient')
|
||||||
|
|
||||||
if not frappe.db.get_global('demo_hr_user'):
|
if not frappe.db.get_global('demo_hr_user'):
|
||||||
user = frappe.get_doc('User', 'CharmaineGaudreau@example.com')
|
user = frappe.get_doc('User', 'CharmaineGaudreau@example.com')
|
||||||
@ -387,5 +388,3 @@ def import_json(doctype, submit=False, values=None):
|
|||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
|
|
||||||
frappe.flags.in_import = False
|
frappe.flags.in_import = False
|
||||||
|
|
||||||
|
|
||||||
|
BIN
erpnext/docs/assets/img/healthcare/._.DS_Store
Executable file
BIN
erpnext/docs/assets/img/healthcare/._appointment_1.png
Executable file
BIN
erpnext/docs/assets/img/healthcare/._appointment_2.png
Executable file
BIN
erpnext/docs/assets/img/healthcare/._appointment_3.png
Executable file
BIN
erpnext/docs/assets/img/healthcare/._consultation_1.png
Executable file
BIN
erpnext/docs/assets/img/healthcare/._consultation_2.png
Executable file
BIN
erpnext/docs/assets/img/healthcare/._consultation_3.png
Executable file
BIN
erpnext/docs/assets/img/healthcare/._consultation_4.png
Executable file
BIN
erpnext/docs/assets/img/healthcare/._home.png
Executable file
BIN
erpnext/docs/assets/img/healthcare/._lab_test_1.png
Executable file
BIN
erpnext/docs/assets/img/healthcare/._lab_test_2.png
Executable file
BIN
erpnext/docs/assets/img/healthcare/._medical_code_1.png
Executable file
BIN
erpnext/docs/assets/img/healthcare/._medical_record_1.png
Executable file
BIN
erpnext/docs/assets/img/healthcare/._medical_record_2.png
Executable file
BIN
erpnext/docs/assets/img/healthcare/._module.png
Executable file
BIN
erpnext/docs/assets/img/healthcare/._patient_1.png
Executable file
BIN
erpnext/docs/assets/img/healthcare/._patient_2.png
Executable file
BIN
erpnext/docs/assets/img/healthcare/._patient_3.png
Executable file
BIN
erpnext/docs/assets/img/healthcare/._physician_1.png
Executable file
BIN
erpnext/docs/assets/img/healthcare/._physician_2.png
Executable file
BIN
erpnext/docs/assets/img/healthcare/._physician_schedule_1.png
Executable file
BIN
erpnext/docs/assets/img/healthcare/._physician_schedule_2.png
Executable file
BIN
erpnext/docs/assets/img/healthcare/._sample_collection_1.png
Executable file
BIN
erpnext/docs/assets/img/healthcare/._vitals_1.png
Executable file
BIN
erpnext/docs/assets/img/healthcare/._vitals_2.png
Executable file
0
erpnext/docs/assets/img/healthcare/__init__.py
Executable file
BIN
erpnext/docs/assets/img/healthcare/appointment_1.png
Executable file
After Width: | Height: | Size: 28 KiB |
BIN
erpnext/docs/assets/img/healthcare/appointment_2.png
Executable file
After Width: | Height: | Size: 64 KiB |
BIN
erpnext/docs/assets/img/healthcare/appointment_3.png
Executable file
After Width: | Height: | Size: 26 KiB |
BIN
erpnext/docs/assets/img/healthcare/consultation_1.png
Executable file
After Width: | Height: | Size: 98 KiB |
BIN
erpnext/docs/assets/img/healthcare/consultation_2.png
Executable file
After Width: | Height: | Size: 24 KiB |
BIN
erpnext/docs/assets/img/healthcare/consultation_3.png
Executable file
After Width: | Height: | Size: 40 KiB |
BIN
erpnext/docs/assets/img/healthcare/consultation_4.png
Executable file
After Width: | Height: | Size: 22 KiB |
BIN
erpnext/docs/assets/img/healthcare/home.png
Executable file
After Width: | Height: | Size: 73 KiB |
BIN
erpnext/docs/assets/img/healthcare/lab_test_1.png
Executable file
After Width: | Height: | Size: 97 KiB |
BIN
erpnext/docs/assets/img/healthcare/lab_test_2.png
Executable file
After Width: | Height: | Size: 56 KiB |
BIN
erpnext/docs/assets/img/healthcare/medical_code_1.png
Executable file
After Width: | Height: | Size: 127 KiB |
BIN
erpnext/docs/assets/img/healthcare/medical_record_1.png
Executable file
After Width: | Height: | Size: 114 KiB |
BIN
erpnext/docs/assets/img/healthcare/medical_record_2.png
Executable file
After Width: | Height: | Size: 58 KiB |
BIN
erpnext/docs/assets/img/healthcare/module.png
Executable file
After Width: | Height: | Size: 110 KiB |
BIN
erpnext/docs/assets/img/healthcare/patient_1.png
Executable file
After Width: | Height: | Size: 104 KiB |
BIN
erpnext/docs/assets/img/healthcare/patient_2.png
Executable file
After Width: | Height: | Size: 73 KiB |
BIN
erpnext/docs/assets/img/healthcare/patient_3.png
Executable file
After Width: | Height: | Size: 59 KiB |
BIN
erpnext/docs/assets/img/healthcare/physician_1.png
Executable file
After Width: | Height: | Size: 96 KiB |
BIN
erpnext/docs/assets/img/healthcare/physician_2.png
Executable file
After Width: | Height: | Size: 88 KiB |
BIN
erpnext/docs/assets/img/healthcare/physician_schedule_1.png
Executable file
After Width: | Height: | Size: 55 KiB |
BIN
erpnext/docs/assets/img/healthcare/physician_schedule_2.png
Executable file
After Width: | Height: | Size: 71 KiB |
BIN
erpnext/docs/assets/img/healthcare/sample_collection_1.png
Executable file
After Width: | Height: | Size: 97 KiB |
BIN
erpnext/docs/assets/img/healthcare/vitals_1.png
Executable file
After Width: | Height: | Size: 86 KiB |
BIN
erpnext/docs/assets/img/healthcare/vitals_2.png
Executable file
After Width: | Height: | Size: 27 KiB |
@ -11,7 +11,7 @@ You can insert Custom Link Field by following steps below.
|
|||||||
|
|
||||||
####Step 2: Select Form
|
####Step 2: Select Form
|
||||||
|
|
||||||
In Customize Form, select Document Type (Quotation, Sales Order, Purchase Invoice Item etc.). Once field are updated in table, open field before which you wish to insert Custom Field. Then click on "Insert Above" to insert new Custom Field.
|
In Customize Form, select Document Type (Quotation, Sales Order, Purchase Invoice Item etc.). Once fields are updated in the accompanying table below, open a field above the one you wish to insert your Custom Field. Then click on "Insert Above" to insert the new Custom Field.
|
||||||
|
|
||||||
<img alt="Select Docytpe" class="screenshot" src="/docs/assets/img/articles/link-field-1.gif">
|
<img alt="Select Docytpe" class="screenshot" src="/docs/assets/img/articles/link-field-1.gif">
|
||||||
|
|
||||||
|
BIN
erpnext/docs/user/manual/en/healthcare/._.DS_Store
Executable file
0
erpnext/docs/user/manual/en/healthcare/__init__.py
Executable file
38
erpnext/docs/user/manual/en/healthcare/appointment.md
Executable file
@ -0,0 +1,38 @@
|
|||||||
|
# Patient Appointment
|
||||||
|
ERPNext Healthcare allows you to book Patient appointments for any date and if configured, send them alerts via Email or SMS.
|
||||||
|
|
||||||
|
You can create a Patient Appointment from
|
||||||
|
> Healthcare > Patient Appointment > New Patient Appointment
|
||||||
|
|
||||||
|
You can book appointments for a registered Patient by searching and selecting the Patient field. You can search the Patient by Patient ID, Name, Email or Mobile number. You can also register a new Patient from the Appointment screen by selecting "Create a new patient" in the Patient field.
|
||||||
|
|
||||||
|
<img class="screenshot" alt="ERPNext Healthcare" src="/docs/assets/img/healthcare/appointment_1.png">
|
||||||
|
|
||||||
|
If you have a front desk executive to manage your appointments, you can configure a user role to have access to Patient Appointment so that she can do the bookings by selecting the Physician whom the Patient wish to consult and the date for booking. "Check Availability" button will pop up all the available time slots with status indicators for the date. She can select a time slot and "Book" the Appointment for the Patient.
|
||||||
|
|
||||||
|
<img class="screenshot" alt="ERPNext Healthcare" src="/docs/assets/img/healthcare/appointment_2.png">
|
||||||
|
|
||||||
|
After Booking, the scheduled time of the Appointment and duration will be updated and seved in the document.
|
||||||
|
|
||||||
|
<img class="screenshot" alt="ERPNext Healthcare" src="/docs/assets/img/healthcare/appointment_3.png">
|
||||||
|
|
||||||
|
You can configure ERPNext to send an SMS alert to the Patient about the booking confirmation or a reminder on the day of Appointment by doing necessary configurations in -
|
||||||
|
|
||||||
|
> Healthcare > Healthcare Settings > Out Patient SMS Alerts
|
||||||
|
|
||||||
|
The screen also allows the executive to select a Referring Physician so that you can track the source the appointment.
|
||||||
|
|
||||||
|
### Actions
|
||||||
|
* Billing: If you collect the consultation fee while booking the Appointment itself you can do so by using the "Create > Invoice" button. This will take you to the ERPNext Accounts Sales Invoice screen.
|
||||||
|
|
||||||
|
* Vital Signs: "Create > Vital Signs" button will take you to the new Vital Signs screen to record the vitals of the Patient.
|
||||||
|
|
||||||
|
* Consultation: From the Appointment screen you can directly create a Consultation to record the details of patient encounter.
|
||||||
|
|
||||||
|
* View Patient Medical Record.
|
||||||
|
|
||||||
|
> Note: User should have privileges (User Role) to view the buttons
|
||||||
|
|
||||||
|
A Patient can also book an appointment with a Physician by checking the Physician's availability directly through the **ERPNext Portal**.
|
||||||
|
|
||||||
|
{next}
|
28
erpnext/docs/user/manual/en/healthcare/consultation.md
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
# Consultation
|
||||||
|
ERPNext Healthcare allows you to record Patient encounters through the Consultation document. You can create a Consultation based on a previously booked Appointment or directly by creating a new Consultation
|
||||||
|
>Healthcare > Consultation > Consultation
|
||||||
|
|
||||||
|
If you are creating the Consultation document from an Appointment, Patient and other related data will automatically be populated else you can search the Patient by name, email phone number etc. The Patient Details section will list the latest Vital Signs record of the patient and other information captured in the Patient screen.
|
||||||
|
|
||||||
|
<img class="screenshot" alt="ERPNext Healthcare" src="/docs/assets/img/healthcare/consultation_1.png">
|
||||||
|
|
||||||
|
### Assessment
|
||||||
|
|
||||||
|
Encounter Impression section allows you to select (or create new) Complaints and your assessment based on the presented complaints. You can opt to include the captured data in Consultation print by selecting the "In Print" flag
|
||||||
|
|
||||||
|
<img class="screenshot" alt="ERPNext Healthcare" src="/docs/assets/img/healthcare/consultation_2.png">
|
||||||
|
|
||||||
|
### Prescriptions
|
||||||
|
|
||||||
|
You can prescribe medicines in the Drug Prescription section by selecting the drug codes (Stock Item) and appropriate dosages. If you are not managing Stock and Items are not configured, you can simply enter the Medicine name and strength in the Strength field which will printed.
|
||||||
|
|
||||||
|
Prescribing a laboratory investigation is similar and if you have Lab Tests configured, you can select from the list. Or key in the Lab Test name to be printed as part of the Prescription.
|
||||||
|
|
||||||
|
<img class="screenshot" alt="ERPNext Healthcare" src="/docs/assets/img/healthcare/consultation_3.png">
|
||||||
|
|
||||||
|
### Medical Coding
|
||||||
|
You can also attach one or more Medical Codes to designate the Diagnosis in the Medical Coding Section. You will have to select the Medical Code Standard you wish to encode the diagnosis and then select the Code by searching the Code itself or the Code Description.
|
||||||
|
|
||||||
|
<img class="screenshot" alt="ERPNext Healthcare" src="/docs/assets/img/healthcare/consultation_4.png">
|
||||||
|
|
||||||
|
{next}
|
13
erpnext/docs/user/manual/en/healthcare/index.md
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
# Healthcare
|
||||||
|
|
||||||
|
ERPNext Healthcare helps you manage your Clinic or Practice efficiently by scheduling **Appointments** and recording **Patient Encounters** (Consultations). You can easily pull out a **Patient's Health Record** anytime to review all the history of treatments assisting you in providing effective, high quality care.
|
||||||
|
|
||||||
|
<img class="screenshot" alt="ERPNext Healthcare" src="/docs/assets/img/healthcare/module.png">
|
||||||
|
|
||||||
|
Patients can view various documents relevant to them and book Appointments via the **ERPNext Portal**. The healthcare module is integrated with **Accounts** and **Human Resources** modules, helping you in **Billing**, **Payroll Management** etc. and benefit from other rich features of ERPNext. You can configure the **Selling** and **Stock** modules manage your Pharmacy.
|
||||||
|
|
||||||
|
ERPNext Healthcare also includes features for effectively managing the functions of an associated **Laboratory** by helping you record **Sample Collection**, emailing and printing **Lab Test** results etc. ERPNext Healthcare allows you to upload **Medical Code Standards** like **ICD10** and attach to Consultations.
|
||||||
|
|
||||||
|
### Topics
|
||||||
|
|
||||||
|
{index}
|
12
erpnext/docs/user/manual/en/healthcare/index.txt
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
patient
|
||||||
|
appointment
|
||||||
|
vital_signs
|
||||||
|
consultation
|
||||||
|
medical_record
|
||||||
|
sample_collection
|
||||||
|
lab_test
|
||||||
|
invoicing
|
||||||
|
physician
|
||||||
|
physician_schedule
|
||||||
|
medical_codes
|
||||||
|
setup
|
8
erpnext/docs/user/manual/en/healthcare/invoicing.md
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
# Invoicing
|
||||||
|
Billing is an integral part of any undertaking and ERPNext Healthcare achieves this by making use of the ERPNext Accounts module.
|
||||||
|
|
||||||
|
> Note: All transactions of a Patient is booked against the Customer which it is linked to.
|
||||||
|
|
||||||
|
All ERPNext Healthcare documents which require Invoicing will have buttons which would take you to the Sales Invoice with the Items configured for the service. You can then proceed by following the ERPNExt Accounts module workflows. Please note that your User account should have appropriate privileges to access the Accounts documents.
|
||||||
|
|
||||||
|
{next}
|
22
erpnext/docs/user/manual/en/healthcare/lab_test.md
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
# Lab Test
|
||||||
|
|
||||||
|
ERPNext Healthcare allows you to manage a clinical laboratory efficiently by allowing you to enter Lab Tests and print or email test results, manage samples collected, create Invoice etc. ERPNext Healthcare comes pre-packed with some sample tests, you can reconfigure Lab Test Templates for each Test and its result format or crate new ones. You can do this in
|
||||||
|
>Healthcare > Setup > Lab Text Templates
|
||||||
|
|
||||||
|
Once you have all necessary Lab Test Templates configured, you can start creating Lab Tests by selecting a Test Template every time you create a Test. To create a new Lab Test
|
||||||
|
>Healthcare > Laboratory > Lab Test > New Lab Test
|
||||||
|
|
||||||
|
<img class="screenshot" alt="ERPNext Healthcare" src="/docs/assets/img/healthcare/lab_test_1.png">
|
||||||
|
|
||||||
|
You can record the test results in the Lab Test document as the results gets ready.
|
||||||
|
|
||||||
|
<img class="screenshot" alt="ERPNext Healthcare" src="/docs/assets/img/healthcare/lab_test_2.png">
|
||||||
|
|
||||||
|
> Note: To create Sample Collection documents for every Lab Test, check "Manage Sample Collection" flag in Healthcare Settings and select Sample in the Lab Test Template
|
||||||
|
|
||||||
|
In many Laboratories, approval of Lab Tests is a must before printing and submitting the document. ERPNext Healthcare allows you to create Users with Role "Lab Test Approver" for this. You will also have to enable this in
|
||||||
|
>Healthcare Settings > Laboratory Settings > Require Lab Test Approval
|
||||||
|
|
||||||
|
This will ensure that emailing or printing of Lab Tests can only be done after Approval of the Lab Test by the Lab Test Approver.
|
||||||
|
|
||||||
|
{next}
|
9
erpnext/docs/user/manual/en/healthcare/medical_codes.md
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
# Medical Code Standards
|
||||||
|
Medical Coding are in many countries required for regulatory compliance and many of the Medical Insurance companies do that pricing based on Medical Code standards. ERPNext Healthcare offers support, however limited, to encode diagnosis and assessments recorded as part of Consultation. This can be done if you configure the Medical Code Standard and related Medical Codes - this is easily done by data import as the code data tends to be quite large. You can create as many Medical Code Standards you wish
|
||||||
|
> Healthcare > Masters > Medical Code Standard
|
||||||
|
|
||||||
|
Medical Code Standard document is used to name the Code Standard and act as a container for all the medical codes which are standardized under it. Medical Codes and descriptions can then be imported to the Medical Code document, after ensuring that you set the Medical Code Standard field to the appropriate Standard name.
|
||||||
|
|
||||||
|
<img class="screenshot" alt="ERPNext Healthcare" src="/docs/assets/img/healthcare/medical_code_1.png">
|
||||||
|
|
||||||
|
{next}
|
13
erpnext/docs/user/manual/en/healthcare/medical_record.md
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
# Patient Medical Record
|
||||||
|
The maintenance of complete and accurate medical records is a requirement of healthcare providers and is critical in rendering effective, high quality care. ERPNext Healthcare allows you to draw up the treatment history of a Patient anytime by merely selecting the Patient. "Medical Record" button is available in various screens so that you can easily switch to the Medical Record page to view the patient history.
|
||||||
|
|
||||||
|
Medical Record automatically keeps track of all Consultations, recorded Vital Signs, Lab Investigations etc. Complaints, Diagnosis etc. captured as part of consultation are easily viewable but to look at the details of other documents, links are provided.
|
||||||
|
|
||||||
|
<img class="screenshot" alt="ERPNext Healthcare" src="/docs/assets/img/healthcare/medical_record_1.png">
|
||||||
|
|
||||||
|
##### Adding notes manually to Medical Record
|
||||||
|
In the Patient screen Create > Medical Record will allow you to record notes to the Medical Record manually. You can also attach files when doing this, and the Medical Record will display links to the attached file along side the notes. Create > Medical Record button is also made available in the Consultation screen
|
||||||
|
|
||||||
|
<img class="screenshot" alt="ERPNext Healthcare" src="/docs/assets/img/healthcare/medical_record_2.png">
|
||||||
|
|
||||||
|
{next}
|