Merge branch 'develop' into payment-terms

This commit is contained in:
tunde 2017-09-19 18:07:12 +01:00
commit 3387d026b1
491 changed files with 32603 additions and 2749 deletions

View File

@ -51,11 +51,14 @@ before_script:
- bench start & - bench start &
- sleep 10 - sleep 10
script: jobs:
- set -e include:
- bench run-tests - stage: test
- sleep 5 script:
- bench reinstall --yes - set -e
- bench --verbose run-setup-wizard-ui-test - bench run-tests
- bench execute erpnext.setup.utils.enable_all_roles_and_domains - # stage
- bench run-ui-tests --app erpnext script:
- bench --verbose run-setup-wizard-ui-test
- bench execute erpnext.setup.utils.enable_all_roles_and_domains
- bench run-ui-tests --app erpnext

View File

@ -4,7 +4,7 @@ import inspect
import frappe import frappe
from erpnext.hooks import regional_overrides from erpnext.hooks import regional_overrides
__version__ = '8.11.2' __version__ = '8.11.4'
def get_default_company(user=None): def get_default_company(user=None):
'''Get default company for user''' '''Get default company for user'''

View File

@ -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)

View File

@ -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,19 +211,13 @@ 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) {
{ frm.events.party(frm);
if (frm.doc.payment_type=="Receive"){
frm.set_value("party_type", "Customer");
}
}
else
{
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);
}
} }
}, },
@ -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);

View File

@ -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,15 +211,17 @@ 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":
ref_party_account = ref_doc.payable_account ref_party_account = ref_doc.payable_account
if ref_party_account != self.party_account: if ref_party_account != self.party_account:
frappe.throw(_("{0} {1} is associated with {2}, but Party Account is {3}") frappe.throw(_("{0} {1} is associated with {2}, but Party Account is {3}")
.format(d.reference_doctype, d.reference_name, ref_party_account, self.party_account)) .format(d.reference_doctype, d.reference_name, ref_party_account, self.party_account))
@ -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()
@ -484,9 +488,14 @@ class PaymentEntry(AccountsController):
doc = frappe.get_doc("Expense Claim", d.reference_name) doc = frappe.get_doc("Expense Claim", d.reference_name)
update_reimbursed_amount(doc) update_reimbursed_amount(doc)
def on_recurring(self, reference_doc, subscription_doc):
self.reference_no = reference_doc.name
self.reference_date = nowdate()
@frappe.whitelist() @frappe.whitelist()
def get_outstanding_reference_documents(args): def get_outstanding_reference_documents(args):
args = json.loads(args) if isinstance(args, basestring):
args = json.loads(args)
party_account_currency = get_account_currency(args.get("party_account")) party_account_currency = get_account_currency(args.get("party_account"))
company_currency = frappe.db.get_value("Company", args.get("company"), "default_currency") company_currency = frappe.db.get_value("Company", args.get("company"), "default_currency")
@ -494,10 +503,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 = get_negative_outstanding_invoices(args.get("party_type"), negative_outstanding_invoices = []
args.get("party"), args.get("party_account"), total_field) if (args.get("party_type") != "Student"):
negative_outstanding_invoices = get_negative_outstanding_invoices(args.get("party_type"),
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"))
@ -510,10 +521,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
@ -628,7 +643,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
@ -671,19 +690,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:
@ -699,6 +722,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))

View File

@ -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()
]); ]);

View File

@ -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'),

View File

@ -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",

View File

@ -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

View File

@ -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,

View File

@ -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
@ -604,6 +606,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()

View File

@ -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,

View File

@ -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
@ -804,6 +806,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"):

View File

@ -224,12 +224,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",
@ -294,7 +294,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"):
@ -303,10 +303,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",
@ -336,7 +338,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:
@ -432,13 +435,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"):
@ -446,28 +448,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
@ -495,7 +497,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
}, },
@ -509,7 +511,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
} }
@ -545,8 +547,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()
@ -1313,6 +1318,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)

View File

@ -0,0 +1,77 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Subscription', {
setup: function(frm) {
if(frm.doc.__islocal) {
var last_route = frappe.route_history.slice(-2, -1)[0];
if(frappe.dynamic_link && frappe.dynamic_link.doc
&& frappe.dynamic_link.doc.name==last_route[2]) {
frm.set_value('reference_doctype', last_route[1]);
frm.set_value('reference_document', last_route[2]);
}
}
frm.fields_dict['reference_document'].get_query = function() {
return {
filters: {
"docstatus": 1
}
};
};
frm.fields_dict['print_format'].get_query = function() {
return {
filters: {
"doc_type": frm.doc.reference_doctype
}
};
};
},
refresh: function(frm) {
if(frm.doc.docstatus == 1) {
let label = __('View {0}', [frm.doc.reference_doctype]);
frm.add_custom_button(__(label),
function() {
frappe.route_options = {
"subscription": frm.doc.name,
};
frappe.set_route("List", frm.doc.reference_doctype);
}
);
if(frm.doc.status != 'Stopped') {
frm.add_custom_button(__("Stop"),
function() {
frm.events.stop_resume_subscription(frm, "Stopped");
}
);
}
if(frm.doc.status == 'Stopped') {
frm.add_custom_button(__("Resume"),
function() {
frm.events.stop_resume_subscription(frm, "Resumed");
}
);
}
}
},
stop_resume_subscription: function(frm, status) {
frappe.call({
method: "erpnext.accounts.doctype.subscription.subscription.stop_resume_subscription",
args: {
subscription: frm.doc.name,
status: status
},
callback: function(r) {
if(r.message) {
frm.set_value("status", r.message);
frm.reload_doc();
}
}
});
}
});

View File

@ -148,7 +148,7 @@
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 1,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Disabled", "label": "Disabled",
"length": 0, "length": 0,
@ -619,24 +619,24 @@
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 1,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "Draft", "default": "Draft",
"fieldname": "status", "fieldname": "status",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 1, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 1,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Status", "label": "Status",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "\nDraft\nSubmitted\nCancelled\nCompleted", "options": "\nDraft\nStopped\nSubmitted\nCancelled\nCompleted",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "print_hide": 0,
@ -690,9 +690,9 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-08-29 15:45:16.157643", "modified": "2017-09-14 12:09:38.471458",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Subscription", "module": "Accounts",
"name": "Subscription", "name": "Subscription",
"name_case": "", "name_case": "",
"owner": "Administrator", "owner": "Administrator",
@ -700,7 +700,7 @@
{ {
"amend": 0, "amend": 0,
"apply_user_permissions": 0, "apply_user_permissions": 0,
"cancel": 1, "cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
@ -716,6 +716,46 @@
"share": 1, "share": 1,
"submit": 1, "submit": 1,
"write": 1 "write": 1
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
} }
], ],
"quick_entry": 0, "quick_entry": 0,

View File

@ -71,13 +71,16 @@ class Subscription(Document):
doc.db_set('subscription', self.name) doc.db_set('subscription', self.name)
def update_status(self): def update_status(self, status=None):
self.status = { self.status = {
'0': 'Draft', '0': 'Draft',
'1': 'Submitted', '1': 'Submitted',
'2': 'Cancelled' '2': 'Cancelled'
}[cstr(self.docstatus or 0)] }[cstr(self.docstatus or 0)]
if status and status != 'Resumed':
self.status = status
def get_next_schedule_date(start_date, frequency, repeat_on_day): def get_next_schedule_date(start_date, frequency, repeat_on_day):
mcount = month_map.get(frequency) mcount = month_map.get(frequency)
if mcount: if mcount:
@ -93,11 +96,10 @@ def make_subscription_entry(date=None):
schedule_date = getdate(data.next_schedule_date) schedule_date = getdate(data.next_schedule_date)
while schedule_date <= getdate(today()): while schedule_date <= getdate(today()):
create_documents(data, schedule_date) create_documents(data, schedule_date)
schedule_date = get_next_schedule_date(schedule_date, schedule_date = get_next_schedule_date(schedule_date,
data.frequency, data.repeat_on_day) data.frequency, data.repeat_on_day)
if schedule_date: if schedule_date and not frappe.db.get_value('Subscription', data.name, 'disabled'):
frappe.db.set_value('Subscription', data.name, 'next_schedule_date', schedule_date) frappe.db.set_value('Subscription', data.name, 'next_schedule_date', schedule_date)
def get_subscription_entries(date): def get_subscription_entries(date):
@ -105,23 +107,29 @@ def get_subscription_entries(date):
where docstatus = 1 and next_schedule_date <=%s where docstatus = 1 and next_schedule_date <=%s
and reference_document is not null and reference_document != '' and reference_document is not null and reference_document != ''
and next_schedule_date <= ifnull(end_date, '2199-12-31') and next_schedule_date <= ifnull(end_date, '2199-12-31')
and ifnull(disabled, 0) = 0""", (date), as_dict=1) and ifnull(disabled, 0) = 0 and status != 'Stopped' """, (date), as_dict=1)
def create_documents(data, schedule_date): def create_documents(data, schedule_date):
try: try:
doc = make_new_document(data, schedule_date) doc = make_new_document(data, schedule_date)
if data.notify_by_email: if data.notify_by_email and data.recipients:
send_notification(doc, data.print_format, data.recipients) print_format = data.print_format or "Standard"
send_notification(doc, print_format, data.recipients)
frappe.db.commit() frappe.db.commit()
except Exception: except Exception:
frappe.db.rollback() frappe.db.rollback()
frappe.db.begin() frappe.db.begin()
frappe.log_error(frappe.get_traceback()) frappe.log_error(frappe.get_traceback())
disabled_subscription(data)
frappe.db.commit() frappe.db.commit()
if data.reference_document and not frappe.flags.in_test: if data.reference_document and not frappe.flags.in_test:
notify_error_to_user(data) notify_error_to_user(data)
def disabled_subscription(data):
subscription = frappe.get_doc('Subscription', data.name)
subscription.db_set('disabled', 1)
def notify_error_to_user(data): def notify_error_to_user(data):
party = '' party = ''
party_type = '' party_type = ''
@ -134,7 +142,7 @@ def notify_error_to_user(data):
if party_type: if party_type:
party = frappe.db.get_value(data.reference_doctype, data.reference_document, party_type) party = frappe.db.get_value(data.reference_doctype, data.reference_document, party_type)
notify_errors(data.reference_document, data.reference_doctype, party, data.owner) notify_errors(data.reference_document, data.reference_doctype, party, data.owner, data.name)
def make_new_document(args, schedule_date): def make_new_document(args, schedule_date):
doc = frappe.get_doc(args.reference_doctype, args.reference_document) doc = frappe.get_doc(args.reference_doctype, args.reference_document)
@ -168,32 +176,32 @@ def get_next_date(dt, mcount, day=None):
def send_notification(new_rv, print_format='Standard', recipients=None): def send_notification(new_rv, print_format='Standard', recipients=None):
"""Notify concerned persons about recurring document generation""" """Notify concerned persons about recurring document generation"""
recipients = recipients or new_rv.notification_email_address print_format = print_format
print_format = print_format or new_rv.recurring_print_format
frappe.sendmail(recipients, frappe.sendmail(recipients,
subject= _("New {0}: #{1}").format(new_rv.doctype, new_rv.name), subject= _("New {0}: #{1}").format(new_rv.doctype, new_rv.name),
message = _("Please find attached {0} #{1}").format(new_rv.doctype, new_rv.name), message = _("Please find attached {0} #{1}").format(new_rv.doctype, new_rv.name),
attachments = [frappe.attach_print(new_rv.doctype, new_rv.name, file_name=new_rv.name, print_format=print_format)]) attachments = [frappe.attach_print(new_rv.doctype, new_rv.name, file_name=new_rv.name, print_format=print_format)])
def notify_errors(doc, doctype, party, owner): def notify_errors(doc, doctype, party, owner, name):
recipients = get_system_managers(only_name=True) recipients = get_system_managers(only_name=True)
frappe.sendmail(recipients + [frappe.db.get_value("User", owner, "email")], frappe.sendmail(recipients + [frappe.db.get_value("User", owner, "email")],
subject="[Urgent] Error while creating recurring %s for %s" % (doctype, doc), subject=_("[Urgent] Error while creating recurring %s for %s" % (doctype, doc)),
message = frappe.get_template("templates/emails/recurring_document_failed.html").render({ message = frappe.get_template("templates/emails/recurring_document_failed.html").render({
"type": doctype, "type": _(doctype),
"name": doc, "name": doc,
"party": party or "" "party": party or "",
"subscription": name
})) }))
assign_task_to_owner(doc, doctype, "Recurring Invoice Failed", recipients) assign_task_to_owner(name, "Recurring Documents Failed", recipients)
def assign_task_to_owner(doc, doctype, msg, users): def assign_task_to_owner(name, msg, users):
for d in users: for d in users:
args = { args = {
'doctype' : 'Subscription',
'assign_to' : d, 'assign_to' : d,
'doctype' : doctype, 'name' : name,
'name' : doc,
'description' : msg, 'description' : msg,
'priority' : 'High' 'priority' : 'High'
} }
@ -205,3 +213,16 @@ def make_subscription(doctype, docname):
doc.reference_doctype = doctype doc.reference_doctype = doctype
doc.reference_document = docname doc.reference_document = docname
return doc return doc
@frappe.whitelist()
def stop_resume_subscription(subscription, status):
doc = frappe.get_doc('Subscription', subscription)
frappe.msgprint(_("Subscription has been {0}").format(status))
if status == 'Resumed':
doc.next_schedule_date = get_next_schedule_date(today(),
doc.frequency, doc.repeat_on_day)
doc.update_status(status)
doc.save()
return doc.status

View File

@ -1,10 +1,14 @@
frappe.listview_settings['Subscription'] = { frappe.listview_settings['Subscription'] = {
add_fields: ["next_schedule_date"], add_fields: ["next_schedule_date"],
get_indicator: function(doc) { get_indicator: function(doc) {
if(doc.next_schedule_date >= frappe.datetime.get_today() ) { if(doc.disabled) {
return [__("Disabled"), "red"];
} else if(doc.next_schedule_date >= frappe.datetime.get_today() && doc.status != 'Stopped') {
return [__("Active"), "green"]; return [__("Active"), "green"];
} else if(doc.docstatus === 0) { } else if(doc.docstatus === 0) {
return [__("Draft"), "red", "docstatus,=,0"]; return [__("Draft"), "red", "docstatus,=,0"];
} else if(doc.status === 'Stopped') {
return [__("Stopped"), "red"];
} else { } else {
return [__("Expired"), "darkgrey"]; return [__("Expired"), "darkgrey"];
} }

View File

@ -10,7 +10,7 @@ from erpnext.accounts.utils import get_fiscal_year
from erpnext.accounts.report.financial_statements import get_months from erpnext.accounts.report.financial_statements import get_months
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from erpnext.subscription.doctype.subscription.subscription import make_subscription_entry from erpnext.accounts.doctype.subscription.subscription import make_subscription_entry
class TestSubscription(unittest.TestCase): class TestSubscription(unittest.TestCase):
def test_daily_subscription(self): def test_daily_subscription(self):

View File

@ -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:

View File

@ -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):

View File

@ -137,14 +137,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",
@ -166,6 +159,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"):

View File

@ -269,9 +269,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
this.calculate_outstanding_amount(); this.calculate_outstanding_amount();
} }
if (this.frm.doc.customer) { this.set_customer_value_in_party_field();
this.party_field.$input.val(this.frm.doc.customer);
}
if (!this.frm.doc.write_off_account) { if (!this.frm.doc.write_off_account) {
this.frm.doc.write_off_account = doc.write_off_account this.frm.doc.write_off_account = doc.write_off_account
@ -282,6 +280,12 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
} }
}, },
set_customer_value_in_party_field: function() {
if (this.frm.doc.customer) {
this.party_field.$input.val(this.frm.doc.customer);
}
},
get_invoice_doc: function (si_docs) { get_invoice_doc: function (si_docs) {
var me = this; var me = this;
this.si_docs = this.get_doc_from_localstorage(); this.si_docs = this.get_doc_from_localstorage();
@ -695,6 +699,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
set_focus: function () { set_focus: function () {
if (this.default_customer || this.frm.doc.customer) { if (this.default_customer || this.frm.doc.customer) {
this.set_customer_value_in_party_field();
this.serach_item.$input.focus(); this.serach_item.$input.focus();
} else { } else {
this.party_field.$input.focus(); this.party_field.$input.focus();

View File

@ -1,5 +1,5 @@
{ {
"align_labels_left": 0, "align_labels_right": 0,
"creation": "2017-08-08 12:33:04.773099", "creation": "2017-08-08 12:33:04.773099",
"custom_format": 1, "custom_format": 1,
"disabled": 0, "disabled": 0,
@ -10,7 +10,7 @@
"html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Monospace;\n\t\tline-height: 200%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n<p class=\"text-center\">\n\t{{ doc.company }}<br>\n\t{% if doc.company_address_display %}\n\t\t{% set company_address = doc.company_address_display.replace(\"\\n\", \" \").replace(\"<br>\", \" \") %}\n\t\t{% if \"GSTIN\" not in company_address %}\n\t\t\t{{ company_address }}\n\t\t\t<b>{{ _(\"GSTIN\") }}:</b>{{ doc.company_gstin }}\n\t\t{% else %}\n\t\t\t{{ company_address.replace(\"GSTIN\", \"<br>GSTIN\") }}\n\t\t{% endif %}\n\t{% endif %}\n\t<br>\n\t<b>{{ doc.select_print_heading or _(\"Invoice\") }}</b><br>\n</p>\n<p>\n\t<b>{{ _(\"Receipt No\") }}:</b> {{ doc.name }}<br>\n\t<b>{{ _(\"Date\") }}:</b> {{ doc.get_formatted(\"posting_date\") }}<br>\n\t{% if doc.grand_total > 50000 %}\n\t\t{% set customer_address = doc.address_display.replace(\"\\n\", \" \").replace(\"<br>\", \" \") %}\n\t\t<b>{{ _(\"Customer\") }}:</b><br>\n\t\t{{ doc.customer_name }}<br>\n\t\t{{ customer_address }}\n\t{% endif %}\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"40%\">{{ _(\"Item\") }}</b></th>\n\t\t\t<th width=\"30%\" class=\"text-right\">{{ _(\"Qty\") }}</th>\n\t\t\t<th width=\"30%\" class=\"text-right\">{{ _(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{%- for item in doc.items -%}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t<br>{{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.gst_hsn_code -%}\n\t\t\t\t\t<br><b>{{ _(\"HSN/SAC\") }}:</b> {{ item.gst_hsn_code }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t<br><b>{{ _(\"Serial No\") }}:</b> {{ item.serial_no }}\n\t\t\t\t{%- endif -%}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ item.qty }}<br>@ {{ item.rate }}</td>\n\t\t\t<td class=\"text-right\">{{ item.get_formatted(\"amount\") }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t{{ _(\"Net Total\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"net_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- for row in doc.taxes -%}\n\t\t{%- if not row.included_in_print_rate -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t{{ row.description }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t{%- endfor -%}\n\t\t{%- if doc.discount_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t</tbody>\n</table>\n<p><b>Tax Breakup:</b></p>\n<div style=\"font-size: 8px\">\n\t{{ doc.other_charges_calculation }}\n</div>\n<p>{{ doc.terms or \"\" }}</p>\n<p class=\"text-center\">{{ _(\"Thank you, please visit again.\") }}</p>", "html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Monospace;\n\t\tline-height: 200%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n<p class=\"text-center\">\n\t{{ doc.company }}<br>\n\t{% if doc.company_address_display %}\n\t\t{% set company_address = doc.company_address_display.replace(\"\\n\", \" \").replace(\"<br>\", \" \") %}\n\t\t{% if \"GSTIN\" not in company_address %}\n\t\t\t{{ company_address }}\n\t\t\t<b>{{ _(\"GSTIN\") }}:</b>{{ doc.company_gstin }}\n\t\t{% else %}\n\t\t\t{{ company_address.replace(\"GSTIN\", \"<br>GSTIN\") }}\n\t\t{% endif %}\n\t{% endif %}\n\t<br>\n\t<b>{{ doc.select_print_heading or _(\"Invoice\") }}</b><br>\n</p>\n<p>\n\t<b>{{ _(\"Receipt No\") }}:</b> {{ doc.name }}<br>\n\t<b>{{ _(\"Date\") }}:</b> {{ doc.get_formatted(\"posting_date\") }}<br>\n\t{% if doc.grand_total > 50000 %}\n\t\t{% set customer_address = doc.address_display.replace(\"\\n\", \" \").replace(\"<br>\", \" \") %}\n\t\t<b>{{ _(\"Customer\") }}:</b><br>\n\t\t{{ doc.customer_name }}<br>\n\t\t{{ customer_address }}\n\t{% endif %}\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"40%\">{{ _(\"Item\") }}</b></th>\n\t\t\t<th width=\"30%\" class=\"text-right\">{{ _(\"Qty\") }}</th>\n\t\t\t<th width=\"30%\" class=\"text-right\">{{ _(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{%- for item in doc.items -%}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t<br>{{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.gst_hsn_code -%}\n\t\t\t\t\t<br><b>{{ _(\"HSN/SAC\") }}:</b> {{ item.gst_hsn_code }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t<br><b>{{ _(\"Serial No\") }}:</b> {{ item.serial_no }}\n\t\t\t\t{%- endif -%}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ item.qty }}<br>@ {{ item.rate }}</td>\n\t\t\t<td class=\"text-right\">{{ item.get_formatted(\"amount\") }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t{{ _(\"Net Total\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"net_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- for row in doc.taxes -%}\n\t\t{%- if not row.included_in_print_rate -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t{{ row.description }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t{%- endfor -%}\n\t\t{%- if doc.discount_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t</tbody>\n</table>\n<p><b>Tax Breakup:</b></p>\n<div style=\"font-size: 8px\">\n\t{{ doc.other_charges_calculation }}\n</div>\n<p>{{ doc.terms or \"\" }}</p>\n<p class=\"text-center\">{{ _(\"Thank you, please visit again.\") }}</p>",
"idx": 0, "idx": 0,
"line_breaks": 0, "line_breaks": 0,
"modified": "2017-08-29 15:54:19.467642", "modified": "2017-09-14 15:54:19.467642",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "GST POS Invoice", "name": "GST POS Invoice",

View File

@ -1,5 +1,5 @@
{ {
"align_labels_left": 0, "align_labels_right": 0,
"creation": "2016-05-05 17:16:18.564460", "creation": "2016-05-05 17:16:18.564460",
"custom_format": 1, "custom_format": 1,
"disabled": 0, "disabled": 0,
@ -10,7 +10,7 @@
"html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Monospace;\n\t\tline-height: 200%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n<p class=\"text-center\">\n\t{{ company }}<br>\n\t{{ __(\"POS No : \") }} {{ offline_pos_name }}<br>\n</p>\n<p>\n\t<b>{{ __(\"Customer\") }}:</b> {{ customer }}<br>\n</p>\n\n<p>\n\t<b>{{ __(\"Date\") }}:</b> {{ dateutil.global_date_format(posting_date) }}<br>\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ __(\"Item\") }}</b></th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ __(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ __(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{% for item in items %}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_name }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ format_number(item.qty, null,precision(\"difference\")) }}<br>@ {{ format_currency(item.rate, currency) }}</td>\n\t\t\t<td class=\"text-right\">{{ format_currency(item.amount, currency) }}</td>\n\t\t</tr>\n\t\t{% endfor %}\n\t</tbody>\n</table>\n\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t{{ __(\"Net Total\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(total, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% for row in taxes %}\n\t\t{% if not row.included_in_print_rate %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t{{ row.description }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(row.tax_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% endif %}\n\t\t{% endfor %}\n\t\t{% if discount_amount %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ __(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(discount_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% endif %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ __(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(grand_total, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ __(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(paid_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t</tbody>\n</table>\n\n\n<hr>\n<p>{{ terms }}</p>\n<p class=\"text-center\">{{ __(\"Thank you, please visit again.\") }}</p>", "html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Monospace;\n\t\tline-height: 200%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n<p class=\"text-center\">\n\t{{ company }}<br>\n\t{{ __(\"POS No : \") }} {{ offline_pos_name }}<br>\n</p>\n<p>\n\t<b>{{ __(\"Customer\") }}:</b> {{ customer }}<br>\n</p>\n\n<p>\n\t<b>{{ __(\"Date\") }}:</b> {{ dateutil.global_date_format(posting_date) }}<br>\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ __(\"Item\") }}</b></th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ __(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ __(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{% for item in items %}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_name }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ format_number(item.qty, null,precision(\"difference\")) }}<br>@ {{ format_currency(item.rate, currency) }}</td>\n\t\t\t<td class=\"text-right\">{{ format_currency(item.amount, currency) }}</td>\n\t\t</tr>\n\t\t{% endfor %}\n\t</tbody>\n</table>\n\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t{{ __(\"Net Total\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(total, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% for row in taxes %}\n\t\t{% if not row.included_in_print_rate %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t{{ row.description }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(row.tax_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% endif %}\n\t\t{% endfor %}\n\t\t{% if discount_amount %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ __(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(discount_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% endif %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ __(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(grand_total, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ __(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(paid_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t</tbody>\n</table>\n\n\n<hr>\n<p>{{ terms }}</p>\n<p class=\"text-center\">{{ __(\"Thank you, please visit again.\") }}</p>",
"idx": 0, "idx": 0,
"line_breaks": 0, "line_breaks": 0,
"modified": "2017-09-01 14:27:04.871233", "modified": "2017-09-14 14:36:04.740728",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Point of Sale", "name": "Point of Sale",

View File

@ -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

View File

@ -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,40 +143,68 @@ 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():
for i, row in enumerate(self.grouped[key]): if self.filters.get("group_by") != "Invoice":
if i==0: for i, row in enumerate(self.grouped[key]):
new_row = row if i==0:
else: new_row = row
new_row.qty += row.qty else:
new_row.buying_amount += row.buying_amount new_row.qty += row.qty
new_row.base_amount += row.base_amount new_row.buying_amount += row.buying_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)
new_row.gross_profit = new_row.base_amount - new_row.buying_amount def set_average_rate(self, new_row):
new_row.gross_profit_percent = ((new_row.gross_profit / new_row.base_amount) * 100.0) \ new_row.gross_profit = new_row.base_amount - new_row.buying_amount
if new_row.base_amount else 0 new_row.gross_profit_percent = ((new_row.gross_profit / new_row.base_amount) * 100.0) \
new_row.buying_rate = (new_row.buying_amount / new_row.qty) \ if new_row.base_amount else 0
if new_row.qty else 0 new_row.buying_rate = (new_row.buying_amount / new_row.qty) if new_row.qty else 0
new_row.base_rate = (new_row.base_amount / new_row.qty) \ new_row.base_rate = (new_row.base_amount / new_row.qty) if new_row.qty else 0
if new_row.qty else 0 return new_row
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,

View File

@ -91,10 +91,10 @@ def get_conditions(filters):
conditions = "" conditions = ""
for opts in (("company", " and company=%(company)s"), for opts in (("company", " and company=%(company)s"),
("supplier", " and pi.supplier = %(supplier)s"), ("supplier", " and `tabPurchase Invoice`.supplier = %(supplier)s"),
("item_code", " and pi_item.item_code = %(item_code)s"), ("item_code", " and `tabPurchase Invoice Item`.item_code = %(item_code)s"),
("from_date", " and pi.posting_date>=%(from_date)s"), ("from_date", " and `tabPurchase Invoice`.posting_date>=%(from_date)s"),
("to_date", " and pi.posting_date<=%(to_date)s"), ("to_date", " and `tabPurchase Invoice`.posting_date<=%(to_date)s"),
("mode_of_payment", " and ifnull(mode_of_payment, '') = %(mode_of_payment)s")): ("mode_of_payment", " and ifnull(mode_of_payment, '') = %(mode_of_payment)s")):
if filters.get(opts[0]): if filters.get(opts[0]):
conditions += opts[1] conditions += opts[1]
@ -104,20 +104,29 @@ def get_conditions(filters):
def get_items(filters, additional_query_columns): def get_items(filters, additional_query_columns):
conditions = get_conditions(filters) conditions = get_conditions(filters)
match_conditions = frappe.build_match_conditions("Purchase Invoice") match_conditions = frappe.build_match_conditions("Purchase Invoice")
if match_conditions:
match_conditions = " and {0} ".format(match_conditions)
if additional_query_columns: if additional_query_columns:
additional_query_columns = ', ' + ', '.join(additional_query_columns) additional_query_columns = ', ' + ', '.join(additional_query_columns)
return frappe.db.sql(""" return frappe.db.sql("""
select select
pi_item.name, pi_item.parent, pi.posting_date, pi.credit_to, pi.company, `tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`,
pi.supplier, pi.remarks, pi.base_net_total, pi_item.item_code, pi_item.item_name, `tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company,
pi_item.item_group, pi_item.project, pi_item.purchase_order, pi_item.purchase_receipt, `tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total, `tabPurchase Invoice Item`.`item_code`,
pi_item.po_detail, pi_item.expense_account, pi_item.stock_qty, pi_item.stock_uom, `tabPurchase Invoice Item`.`item_name`, `tabPurchase Invoice Item`.`item_group`,
pi_item.base_net_rate, pi_item.base_net_amount, `tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
pi.supplier_name, pi.mode_of_payment {0} `tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`,
from `tabPurchase Invoice` pi, `tabPurchase Invoice Item` pi_item `tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`,
where pi.name = pi_item.parent and pi.docstatus = 1 %s %s `tabPurchase Invoice Item`.`stock_uom`, `tabPurchase Invoice Item`.`base_net_rate`,
order by pi.posting_date desc, pi_item.item_code desc `tabPurchase Invoice Item`.`base_net_amount`,
`tabPurchase Invoice`.supplier_name, `tabPurchase Invoice`.mode_of_payment {0}
from `tabPurchase Invoice`, `tabPurchase Invoice Item`
where `tabPurchase Invoice`.name = `tabPurchase Invoice Item`.`parent` and
`tabPurchase Invoice`.docstatus = 1 %s %s
order by `tabPurchase Invoice`.posting_date desc, `tabPurchase Invoice Item`.item_code desc
""".format(additional_query_columns) % (conditions, match_conditions), filters, as_dict=1) """.format(additional_query_columns) % (conditions, match_conditions), filters, as_dict=1)
def get_aii_accounts(): def get_aii_accounts():

View File

@ -93,37 +93,49 @@ def get_conditions(filters):
conditions = "" conditions = ""
for opts in (("company", " and company=%(company)s"), for opts in (("company", " and company=%(company)s"),
("customer", " and si.customer = %(customer)s"), ("customer", " and `tabSales Invoice`.customer = %(customer)s"),
("item_code", " and si_item.item_code = %(item_code)s"), ("item_code", " and `tabSales Invoice Item`.item_code = %(item_code)s"),
("from_date", " and si.posting_date>=%(from_date)s"), ("from_date", " and `tabSales Invoice`.posting_date>=%(from_date)s"),
("to_date", " and si.posting_date<=%(to_date)s")): ("to_date", " and `tabSales Invoice`.posting_date<=%(to_date)s")):
if filters.get(opts[0]): if filters.get(opts[0]):
conditions += opts[1] conditions += opts[1]
if filters.get("mode_of_payment"): if filters.get("mode_of_payment"):
conditions += """ and exists(select name from `tabSales Invoice Payment` conditions += """ and exists(select name from `tabSales Invoice Payment`
where parent=si.name where parent=si.name
and ifnull(`tabSales Invoice Payment`.mode_of_payment, '') = %(mode_of_payment)s)""" and ifnull(`tabSales Invoice Payment`.mode_of_payment, '') = %(mode_of_payment)s)"""
return conditions return conditions
def get_items(filters, additional_query_columns): def get_items(filters, additional_query_columns):
conditions = get_conditions(filters)
match_conditions = frappe.build_match_conditions("Sales Invoice")
if match_conditions:
match_conditions = " and {0} ".format(match_conditions)
if additional_query_columns: if additional_query_columns:
additional_query_columns = ', ' + ', '.join(additional_query_columns) additional_query_columns = ', ' + ', '.join(additional_query_columns)
conditions = get_conditions(filters)
return frappe.db.sql(""" return frappe.db.sql("""
select select
si_item.name, si_item.parent, si.posting_date, si.debit_to, si.project, `tabSales Invoice Item`.name, `tabSales Invoice Item`.parent,
si.customer, si.remarks, si.territory, si.company, si.base_net_total, `tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
si_item.item_code, si_item.item_name, si_item.item_group, si_item.sales_order, `tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks,
si_item.delivery_note, si_item.income_account, si_item.cost_center, `tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
si_item.stock_qty, si_item.stock_uom, si_item.base_net_rate, si_item.base_net_amount, `tabSales Invoice Item`.item_code, `tabSales Invoice Item`.item_name,
si.customer_name, si.customer_group, si_item.so_detail, si.update_stock {0} `tabSales Invoice Item`.item_group, `tabSales Invoice Item`.sales_order,
from `tabSales Invoice` si, `tabSales Invoice Item` si_item `tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.income_account,
where si.name = si_item.parent and si.docstatus = 1 %s `tabSales Invoice Item`.cost_center, `tabSales Invoice Item`.stock_qty,
order by si.posting_date desc, si_item.item_code desc `tabSales Invoice Item`.stock_uom, `tabSales Invoice Item`.base_net_rate,
""".format(additional_query_columns or '') % conditions, filters, as_dict=1) `tabSales Invoice Item`.base_net_amount, `tabSales Invoice`.customer_name,
`tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail,
`tabSales Invoice`.update_stock {0}
from `tabSales Invoice`, `tabSales Invoice Item`
where `tabSales Invoice`.name = `tabSales Invoice Item`.parent
and `tabSales Invoice`.docstatus = 1 %s %s
order by `tabSales Invoice`.posting_date desc, `tabSales Invoice Item`.item_code desc
""".format(additional_query_columns or '') % (conditions, match_conditions), filters, as_dict=1)
def get_delivery_notes_against_sales_order(item_list): def get_delivery_notes_against_sales_order(item_list):
so_dn_map = frappe._dict() so_dn_map = frappe._dict()

View File

@ -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,

View File

@ -295,6 +295,7 @@ def make_purchase_receipt(source_name, target_doc=None):
"field_map": { "field_map": {
"name": "purchase_order_item", "name": "purchase_order_item",
"parent": "purchase_order", "parent": "purchase_order",
"bom": "bom"
}, },
"postprocess": update_item, "postprocess": update_item,
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1 "condition": lambda doc: abs(doc.received_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1

View File

@ -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'),

View File

@ -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):

View File

@ -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");
}, },

View File

@ -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,

View File

@ -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},
] ]
]} ]}

View File

@ -32,6 +32,12 @@ def get_data():
"label": _("POS"), "label": _("POS"),
"description": _("Point of Sale") "description": _("Point of Sale")
}, },
{
"type": "doctype",
"name": "Subscription",
"label": _("Subscription"),
"description": _("To make recurring documents")
},
{ {
"type": "report", "type": "report",
"name": "Accounts Receivable", "name": "Accounts Receivable",

View File

@ -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")
} }
] ]

View 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.")
}
]
}
]

View File

@ -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"

View File

@ -16,6 +16,8 @@ from erpnext.controllers.stock_controller import StockController
class BuyingController(StockController): class BuyingController(StockController):
def __setup__(self): def __setup__(self):
if hasattr(self, "taxes"): if hasattr(self, "taxes"):
self.flags.print_taxes_with_zero_amount = cint(frappe.db.get_single_value("Print Settings",
"print_taxes_with_zero_amount"))
self.print_templates = { self.print_templates = {
"taxes": "templates/print_formats/includes/taxes.html" "taxes": "templates/print_formats/includes/taxes.html"
} }

View File

@ -227,21 +227,30 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
"_txt": txt.replace('%', '') "_txt": txt.replace('%', '')
}) })
def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, filters, as_dict): def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, filters, as_dict):
return frappe.db.sql(""" return frappe.db.sql("""
select `tabDelivery Note`.name, `tabDelivery Note`.customer, `tabDelivery Note`.posting_date select `tabDelivery Note`.name, `tabDelivery Note`.customer, `tabDelivery Note`.posting_date
from `tabDelivery Note` from `tabDelivery Note`
where `tabDelivery Note`.`%(key)s` like %(txt)s and where `tabDelivery Note`.`%(key)s` like %(txt)s and
`tabDelivery Note`.docstatus = 1 and `tabDelivery Note`.is_return = 0 `tabDelivery Note`.docstatus = 1
and status not in ("Stopped", "Closed") %(fcond)s and status not in ("Stopped", "Closed") %(fcond)s
and (`tabDelivery Note`.per_billed < 100 or `tabDelivery Note`.grand_total = 0) and (
(`tabDelivery Note`.is_return = 0 and `tabDelivery Note`.per_billed < 100)
or `tabDelivery Note`.grand_total = 0
or (
`tabDelivery Note`.is_return = 1
and return_against in (select name from `tabDelivery Note` where per_billed < 100)
)
)
%(mcond)s order by `tabDelivery Note`.`%(key)s` asc %(mcond)s order by `tabDelivery Note`.`%(key)s` asc
""" % { """ % {
"key": searchfield, "key": searchfield,
"fcond": get_filters_cond(doctype, filters, []), "fcond": get_filters_cond(doctype, filters, []),
"mcond": get_match_cond(doctype), "mcond": get_match_cond(doctype),
"txt": "%(txt)s" "txt": "%(txt)s"
}, { "txt": ("%%%s%%" % txt) }, as_dict=as_dict) }, {"txt": ("%%%s%%" % txt)}, as_dict=as_dict)
def get_batch_no(doctype, txt, searchfield, start, page_len, filters): def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
cond = "" cond = ""

View File

@ -14,6 +14,8 @@ from erpnext.controllers.stock_controller import StockController
class SellingController(StockController): class SellingController(StockController):
def __setup__(self): def __setup__(self):
if hasattr(self, "taxes"): if hasattr(self, "taxes"):
self.flags.print_taxes_with_zero_amount = cint(frappe.db.get_single_value("Print Settings",
"print_taxes_with_zero_amount"))
self.print_templates = { self.print_templates = {
"taxes": "templates/print_formats/includes/taxes.html" "taxes": "templates/print_formats/includes/taxes.html"
} }
@ -177,6 +179,9 @@ class SellingController(StockController):
return return
for it in self.get("items"): for it in self.get("items"):
if not it.item_code:
continue
last_purchase_rate, is_stock_item = frappe.db.get_value("Item", it.item_code, ["last_purchase_rate", "is_stock_item"]) last_purchase_rate, is_stock_item = frappe.db.get_value("Item", it.item_code, ["last_purchase_rate", "is_stock_item"])
last_purchase_rate_in_sales_uom = last_purchase_rate / (it.conversion_factor or 1) last_purchase_rate_in_sales_uom = last_purchase_rate / (it.conversion_factor or 1)
if flt(it.base_rate) < flt(last_purchase_rate_in_sales_uom): if flt(it.base_rate) < flt(last_purchase_rate_in_sales_uom):

View File

@ -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,26 +568,21 @@ 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(
tax_rate=flt(tax_data[0]),
itemised_tax[item_code][tax.description] = frappe._dict(dict( tax_amount=flt(tax_data[1])
tax_rate=flt(tax_data[0]), ))
tax_amount=flt(tax_data[1]) else:
)) itemised_tax[item_code][tax.description] = frappe._dict(dict(
else: tax_rate=flt(tax_data),
itemised_tax[item_code][tax.description] = frappe._dict(dict( tax_amount=0.0
tax_rate=flt(tax_data), ))
tax_amount=0.0
))
return itemised_tax return itemised_tax

View File

@ -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(

View File

@ -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({

File diff suppressed because it is too large Load Diff

View 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",

View 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"
}
]

View 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"
}
]

View File

@ -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()

View File

@ -15,5 +15,8 @@ data = {
}, },
'Education': { 'Education': {
'company_name': 'Whitmore College' 'company_name': 'Whitmore College'
},
'Healthcare': {
'company_name': 'ABC Hospital Ltd.'
} }
} }

View 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')

View File

@ -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

View File

Before

Width:  |  Height:  |  Size: 818 KiB

After

Width:  |  Height:  |  Size: 818 KiB

View File

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

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