Merge branch 'develop' into validate-paid-inv-msg

This commit is contained in:
Saqib 2020-04-30 20:08:18 +05:30 committed by GitHub
commit dac51ea1cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
99 changed files with 1154 additions and 988 deletions

View File

@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe
import unittest
from frappe.utils import nowdate
from frappe.utils import nowdate, now_datetime
from erpnext.accounts.utils import get_fiscal_year
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
from erpnext.accounts.doctype.budget.budget import get_actual_expense, BudgetError
@ -13,27 +13,28 @@ from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journ
class TestBudget(unittest.TestCase):
def test_monthly_budget_crossed_ignore(self):
set_total_expense_zero("2013-02-28", "cost_center")
set_total_expense_zero(nowdate(), "cost_center")
budget = make_budget(budget_against="Cost Center")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True)
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv.name}))
budget.cancel()
jv.cancel()
def test_monthly_budget_crossed_stop1(self):
set_total_expense_zero("2013-02-28", "cost_center")
set_total_expense_zero(nowdate(), "cost_center")
budget = make_budget(budget_against="Cost Center")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date="2013-02-28")
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date=nowdate())
self.assertRaises(BudgetError, jv.submit)
@ -41,14 +42,14 @@ class TestBudget(unittest.TestCase):
budget.cancel()
def test_exception_approver_role(self):
set_total_expense_zero("2013-02-28", "cost_center")
set_total_expense_zero(nowdate(), "cost_center")
budget = make_budget(budget_against="Cost Center")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date="2013-03-02")
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date=nowdate())
self.assertRaises(BudgetError, jv.submit)
@ -112,16 +113,17 @@ class TestBudget(unittest.TestCase):
budget.load_from_db()
budget.cancel()
po.cancel()
def test_monthly_budget_crossed_stop2(self):
set_total_expense_zero("2013-02-28", "project")
set_total_expense_zero(nowdate(), "project")
budget = make_budget(budget_against="Project")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", project="_Test Project", posting_date="2013-02-28")
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", project="_Test Project", posting_date=nowdate())
self.assertRaises(BudgetError, jv.submit)
@ -129,86 +131,76 @@ class TestBudget(unittest.TestCase):
budget.cancel()
def test_yearly_budget_crossed_stop1(self):
set_total_expense_zero("2013-02-28", "cost_center")
set_total_expense_zero(nowdate(), "cost_center")
budget = make_budget(budget_against="Cost Center")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 150000, "_Test Cost Center - _TC", posting_date="2013-03-28")
"_Test Bank - _TC", 250000, "_Test Cost Center - _TC", posting_date=nowdate())
self.assertRaises(BudgetError, jv.submit)
budget.cancel()
def test_yearly_budget_crossed_stop2(self):
set_total_expense_zero("2013-02-28", "project")
set_total_expense_zero(nowdate(), "project")
budget = make_budget(budget_against="Project")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 150000, "_Test Cost Center - _TC", project="_Test Project", posting_date="2013-03-28")
"_Test Bank - _TC", 250000, "_Test Cost Center - _TC", project="_Test Project", posting_date=nowdate())
self.assertRaises(BudgetError, jv.submit)
budget.cancel()
def test_monthly_budget_on_cancellation1(self):
set_total_expense_zero("2013-02-28", "cost_center")
set_total_expense_zero(nowdate(), "cost_center")
budget = make_budget(budget_against="Cost Center")
jv1 = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True)
for i in range(now_datetime().month):
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv1.name}))
jv2 = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True)
self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv2.name}))
self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv.name}))
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
self.assertRaises(BudgetError, jv1.cancel)
self.assertRaises(BudgetError, jv.cancel)
budget.load_from_db()
budget.cancel()
def test_monthly_budget_on_cancellation2(self):
set_total_expense_zero("2013-02-28", "project")
set_total_expense_zero(nowdate(), "project")
budget = make_budget(budget_against="Project")
jv1 = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True, project="_Test Project")
for i in range(now_datetime().month):
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True, project="_Test Project")
self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv1.name}))
jv2 = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True, project="_Test Project")
self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv2.name}))
self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv.name}))
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
self.assertRaises(BudgetError, jv1.cancel)
self.assertRaises(BudgetError, jv.cancel)
budget.load_from_db()
budget.cancel()
def test_monthly_budget_against_group_cost_center(self):
set_total_expense_zero("2013-02-28", "cost_center")
set_total_expense_zero("2013-02-28", "cost_center", "_Test Cost Center 2 - _TC")
set_total_expense_zero(nowdate(), "cost_center")
set_total_expense_zero(nowdate(), "cost_center", "_Test Cost Center 2 - _TC")
budget = make_budget(budget_against="Cost Center", cost_center="_Test Company - _TC")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center 2 - _TC", posting_date="2013-02-28")
"_Test Bank - _TC", 40000, "_Test Cost Center 2 - _TC", posting_date=nowdate())
self.assertRaises(BudgetError, jv.submit)
@ -231,7 +223,7 @@ class TestBudget(unittest.TestCase):
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, cost_center, posting_date="2013-02-28")
"_Test Bank - _TC", 40000, cost_center, posting_date=nowdate())
self.assertRaises(BudgetError, jv.submit)
@ -246,12 +238,14 @@ def set_total_expense_zero(posting_date, budget_against_field=None, budget_again
else:
budget_against = budget_against_CC or "_Test Cost Center - _TC"
fiscal_year = get_fiscal_year(nowdate())[0]
args = frappe._dict({
"account": "_Test Account Cost for Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC",
"monthly_end_date": posting_date,
"company": "_Test Company",
"fiscal_year": "_Test Fiscal Year 2013",
"fiscal_year": fiscal_year,
"budget_against_field": budget_against_field,
})
@ -263,10 +257,10 @@ def set_total_expense_zero(posting_date, budget_against_field=None, budget_again
if existing_expense:
if budget_against_field == "cost_center":
make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True)
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
elif budget_against_field == "project":
make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project="_Test Project", posting_date="2013-02-28")
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project="_Test Project", posting_date=nowdate())
def make_budget(**args):
args = frappe._dict(args)
@ -274,10 +268,13 @@ def make_budget(**args):
budget_against=args.budget_against
cost_center=args.cost_center
fiscal_year = get_fiscal_year(nowdate())[0]
if budget_against == "Project":
budget_list = frappe.get_all("Budget", fields=["name"], filters = {"name": ("like", "_Test Project/_Test Fiscal Year 2013%")})
project_name = "{0}%".format("_Test Project/" + fiscal_year)
budget_list = frappe.get_all("Budget", fields=["name"], filters = {"name": ("like", project_name)})
else:
cost_center_name = "{0}%".format(cost_center or "_Test Cost Center - _TC/_Test Fiscal Year 2013")
cost_center_name = "{0}%".format(cost_center or "_Test Cost Center - _TC/" + fiscal_year)
budget_list = frappe.get_all("Budget", fields=["name"], filters = {"name": ("like", cost_center_name)})
for d in budget_list:
frappe.db.sql("delete from `tabBudget` where name = %(name)s", d)
@ -290,8 +287,10 @@ def make_budget(**args):
else:
budget.cost_center =cost_center or "_Test Cost Center - _TC"
monthly_distribution = frappe.get_doc("Monthly Distribution", "_Test Distribution")
monthly_distribution.fiscal_year = fiscal_year
budget.fiscal_year = "_Test Fiscal Year 2013"
budget.fiscal_year = fiscal_year
budget.monthly_distribution = "_Test Distribution"
budget.company = "_Test Company"
budget.applicable_on_booking_actual_expenses = 1
@ -300,7 +299,7 @@ def make_budget(**args):
budget.budget_against = budget_against
budget.append("accounts", {
"account": "_Test Account Cost for Goods Sold - _TC",
"budget_amount": 100000
"budget_amount": 200000
})
if args.applicable_on_material_request:

View File

@ -18,7 +18,7 @@ frappe.ui.form.on('Cost Center', {
},
refresh: function(frm) {
if (!frm.is_new()) {
frm.add_custom_button(__('Update Cost Center Number'), function () {
frm.add_custom_button(__('Update Cost Center Name / Number'), function () {
frm.trigger("update_cost_center_number");
});
}
@ -47,35 +47,45 @@ frappe.ui.form.on('Cost Center', {
},
update_cost_center_number: function(frm) {
var d = new frappe.ui.Dialog({
title: __('Update Cost Center Number'),
title: __('Update Cost Center Name / Number'),
fields: [
{
"label": 'Cost Center Number',
"label": "Cost Center Name",
"fieldname": "cost_center_name",
"fieldtype": "Data",
"reqd": 1,
"default": frm.doc.cost_center_name
},
{
"label": "Cost Center Number",
"fieldname": "cost_center_number",
"fieldtype": "Data",
"reqd": 1
"reqd": 1,
"default": frm.doc.cost_center_number
}
],
primary_action: function() {
var data = d.get_values();
if(data.cost_center_number === frm.doc.cost_center_number) {
if(data.cost_center_name === frm.doc.cost_center_name && data.cost_center_number === frm.doc.cost_center_number) {
d.hide();
return;
}
frappe.dom.freeze();
frappe.call({
method: "erpnext.accounts.utils.update_number_field",
method: "erpnext.accounts.utils.update_cost_center",
args: {
doctype_name: frm.doc.doctype,
name: frm.doc.name,
field_name: d.fields[0].fieldname,
number_value: data.cost_center_number,
docname: frm.doc.name,
cost_center_name: data.cost_center_name,
cost_center_number: data.cost_center_number,
company: frm.doc.company
},
callback: function(r) {
frappe.dom.unfreeze();
if(!r.exc) {
if(r.message) {
frappe.set_route("Form", "Cost Center", r.message);
} else {
me.frm.set_value("cost_center_name", data.cost_center_name);
me.frm.set_value("cost_center_number", data.cost_center_number);
}
d.hide();

View File

@ -2,7 +2,6 @@
"actions": [],
"allow_copy": 1,
"allow_import": 1,
"allow_rename": 1,
"creation": "2013-01-23 19:57:17",
"description": "Track separate Income and Expense for product verticals or divisions.",
"doctype": "DocType",
@ -126,7 +125,7 @@
"idx": 1,
"is_tree": 1,
"links": [],
"modified": "2020-03-18 17:59:04.321637",
"modified": "2020-04-29 16:09:30.025214",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Cost Center",

View File

@ -30,7 +30,8 @@
"company",
"finance_book",
"to_rename",
"due_date"
"due_date",
"is_cancelled"
],
"fields": [
{
@ -245,12 +246,18 @@
"fieldname": "due_date",
"fieldtype": "Date",
"label": "Due Date"
},
{
"default": "0",
"fieldname": "is_cancelled",
"fieldtype": "Check",
"label": "Is Cancelled"
}
],
"icon": "fa fa-list",
"idx": 1,
"in_create": 1,
"modified": "2020-03-28 16:22:33.766994",
"modified": "2020-04-07 16:22:33.766994",
"modified_by": "Administrator",
"module": "Accounts",
"name": "GL Entry",

View File

@ -30,23 +30,20 @@ class GLEntry(Document):
self.pl_must_have_cost_center()
self.validate_cost_center()
if not self.flags.from_repost:
self.check_pl_account()
self.validate_party()
self.validate_currency()
self.check_pl_account()
self.validate_party()
self.validate_currency()
def on_update_with_args(self, adv_adj, update_outstanding = 'Yes', from_repost=False):
if not from_repost:
self.validate_account_details(adv_adj)
self.validate_dimensions_for_pl_and_bs()
check_freezing_date(self.posting_date, adv_adj)
def on_update_with_args(self, adv_adj, update_outstanding = 'Yes'):
self.validate_account_details(adv_adj)
self.validate_dimensions_for_pl_and_bs()
validate_frozen_account(self.account, adv_adj)
validate_balance_type(self.account, adv_adj)
# Update outstanding amt on against voucher
if self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees'] \
and self.against_voucher and update_outstanding == 'Yes' and not from_repost:
and self.against_voucher and update_outstanding == 'Yes':
update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type,
self.against_voucher)
@ -159,7 +156,6 @@ class GLEntry(Document):
if self.party_type and self.party:
validate_party_gle_currency(self.party_type, self.party, self.company, self.account_currency)
def validate_and_set_fiscal_year(self):
if not self.fiscal_year:
self.fiscal_year = get_fiscal_year(self.posting_date, company=self.company)[0]
@ -176,19 +172,6 @@ def validate_balance_type(account, adv_adj=False):
(balance_must_be=="Credit" and flt(balance) > 0):
frappe.throw(_("Balance for Account {0} must always be {1}").format(account, _(balance_must_be)))
def check_freezing_date(posting_date, adv_adj=False):
"""
Nobody can do GL Entries where posting date is before freezing date
except authorized person
"""
if not adv_adj:
acc_frozen_upto = frappe.db.get_value('Accounts Settings', None, 'acc_frozen_upto')
if acc_frozen_upto:
frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier')
if getdate(posting_date) <= getdate(acc_frozen_upto) \
and not frozen_accounts_modifier in frappe.get_roles():
frappe.throw(_("You are not authorized to add or update entries before {0}").format(formatdate(acc_frozen_upto)))
def update_outstanding_amt(account, party_type, party, against_voucher_type, against_voucher, on_cancel=False):
if party_type and party:
party_condition = " and party_type={0} and party={1}"\

View File

@ -57,6 +57,7 @@ class JournalEntry(AccountsController):
from erpnext.hr.doctype.salary_slip.salary_slip import unlink_ref_doc_from_salary_slip
unlink_ref_doc_from_payment_entries(self)
unlink_ref_doc_from_salary_slip(self.name)
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
self.make_gl_entries(1)
self.update_advance_paid()
self.update_expense_claim()
@ -594,7 +595,7 @@ class JournalEntry(AccountsController):
for d in self.accounts:
if d.reference_type=="Expense Claim" and d.reference_name:
doc = frappe.get_doc("Expense Claim", d.reference_name)
update_reimbursed_amount(doc)
update_reimbursed_amount(doc, jv=self.name)
def validate_expense_claim(self):

View File

@ -76,6 +76,7 @@ class PaymentEntry(AccountsController):
self.set_status()
def on_cancel(self):
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
self.setup_party_account_field()
self.make_gl_entries(cancel=1)
self.update_outstanding_amounts()
@ -591,7 +592,7 @@ class PaymentEntry(AccountsController):
for d in self.get("references"):
if d.reference_doctype=="Expense Claim" and d.reference_name:
doc = frappe.get_doc("Expense Claim", d.reference_name)
update_reimbursed_amount(doc)
update_reimbursed_amount(doc, self.name)
def on_recurring(self, reference_doc, auto_repeat_doc):
self.reference_no = reference_doc.name

View File

@ -35,8 +35,6 @@ class TestPaymentEntry(unittest.TestCase):
pe.cancel()
self.assertFalse(self.get_gle(pe.name))
so_advance_paid = frappe.db.get_value("Sales Order", so.name, "advance_paid")
self.assertEqual(so_advance_paid, 0)
@ -124,7 +122,6 @@ class TestPaymentEntry(unittest.TestCase):
self.assertEqual(outstanding_amount, 0)
pe.cancel()
self.assertFalse(self.get_gle(pe.name))
outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"))
self.assertEqual(outstanding_amount, 100)
@ -381,7 +378,6 @@ class TestPaymentEntry(unittest.TestCase):
self.assertEqual(outstanding_amount, 0)
pe3.cancel()
self.assertFalse(self.get_gle(pe3.name))
outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si1.name, "outstanding_amount"))
self.assertEqual(outstanding_amount, -100)

View File

@ -5,7 +5,7 @@ frappe.ui.form.on('Period Closing Voucher', {
onload: function(frm) {
if (!frm.doc.transaction_date) frm.doc.transaction_date = frappe.datetime.obj_to_str(new Date());
},
setup: function(frm) {
frm.set_query("closing_account_head", function() {
return {
@ -18,9 +18,9 @@ frappe.ui.form.on('Period Closing Voucher', {
}
});
},
refresh: function(frm) {
if(frm.doc.docstatus==1) {
if(frm.doc.docstatus > 0) {
frm.add_custom_button(__('Ledger'), function() {
frappe.route_options = {
"voucher_no": frm.doc.name,
@ -33,5 +33,5 @@ frappe.ui.form.on('Period Closing Voucher', {
}, "fa fa-table");
}
}
})

View File

@ -17,8 +17,9 @@ class PeriodClosingVoucher(AccountsController):
self.make_gl_entries()
def on_cancel(self):
frappe.db.sql("""delete from `tabGL Entry`
where voucher_type = 'Period Closing Voucher' and voucher_no=%s""", self.name)
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
from erpnext.accounts.general_ledger import make_reverse_gl_entries
make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name, cancel=True)
def validate_account_head(self):
closing_account_type = frappe.db.get_value("Account", self.closing_account_head, "root_type")

View File

@ -382,11 +382,6 @@ function hide_fields(doc) {
cur_frm.refresh_fields();
}
cur_frm.cscript.update_stock = function(doc, dt, dn) {
hide_fields(doc, dt, dn);
this.frm.fields_dict.items.grid.toggle_reqd("item_code", doc.update_stock? true: false)
}
cur_frm.fields_dict.cash_bank_account.get_query = function(doc) {
return {
filters: [
@ -528,5 +523,10 @@ frappe.ui.form.on("Purchase Invoice", {
erpnext.buying.get_default_bom(frm);
}
frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted==="Yes");
},
update_stock: function(frm) {
hide_fields(frm.doc);
frm.fields_dict.items.grid.toggle_reqd("item_code", frm.doc.update_stock? true: false);
}
})

View File

@ -14,7 +14,7 @@ from erpnext.accounts.party import get_party_account, get_due_date
from erpnext.accounts.utils import get_account_currency, get_fiscal_year
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billed_amount_based_on_po
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, make_reverse_gl_entries
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
from erpnext.buying.utils import check_on_hold_or_closed_status
from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center
@ -382,7 +382,7 @@ class PurchaseInvoice(BuyingController):
self.update_project()
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
def make_gl_entries(self, gl_entries=None, repost_future_gle=True, from_repost=False):
def make_gl_entries(self, gl_entries=None):
if not self.grand_total:
return
if not gl_entries:
@ -391,21 +391,17 @@ class PurchaseInvoice(BuyingController):
if gl_entries:
update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes"
make_gl_entries(gl_entries, cancel=(self.docstatus == 2),
update_outstanding=update_outstanding, merge_entries=False, from_repost=from_repost)
if self.docstatus == 1:
make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False)
elif self.docstatus == 2:
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
if update_outstanding == "No":
update_outstanding_amt(self.credit_to, "Supplier", self.supplier,
self.doctype, self.return_against if cint(self.is_return) and self.return_against else self.name)
if (repost_future_gle or self.flags.repost_future_gle) and cint(self.update_stock) and self.auto_accounting_for_stock:
from erpnext.controllers.stock_controller import update_gl_entries_after
items, warehouses = self.get_items_and_warehouses()
update_gl_entries_after(self.posting_date, self.posting_time,
warehouses, items, company = self.company)
elif self.docstatus == 2 and cint(self.update_stock) and self.auto_accounting_for_stock:
delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
def get_gl_entries(self, warehouse_account=None):
self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
@ -934,6 +930,7 @@ class PurchaseInvoice(BuyingController):
frappe.db.set(self, 'status', 'Cancelled')
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
def update_project(self):
project_list = []

View File

@ -138,13 +138,12 @@
"row_id": 7
}
],
"posting_date": "2013-02-03",
"supplier": "_Test Supplier",
"supplier_name": "_Test Supplier"
},
{
"bill_no": "NA",
"buying_price_list": "_Test Price List",
@ -204,7 +203,6 @@
"tax_amount": 150.0
}
],
"posting_date": "2013-02-03",
"supplier": "_Test Supplier",
"supplier_name": "_Test Supplier"
}

View File

@ -345,7 +345,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
set_dynamic_labels: function() {
this._super();
this.hide_fields(this.frm.doc);
this.frm.events.hide_fields(this.frm)
},
items_on_form_rendered: function() {
@ -404,7 +404,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
if(r.message && r.message.print_format) {
me.frm.pos_print_format = r.message.print_format;
}
me.frm.script_manager.trigger("update_stock");
me.frm.trigger("update_stock");
if(me.frm.doc.taxes_and_charges) {
me.frm.script_manager.trigger("taxes_and_charges");
}
@ -446,35 +446,6 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
// for backward compatibility: combine new and previous states
$.extend(cur_frm.cscript, new erpnext.accounts.SalesInvoiceController({frm: cur_frm}));
// Hide Fields
// ------------
cur_frm.cscript.hide_fields = function(doc) {
var parent_fields = ['project', 'due_date', 'is_opening', 'source', 'total_advance', 'get_advances',
'advances', 'from_date', 'to_date'];
if(cint(doc.is_pos) == 1) {
hide_field(parent_fields);
} else {
for (var i in parent_fields) {
var docfield = frappe.meta.docfield_map[doc.doctype][parent_fields[i]];
if(!docfield.hidden) unhide_field(parent_fields[i]);
}
}
// India related fields
if (frappe.boot.sysdefaults.country == 'India') unhide_field(['c_form_applicable', 'c_form_no']);
else hide_field(['c_form_applicable', 'c_form_no']);
this.frm.toggle_enable("write_off_amount", !!!cint(doc.write_off_outstanding_amount_automatically));
cur_frm.refresh_fields();
}
cur_frm.cscript.update_stock = function(doc, dt, dn) {
cur_frm.cscript.hide_fields(doc, dt, dn);
this.frm.fields_dict.items.grid.toggle_reqd("item_code", doc.update_stock? true: false)
}
cur_frm.cscript['Make Delivery Note'] = function() {
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_delivery_note",
@ -719,6 +690,12 @@ frappe.ui.form.on('Sales Invoice', {
frm.redemption_conversion_factor = null;
},
update_stock: function(frm, dt, dn) {
frm.events.hide_fields(frm);
frm.fields_dict.items.grid.toggle_reqd("item_code", frm.doc.update_stock);
frm.trigger('reset_posting_time');
},
redeem_loyalty_points: function(frm) {
frm.events.get_loyalty_details(frm);
},
@ -742,6 +719,29 @@ frappe.ui.form.on('Sales Invoice', {
}
},
hide_fields: function(frm) {
let doc = frm.doc;
var parent_fields = ['project', 'due_date', 'is_opening', 'source', 'total_advance', 'get_advances',
'advances', 'from_date', 'to_date'];
if(cint(doc.is_pos) == 1) {
hide_field(parent_fields);
} else {
for (var i in parent_fields) {
var docfield = frappe.meta.docfield_map[doc.doctype][parent_fields[i]];
if(!docfield.hidden) unhide_field(parent_fields[i]);
}
}
// India related fields
if (frappe.boot.sysdefaults.country == 'India') unhide_field(['c_form_applicable', 'c_form_no']);
else hide_field(['c_form_applicable', 'c_form_no']);
frm.toggle_enable("write_off_amount", !!!cint(doc.write_off_outstanding_amount_automatically));
frm.refresh_fields();
},
get_loyalty_details: function(frm) {
if (frm.doc.customer && frm.doc.redeem_loyalty_points) {
frappe.call({

View File

@ -149,9 +149,9 @@
"edit_printing_settings",
"letter_head",
"group_same_items",
"language",
"column_break_84",
"select_print_heading",
"column_break_84",
"language",
"more_information",
"inter_company_invoice_reference",
"is_internal_customer",
@ -1579,7 +1579,7 @@
"idx": 181,
"is_submittable": 1,
"links": [],
"modified": "2020-04-17 12:38:41.435728",
"modified": "2020-04-29 13:37:09.355300",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@ -7,7 +7,6 @@ import frappe.defaults
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate
from frappe import _, msgprint, throw
from erpnext.accounts.party import get_party_account, get_due_date
from erpnext.controllers.stock_controller import update_gl_entries_after
from frappe.model.mapper import get_mapped_doc
from erpnext.accounts.doctype.sales_invoice.pos import update_multi_mode_option
@ -282,6 +281,8 @@ class SalesInvoice(SellingController):
if "Healthcare" in active_domains:
manage_invoice_submit_cancel(self, "on_cancel")
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
def update_status_updater_args(self):
if cint(self.update_stock):
self.status_updater.append({
@ -717,7 +718,9 @@ class SalesInvoice(SellingController):
if d.delivery_note and frappe.db.get_value("Delivery Note", d.delivery_note, "docstatus") != 1:
throw(_("Delivery Note {0} is not submitted").format(d.delivery_note))
def make_gl_entries(self, gl_entries=None, repost_future_gle=True, from_repost=False):
def make_gl_entries(self, gl_entries=None):
from erpnext.accounts.general_ledger import make_reverse_gl_entries
auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
if not gl_entries:
gl_entries = self.get_gl_entries()
@ -729,23 +732,19 @@ class SalesInvoice(SellingController):
update_outstanding = "No" if (cint(self.is_pos) or self.write_off_account or
cint(self.redeem_loyalty_points)) else "Yes"
make_gl_entries(gl_entries, cancel=(self.docstatus == 2),
update_outstanding=update_outstanding, merge_entries=False, from_repost=from_repost)
if self.docstatus == 1:
make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False)
elif self.docstatus == 2:
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
if update_outstanding == "No":
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
update_outstanding_amt(self.debit_to, "Customer", self.customer,
self.doctype, self.return_against if cint(self.is_return) and self.return_against else self.name)
if (repost_future_gle or self.flags.repost_future_gle) and cint(self.update_stock) \
and cint(auto_accounting_for_stock):
items, warehouses = self.get_items_and_warehouses()
update_gl_entries_after(self.posting_date, self.posting_time,
warehouses, items, company = self.company)
elif self.docstatus == 2 and cint(self.update_stock) \
and cint(auto_accounting_for_stock):
from erpnext.accounts.general_ledger import delete_gl_entries
delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
def get_gl_entries(self, warehouse_account=None):
from erpnext.accounts.general_ledger import merge_similar_entries

View File

@ -364,7 +364,7 @@ class TestSalesInvoice(unittest.TestCase):
gle = frappe.db.sql("""select * from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)
self.assertFalse(gle)
self.assertTrue(gle)
def test_tax_calculation_with_multiple_items(self):
si = create_sales_invoice(qty=84, rate=4.6, do_not_save=True)
@ -678,14 +678,15 @@ class TestSalesInvoice(unittest.TestCase):
gle = frappe.db.sql("""select * from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)
self.assertFalse(gle)
self.assertTrue(gle)
def test_pos_gl_entry_with_perpetual_inventory(self):
make_pos_profile()
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True)
pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1",
income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True)
pos.is_pos = 1
pos.update_stock = 1
@ -766,9 +767,13 @@ class TestSalesInvoice(unittest.TestCase):
def test_pos_change_amount(self):
make_pos_profile()
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",
item_code= "_Test FG Item",warehouse= "Stores - TCP1", cost_center= "Main - TCP1")
pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True)
pos = create_sales_invoice(company= "_Test Company with perpetual inventory",
debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1",
income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1",
cost_center = "Main - TCP1", do_not_save=True)
pos.is_pos = 1
pos.update_stock = 1
@ -787,8 +792,15 @@ class TestSalesInvoice(unittest.TestCase):
from erpnext.accounts.doctype.sales_invoice.pos import make_invoice
pos_profile = make_pos_profile()
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True)
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",
item_code= "_Test FG Item",
warehouse= "Stores - TCP1", cost_center= "Main - TCP1")
pos = create_sales_invoice(company= "_Test Company with perpetual inventory",
debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1",
income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1",
cost_center = "Main - TCP1", do_not_save=True)
pos.is_pos = 1
pos.update_stock = 1
@ -891,11 +903,9 @@ class TestSalesInvoice(unittest.TestCase):
gle = frappe.db.sql("""select * from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)
self.assertFalse(gle)
self.assertTrue(gle)
frappe.db.sql("delete from `tabPOS Profile`")
si.delete()
def test_pos_si_without_payment(self):
set_perpetual_inventory()
@ -1012,9 +1022,6 @@ class TestSalesInvoice(unittest.TestCase):
si.cancel()
self.assertTrue(not frappe.db.sql("""select name from `tabJournal Entry Account`
where reference_name=%s""", si.name))
def test_serialized(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@ -1230,7 +1237,7 @@ class TestSalesInvoice(unittest.TestCase):
gle = frappe.db.sql("""select name from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)
self.assertFalse(gle)
self.assertTrue(gle)
def test_invalid_currency(self):
# Customer currency = USD

View File

@ -3,7 +3,7 @@
from __future__ import unicode_literals
import frappe, erpnext
from frappe.utils import flt, cstr, cint, comma_and
from frappe.utils import flt, cstr, cint, comma_and, today, getdate, formatdate, now
from frappe import _
from erpnext.accounts.utils import get_stock_and_account_balance
from frappe.model.meta import get_field_precision
@ -15,17 +15,17 @@ class ClosedAccountingPeriod(frappe.ValidationError): pass
class StockAccountInvalidTransaction(frappe.ValidationError): pass
class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass
def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes', from_repost=False):
def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes'):
if gl_map:
if not cancel:
validate_accounting_period(gl_map)
gl_map = process_gl_map(gl_map, merge_entries)
if gl_map and len(gl_map) > 1:
save_entries(gl_map, adv_adj, update_outstanding, from_repost)
save_entries(gl_map, adv_adj, update_outstanding)
else:
frappe.throw(_("Incorrect number of General Ledger Entries found. You might have selected a wrong Account in the transaction."))
else:
delete_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding)
make_reverse_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding)
def validate_accounting_period(gl_map):
accounting_periods = frappe.db.sql(""" SELECT
@ -119,33 +119,36 @@ def check_if_in_list(gle, gl_map, dimensions=None):
if same_head:
return e
def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
if not from_repost:
validate_cwip_accounts(gl_map)
def save_entries(gl_map, adv_adj, update_outstanding):
validate_cwip_accounts(gl_map)
round_off_debit_credit(gl_map)
if gl_map:
check_freezing_date(gl_map[0]["posting_date"], adv_adj)
for entry in gl_map:
make_entry(entry, adv_adj, update_outstanding, from_repost)
make_entry(entry, adv_adj, update_outstanding)
# check against budget
if not from_repost:
validate_expense_against_budget(entry)
validate_expense_against_budget(entry)
if not from_repost:
validate_account_for_perpetual_inventory(gl_map)
validate_account_for_perpetual_inventory(gl_map)
def make_entry(args, adv_adj, update_outstanding, from_repost=False):
def make_entry(args, adv_adj, update_outstanding):
gle = frappe.new_doc("GL Entry")
gle.update(args)
gle.flags.ignore_permissions = 1
gle.flags.from_repost = from_repost
gle.validate()
gle.db_insert()
gle.run_method("on_update_with_args", adv_adj, update_outstanding, from_repost)
gle.run_method("on_update_with_args", adv_adj, update_outstanding)
gle.flags.ignore_validate = True
gle.submit()
# check against budget
validate_expense_against_budget(args)
def validate_account_for_perpetual_inventory(gl_map):
if cint(erpnext.is_perpetual_inventory_enabled(gl_map[0].company)):
account_list = [gl_entries.account for gl_entries in gl_map]
@ -169,33 +172,33 @@ def validate_account_for_perpetual_inventory(gl_map):
.format(account), StockAccountInvalidTransaction)
# This has been comment for a temporary, will add this code again on release of immutable ledger
# elif account_bal != stock_bal:
# precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
# currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency"))
elif account_bal != stock_bal:
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency"))
# diff = flt(stock_bal - account_bal, precision)
# error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses.").format(
# stock_bal, account_bal, frappe.bold(account))
# error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(diff))
# stock_adjustment_account = frappe.db.get_value("Company",gl_map[0].company,"stock_adjustment_account")
diff = flt(stock_bal - account_bal, precision)
error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses.").format(
stock_bal, account_bal, frappe.bold(account))
error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(diff))
stock_adjustment_account = frappe.db.get_value("Company",gl_map[0].company,"stock_adjustment_account")
# db_or_cr_warehouse_account =('credit_in_account_currency' if diff < 0 else 'debit_in_account_currency')
# db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if diff < 0 else 'credit_in_account_currency')
db_or_cr_warehouse_account =('credit_in_account_currency' if diff < 0 else 'debit_in_account_currency')
db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if diff < 0 else 'credit_in_account_currency')
# journal_entry_args = {
# 'accounts':[
# {'account': account, db_or_cr_warehouse_account : abs(diff)},
# {'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff) }]
# }
journal_entry_args = {
'accounts':[
{'account': account, db_or_cr_warehouse_account : abs(diff)},
{'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff) }]
}
# frappe.msgprint(msg="""{0}<br></br>{1}<br></br>""".format(error_reason, error_resolution),
# raise_exception=StockValueAndAccountBalanceOutOfSync,
# title=_('Values Out Of Sync'),
# primary_action={
# 'label': _('Make Journal Entry'),
# 'client_action': 'erpnext.route_to_adjustment_jv',
# 'args': journal_entry_args
# })
frappe.msgprint(msg="""{0}<br></br>{1}<br></br>""".format(error_reason, error_resolution),
raise_exception=StockValueAndAccountBalanceOutOfSync,
title=_('Values Out Of Sync'),
primary_action={
'label': _('Make Journal Entry'),
'client_action': 'erpnext.route_to_adjustment_jv',
'args': journal_entry_args
})
def validate_cwip_accounts(gl_map):
cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")])
@ -282,31 +285,64 @@ def get_round_off_account_and_cost_center(company):
return round_off_account, round_off_cost_center
def delete_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None,
adv_adj=False, update_outstanding="Yes"):
from erpnext.accounts.doctype.gl_entry.gl_entry import validate_balance_type, \
check_freezing_date, update_outstanding_amt, validate_frozen_account
def make_reverse_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None,
adv_adj=False, update_outstanding="Yes"):
"""
Get original gl entries of the voucher
and make reverse gl entries by swapping debit and credit
"""
if not gl_entries:
gl_entries = frappe.db.sql("""
select account, posting_date, party_type, party, cost_center, fiscal_year,voucher_type,
voucher_no, against_voucher_type, against_voucher, cost_center, company
from `tabGL Entry`
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no), as_dict=True)
gl_entries = frappe.get_all("GL Entry",
fields = ["*"],
filters = {
"voucher_type": voucher_type,
"voucher_no": voucher_no
})
if gl_entries:
set_as_cancel(gl_entries[0]['voucher_type'], gl_entries[0]['voucher_no'])
check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
frappe.db.sql("""delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s""",
(voucher_type or gl_entries[0]["voucher_type"], voucher_no or gl_entries[0]["voucher_no"]))
for entry in gl_entries:
entry['name'] = None
debit = entry.get('debit', 0)
credit = entry.get('credit', 0)
for entry in gl_entries:
validate_frozen_account(entry["account"], adv_adj)
validate_balance_type(entry["account"], adv_adj)
if not adv_adj:
validate_expense_against_budget(entry)
debit_in_account_currency = entry.get('debit_in_account_currency', 0)
credit_in_account_currency = entry.get('credit_in_account_currency', 0)
if entry.get("against_voucher") and update_outstanding == 'Yes' and not adv_adj:
update_outstanding_amt(entry["account"], entry.get("party_type"), entry.get("party"), entry.get("against_voucher_type"),
entry.get("against_voucher"), on_cancel=True)
entry['debit'] = credit
entry['credit'] = debit
entry['debit_in_account_currency'] = credit_in_account_currency
entry['credit_in_account_currency'] = debit_in_account_currency
entry['remarks'] = "On cancellation of " + entry['voucher_no']
entry['is_cancelled'] = 1
entry['posting_date'] = today()
if entry['debit'] or entry['credit']:
make_entry(entry, adv_adj, "Yes")
def check_freezing_date(posting_date, adv_adj=False):
"""
Nobody can do GL Entries where posting date is before freezing date
except authorized person
"""
if not adv_adj:
acc_frozen_upto = frappe.db.get_value('Accounts Settings', None, 'acc_frozen_upto')
if acc_frozen_upto:
frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier')
if getdate(posting_date) <= getdate(acc_frozen_upto) \
and not frozen_accounts_modifier in frappe.get_roles():
frappe.throw(_("You are not authorized to add or update entries before {0}").format(formatdate(acc_frozen_upto)))
def set_as_cancel(voucher_type, voucher_no):
"""
Set is_cancelled=1 in all original gl entries for the voucher
"""
frappe.db.sql("""update `tabGL Entry` set is_cancelled = 1,
modified=%s, modified_by=%s
where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
(now(), frappe.session.user, voucher_type, voucher_no))

View File

@ -154,8 +154,12 @@ frappe.query_reports["General Ledger"] = {
{
"fieldname": "include_default_book_entries",
"label": __("Include Default Book Entries"),
"fieldtype": "Check",
"default": 1
"fieldtype": "Check"
},
{
"fieldname": "show_cancelled_entries",
"label": __("Show Cancelled Entries"),
"fieldtype": "Check"
}
]
}

View File

@ -188,6 +188,9 @@ def get_conditions(filters):
else:
conditions.append("finance_book in (%(finance_book)s)")
if not filters.get("show_cancelled_entries"):
conditions.append("is_cancelled = 0")
from frappe.desk.reportview import build_match_conditions
match_conditions = build_match_conditions("GL Entry")

View File

@ -35,6 +35,12 @@ def execute(filters=None):
})
return columns, data
# to avoid error eg: gross_income[0] : list index out of range
if not gross_income:
gross_income = [{}]
if not gross_expense:
gross_expense = [{}]
data.append({
"account_name": "'" + _("Included in Gross Profit") + "'",
"account": "'" + _("Included in Gross Profit") + "'"

View File

@ -817,48 +817,37 @@ def create_payment_gateway_account(gateway):
pass
@frappe.whitelist()
def update_number_field(doctype_name, name, field_name, number_value, company):
def update_cost_center(docname, cost_center_name, cost_center_number, company):
'''
doctype_name = Name of the DocType
name = Docname being referred
field_name = Name of the field thats holding the 'number' attribute
number_value = Numeric value entered in field_name
Stores the number entered in the dialog to the DocType's field.
Renames the document by adding the number as a prefix to the current name and updates
all transaction where it was present.
'''
doc_title = frappe.db.get_value(doctype_name, name, frappe.scrub(doctype_name)+"_name")
validate_field_number("Cost Center", docname, cost_center_number, company, "cost_center_number")
validate_field_number(doctype_name, name, number_value, company, field_name)
if cost_center_number:
frappe.db.set_value("Cost Center", docname, "cost_center_number", cost_center_number.strip())
else:
frappe.db.set_value("Cost Center", docname, "cost_center_number", "")
frappe.db.set_value(doctype_name, name, field_name, number_value)
frappe.db.set_value("Cost Center", docname, "cost_center_name", cost_center_name.strip())
if doc_title[0].isdigit():
separator = " - " if " - " in doc_title else " "
doc_title = doc_title.split(separator, 1)[1]
frappe.db.set_value(doctype_name, name, frappe.scrub(doctype_name)+"_name", doc_title)
new_name = get_autoname_with_number(number_value, doc_title, name, company)
if name != new_name:
frappe.rename_doc(doctype_name, name, new_name)
new_name = get_autoname_with_number(cost_center_number, cost_center_name, docname, company)
if docname != new_name:
frappe.rename_doc("Cost Center", docname, new_name, force=1)
return new_name
def validate_field_number(doctype_name, name, number_value, company, field_name):
def validate_field_number(doctype_name, docname, number_value, company, field_name):
''' Validate if the number entered isn't already assigned to some other document. '''
if number_value:
filters = {field_name: number_value, "name": ["!=", docname]}
if company:
doctype_with_same_number = frappe.db.get_value(doctype_name,
{field_name: number_value, "company": company, "name": ["!=", name]})
else:
doctype_with_same_number = frappe.db.get_value(doctype_name,
{field_name: number_value, "name": ["!=", name]})
filters["company"] = company
doctype_with_same_number = frappe.db.get_value(doctype_name, filters)
if doctype_with_same_number:
frappe.throw(_("{0} Number {1} already used in account {2}")
.format(doctype_name, number_value, doctype_with_same_number))
frappe.throw(_("{0} Number {1} is already used in {2} {3}")
.format(doctype_name, number_value, doctype_name.lower(), doctype_with_same_number))
def get_autoname_with_number(number_value, doc_title, name, company):
''' append title with prefix as number and suffix as company's abbreviation separated by '-' '''
@ -897,4 +886,60 @@ def get_stock_accounts(company):
return frappe.get_all("Account", filters = {
"account_type": "Stock",
"company": company
})
})
def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
warehouse_account=None, company=None):
def _delete_gl_entries(voucher_type, voucher_no):
frappe.db.sql("""delete from `tabGL Entry`
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
if not warehouse_account:
warehouse_account = get_warehouse_account_map(company)
future_stock_vouchers = get_future_stock_vouchers(posting_date, posting_time, for_warehouses, for_items)
gle = get_voucherwise_gl_entries(future_stock_vouchers, posting_date)
for voucher_type, voucher_no in future_stock_vouchers:
existing_gle = gle.get((voucher_type, voucher_no), [])
voucher_obj = frappe.get_doc(voucher_type, voucher_no)
expected_gle = voucher_obj.get_gl_entries(warehouse_account)
if expected_gle:
if not existing_gle or not compare_existing_and_expected_gle(existing_gle, expected_gle):
_delete_gl_entries(voucher_type, voucher_no)
voucher_obj.make_gl_entries(gl_entries=expected_gle, repost_future_gle=False, from_repost=True)
else:
_delete_gl_entries(voucher_type, voucher_no)
def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, for_items=None):
future_stock_vouchers = []
values = []
condition = ""
if for_items:
condition += " and item_code in ({})".format(", ".join(["%s"] * len(for_items)))
values += for_items
if for_warehouses:
condition += " and warehouse in ({})".format(", ".join(["%s"] * len(for_warehouses)))
values += for_warehouses
for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no
from `tabStock Ledger Entry` sle
where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) {condition}
order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc for update""".format(condition=condition),
tuple([posting_date, posting_time] + values), as_dict=True):
future_stock_vouchers.append([d.voucher_type, d.voucher_no])
return future_stock_vouchers
def get_voucherwise_gl_entries(future_stock_vouchers, posting_date):
gl_entries = {}
if future_stock_vouchers:
for d in frappe.db.sql("""select * from `tabGL Entry`
where posting_date >= %s and voucher_no in (%s)""" %
('%s', ', '.join(['%s']*len(future_stock_vouchers))),
tuple([posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1):
gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d)
return gl_entries

View File

@ -11,7 +11,7 @@ from frappe.model.document import Document
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.assets.doctype.asset.depreciation \
import get_disposal_account_and_cost_center, get_depreciation_accounts
from erpnext.accounts.general_ledger import make_gl_entries, delete_gl_entries
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries
from erpnext.accounts.utils import get_account_currency
from erpnext.controllers.accounts_controller import AccountsController
@ -41,7 +41,8 @@ class Asset(AccountsController):
self.validate_cancellation()
self.delete_depreciation_entries()
self.set_status()
delete_gl_entries(voucher_type='Asset', voucher_no=self.name)
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
make_reverse_gl_entries(voucher_type='Asset', voucher_no=self.name)
self.db_set('booked_fixed_asset', 0)
def validate_asset_and_reference(self):

View File

@ -66,9 +66,6 @@ class TestAsset(unittest.TestCase):
pr.cancel()
self.assertEqual(asset.docstatus, 2)
self.assertFalse(frappe.db.get_value("GL Entry",
{"voucher_type": "Purchase Invoice", "voucher_no": pi.name}))
def test_is_fixed_asset_set(self):
asset = create_asset(is_existing_asset = 1)
doc = frappe.new_doc('Purchase Invoice')

View File

@ -11,7 +11,8 @@ from frappe.model.document import Document
class AssetCategory(Document):
def validate(self):
self.validate_finance_books()
self.validate_accounts()
self.validate_account_types()
self.validate_account_currency()
def validate_finance_books(self):
for d in self.finance_books:
@ -19,7 +20,26 @@ class AssetCategory(Document):
if cint(d.get(frappe.scrub(field)))<1:
frappe.throw(_("Row {0}: {1} must be greater than 0").format(d.idx, field), frappe.MandatoryError)
def validate_accounts(self):
def validate_account_currency(self):
account_types = [
'fixed_asset_account', 'accumulated_depreciation_account', 'depreciation_expense_account', 'capital_work_in_progress_account'
]
invalid_accounts = []
for d in self.accounts:
company_currency = frappe.get_value('Company', d.get('company_name'), 'default_currency')
for type_of_account in account_types:
if d.get(type_of_account):
account_currency = frappe.get_value("Account", d.get(type_of_account), "account_currency")
if account_currency != company_currency:
invalid_accounts.append(frappe._dict({ 'type': type_of_account, 'idx': d.idx, 'account': d.get(type_of_account) }))
for d in invalid_accounts:
frappe.throw(_("Row #{}: Currency of {} - {} doesn't matches company currency.")
.format(d.idx, frappe.bold(frappe.unscrub(d.type)), frappe.bold(d.account)),
title=_("Invalid Account"))
def validate_account_types(self):
account_type_map = {
'fixed_asset_account': { 'account_type': 'Fixed Asset' },
'accumulated_depreciation_account': { 'account_type': 'Accumulated Depreciation' },

View File

@ -170,6 +170,10 @@ def get_data():
"type": "doctype",
"name": "Payroll Period",
},
{
"type": "doctype",
"name": "Income Tax Slab",
},
{
"type": "doctype",
"name": "Salary Component",
@ -209,6 +213,10 @@ def get_data():
"name": "Employee Tax Exemption Proof Submission",
"dependencies": ["Employee"]
},
{
"type": "doctype",
"name": "Employee Other Income",
},
{
"type": "doctype",
"name": "Employee Benefit Application",

View File

@ -664,23 +664,26 @@ class AccountsController(TransactionBase):
def set_total_advance_paid(self):
if self.doctype == "Sales Order":
dr_or_cr = "credit_in_account_currency"
rev_dr_or_cr = "debit_in_account_currency"
party = self.customer
else:
dr_or_cr = "debit_in_account_currency"
rev_dr_or_cr = "credit_in_account_currency"
party = self.supplier
advance = frappe.db.sql("""
select
account_currency, sum({dr_or_cr}) as amount
account_currency, sum({dr_or_cr}) - sum({rev_dr_cr}) as amount
from
`tabGL Entry`
where
against_voucher_type = %s and against_voucher = %s and party=%s
and docstatus = 1
""".format(dr_or_cr=dr_or_cr), (self.doctype, self.name, party), as_dict=1)
""".format(dr_or_cr=dr_or_cr, rev_dr_cr=rev_dr_or_cr), (self.doctype, self.name, party), as_dict=1) #nosec
if advance:
advance = advance[0]
advance_paid = flt(advance.amount, self.precision("advance_paid"))
formatted_advance_paid = fmt_money(advance_paid, precision=self.precision("advance_paid"),
currency=advance.account_currency)

View File

@ -7,7 +7,7 @@ from frappe.utils import cint, flt, cstr, get_link_to_form, today, getdate
from frappe import _
import frappe.defaults
from erpnext.accounts.utils import get_fiscal_year
from erpnext.accounts.general_ledger import make_gl_entries, delete_gl_entries, process_gl_map
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries, process_gl_map
from erpnext.controllers.accounts_controller import AccountsController
from erpnext.stock.stock_ledger import get_valuation_rate
from erpnext.stock import get_warehouse_account_map
@ -23,9 +23,9 @@ class StockController(AccountsController):
self.validate_serialized_batch()
self.validate_customer_provided_item()
def make_gl_entries(self, gl_entries=None, repost_future_gle=True, from_repost=False):
def make_gl_entries(self, gl_entries=None):
if self.docstatus == 2:
delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
if cint(erpnext.is_perpetual_inventory_enabled(self.company)):
warehouse_account = get_warehouse_account_map(self.company)
@ -33,16 +33,12 @@ class StockController(AccountsController):
if self.docstatus==1:
if not gl_entries:
gl_entries = self.get_gl_entries(warehouse_account)
make_gl_entries(gl_entries, from_repost=from_repost)
make_gl_entries(gl_entries)
if (repost_future_gle or self.flags.repost_future_gle):
items, warehouses = self.get_items_and_warehouses()
update_gl_entries_after(self.posting_date, self.posting_time, warehouses, items,
warehouse_account, company=self.company)
elif self.doctype in ['Purchase Receipt', 'Purchase Invoice'] and self.docstatus == 1:
gl_entries = []
gl_entries = self.get_asset_gl_entry(gl_entries)
make_gl_entries(gl_entries, from_repost=from_repost)
make_gl_entries(gl_entries)
def validate_serialized_batch(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@ -274,21 +270,21 @@ class StockController(AccountsController):
"batch_no": cstr(d.get("batch_no")).strip(),
"serial_no": d.get("serial_no"),
"project": d.get("project") or self.get('project'),
"is_cancelled": self.docstatus==2 and "Yes" or "No"
"is_cancelled": 1 if self.docstatus==2 else 0
})
sl_dict.update(args)
return sl_dict
def make_sl_entries(self, sl_entries, is_amended=None, allow_negative_stock=False,
def make_sl_entries(self, sl_entries, allow_negative_stock=False,
via_landed_cost_voucher=False):
from erpnext.stock.stock_ledger import make_sl_entries
make_sl_entries(sl_entries, is_amended, allow_negative_stock, via_landed_cost_voucher)
make_sl_entries(sl_entries, allow_negative_stock, via_landed_cost_voucher)
def make_gl_entries_on_cancel(self, repost_future_gle=True):
def make_gl_entries_on_cancel(self):
if frappe.db.sql("""select name from `tabGL Entry` where voucher_type=%s
and voucher_no=%s""", (self.doctype, self.name)):
self.make_gl_entries(repost_future_gle=repost_future_gle)
self.make_gl_entries()
def get_serialized_items(self):
serialized_items = []
@ -391,29 +387,6 @@ class StockController(AccountsController):
if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'):
d.allow_zero_valuation_rate = 1
def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
warehouse_account=None, company=None):
def _delete_gl_entries(voucher_type, voucher_no):
frappe.db.sql("""delete from `tabGL Entry`
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
if not warehouse_account:
warehouse_account = get_warehouse_account_map(company)
future_stock_vouchers = get_future_stock_vouchers(posting_date, posting_time, for_warehouses, for_items)
gle = get_voucherwise_gl_entries(future_stock_vouchers, posting_date)
for voucher_type, voucher_no in future_stock_vouchers:
existing_gle = gle.get((voucher_type, voucher_no), [])
voucher_obj = frappe.get_doc(voucher_type, voucher_no)
expected_gle = voucher_obj.get_gl_entries(warehouse_account)
if expected_gle:
if not existing_gle or not compare_existing_and_expected_gle(existing_gle, expected_gle):
_delete_gl_entries(voucher_type, voucher_no)
voucher_obj.make_gl_entries(gl_entries=expected_gle, repost_future_gle=False, from_repost=True)
else:
_delete_gl_entries(voucher_type, voucher_no)
def compare_existing_and_expected_gle(existing_gle, expected_gle):
matched = True
for entry in expected_gle:
@ -430,36 +403,3 @@ def compare_existing_and_expected_gle(existing_gle, expected_gle):
matched = False
break
return matched
def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, for_items=None):
future_stock_vouchers = []
values = []
condition = ""
if for_items:
condition += " and item_code in ({})".format(", ".join(["%s"] * len(for_items)))
values += for_items
if for_warehouses:
condition += " and warehouse in ({})".format(", ".join(["%s"] * len(for_warehouses)))
values += for_warehouses
for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no
from `tabStock Ledger Entry` sle
where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) {condition}
order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc for update""".format(condition=condition),
tuple([posting_date, posting_time] + values), as_dict=True):
future_stock_vouchers.append([d.voucher_type, d.voucher_no])
return future_stock_vouchers
def get_voucherwise_gl_entries(future_stock_vouchers, posting_date):
gl_entries = {}
if future_stock_vouchers:
for d in frappe.db.sql("""select * from `tabGL Entry`
where posting_date >= %s and voucher_no in (%s)""" %
('%s', ', '.join(['%s']*len(future_stock_vouchers))),
tuple([posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1):
gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d)
return gl_entries

24
erpnext/crm/utils.py Normal file
View File

@ -0,0 +1,24 @@
import frappe
def update_lead_phone_numbers(contact, method):
if contact.phone_nos:
contact_lead = contact.get_link_for("Lead")
if contact_lead:
phone = mobile_no = contact.phone_nos[0].phone
if len(contact.phone_nos) > 1:
# get the default phone number
primary_phones = [phone_doc.phone for phone_doc in contact.phone_nos if phone_doc.is_primary_phone]
if primary_phones:
phone = primary_phones[0]
# get the default mobile number
primary_mobile_nos = [phone_doc.phone for phone_doc in contact.phone_nos if phone_doc.is_primary_mobile_no]
if primary_mobile_nos:
mobile_no = primary_mobile_nos[0]
lead = frappe.get_doc("Lead", contact_lead)
lead.phone = phone
lead.mobile_no = mobile_no
lead.save()

View File

@ -10,7 +10,7 @@ from frappe.utils import money_in_words
from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
from frappe.utils.csvutils import getlink
from erpnext.controllers.accounts_controller import AccountsController
from erpnext.accounts.general_ledger import delete_gl_entries
from erpnext.accounts.general_ledger import make_reverse_gl_entries
class Fees(AccountsController):
@ -81,7 +81,8 @@ class Fees(AccountsController):
frappe.msgprint(_("Payment request {0} created").format(getlink("Payment Request", pr.name)))
def on_cancel(self):
delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name, cancel=True)
# frappe.db.set(self, 'status', 'Cancelled')

View File

@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Video", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Video
() => frappe.tests.make('Video', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -1,10 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
class TestVideo(unittest.TestCase):
pass

View File

@ -1,8 +0,0 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Video', {
refresh: function(frm) {
}
});

View File

@ -1,112 +0,0 @@
{
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:title",
"creation": "2018-10-17 05:47:13.087395",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"title",
"provider",
"url",
"column_break_4",
"publish_date",
"duration",
"section_break_7",
"description"
],
"fields": [
{
"fieldname": "title",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Title",
"reqd": 1,
"unique": 1
},
{
"fieldname": "description",
"fieldtype": "Text Editor",
"in_list_view": 1,
"label": "Description",
"reqd": 1
},
{
"fieldname": "duration",
"fieldtype": "Data",
"label": "Duration"
},
{
"fieldname": "url",
"fieldtype": "Data",
"in_list_view": 1,
"label": "URL",
"reqd": 1
},
{
"fieldname": "publish_date",
"fieldtype": "Date",
"label": "Publish Date"
},
{
"fieldname": "provider",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Provider",
"options": "YouTube\nVimeo",
"reqd": 1
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_7",
"fieldtype": "Section Break"
}
],
"modified": "2019-06-12 12:36:48.753092",
"modified_by": "Administrator",
"module": "Education",
"name": "Video",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Academics User",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Instructor",
"share": 1,
"write": 1
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "LMS User",
"share": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -1,13 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class Video(Document):
def get_video(self):
pass

View File

@ -80,6 +80,7 @@ frappe.ui.form.on('Clinical Procedure', {
frappe.call({
method: 'complete_procedure',
doc: frm.doc,
freeze: true,
callback: function(r) {
if (r.message) {
frappe.show_alert({
@ -87,8 +88,8 @@ frappe.ui.form.on('Clinical Procedure', {
['<a class="bold" href="#Form/Stock Entry/'+ r.message + '">' + r.message + '</a>']),
indicator: 'green'
});
frm.reload_doc();
}
frm.reload_doc();
}
});
}
@ -111,9 +112,10 @@ frappe.ui.form.on('Clinical Procedure', {
frappe.call({
doc: frm.doc,
method: 'make_material_receipt',
freeze: true,
callback: function(r) {
if (!r.exc) {
cur_frm.reload_doc();
frm.reload_doc();
let doclist = frappe.model.sync(r.message);
frappe.set_route('Form', doclist[0].doctype, doclist[0].name);
}
@ -122,7 +124,7 @@ frappe.ui.form.on('Clinical Procedure', {
}
);
} else {
cur_frm.reload_doc();
frm.reload_doc();
}
}
}

View File

@ -87,7 +87,8 @@ class ClinicalProcedure(Document):
else:
frappe.throw(_('Please set Customer in Patient {0}').format(frappe.bold(self.patient)), title=_('Customer Not Found'))
frappe.db.set_value('Clinical Procedure', self.name, 'status', 'Completed')
self.db_set('status', 'Completed')
if self.consume_stock and self.items:
return stock_entry
@ -245,9 +246,9 @@ def make_procedure(source_name, target_doc=None):
def insert_clinical_procedure_to_medical_record(doc):
subject = cstr(doc.procedure_template)
subject = frappe.bold(_("Clinical Procedure conducted: ")) + cstr(doc.procedure_template) + "<br>"
if doc.practitioner:
subject += ' ' + doc.practitioner
subject += frappe.bold(_('Healthcare Practitioner: ')) + doc.practitioner
if subject and doc.notes:
subject += '<br/>' + doc.notes

View File

@ -24,6 +24,8 @@ erpnext.ExerciseEditor = Class.extend({
this.exercise_cards = $('<div class="exercise-cards"></div>').appendTo(this.wrapper);
this.row = $('<div class="exercise-row"></div>').appendTo(this.wrapper);
let me = this;
this.exercise_toolbar.find(".btn-add")
@ -32,7 +34,7 @@ erpnext.ExerciseEditor = Class.extend({
me.show_add_card_dialog(frm);
});
if (frm.doc.steps_table.length > 0) {
if (frm.doc.steps_table && frm.doc.steps_table.length > 0) {
this.make_cards(frm);
this.make_buttons(frm);
}
@ -41,7 +43,6 @@ erpnext.ExerciseEditor = Class.extend({
make_cards: function(frm) {
var me = this;
$(me.exercise_cards).empty();
this.row = $('<div class="exercise-row"></div>').appendTo(me.exercise_cards);
$.each(frm.doc.steps_table, function(i, step) {
$(repl(`
@ -78,6 +79,7 @@ erpnext.ExerciseEditor = Class.extend({
frm.doc.steps_table.pop(id);
frm.refresh_field('steps_table');
$('#col-'+id).remove();
frm.dirty();
}, 300);
});
},
@ -106,7 +108,10 @@ erpnext.ExerciseEditor = Class.extend({
],
primary_action: function() {
let data = d.get_values();
let i = frm.doc.steps_table.length;
let i = 0;
if (frm.doc.steps_table) {
i = frm.doc.steps_table.length;
}
$(repl(`
<div class="exercise-col col-sm-4" id="%(col_id)s">
<div class="card h-100 exercise-card" id="%(card_id)s">
@ -165,9 +170,10 @@ erpnext.ExerciseEditor = Class.extend({
frm.doc.steps_table[id].image = data.image;
frm.doc.steps_table[id].description = data.step_description;
refresh_field('steps_table');
frm.dirty();
new_dialog.hide();
},
primary_action_label: __("Save"),
primary_action_label: __("Edit"),
});
new_dialog.set_values({

View File

@ -288,23 +288,23 @@ def insert_lab_test_to_medical_record(doc):
table_row = False
subject = cstr(doc.lab_test_name)
if doc.practitioner:
subject += " "+ doc.practitioner
subject += frappe.bold(_("Healthcare Practitioner: "))+ doc.practitioner + "<br>"
if doc.normal_test_items:
item = doc.normal_test_items[0]
comment = ""
if item.lab_test_comment:
comment = str(item.lab_test_comment)
table_row = item.lab_test_name
table_row = frappe.bold(_("Lab Test Conducted: ")) + item.lab_test_name
if item.lab_test_event:
table_row += " " + item.lab_test_event
table_row += frappe.bold(_("Lab Test Event: ")) + item.lab_test_event
if item.result_value:
table_row += " " + item.result_value
table_row += " " + frappe.bold(_("Lab Test Result: ")) + item.result_value
if item.normal_range:
table_row += " normal_range("+item.normal_range+")"
table_row += " "+comment
table_row += " " + _("Normal Range:") + item.normal_range
table_row += " " + comment
elif doc.special_test_items:
item = doc.special_test_items[0]
@ -316,12 +316,12 @@ def insert_lab_test_to_medical_record(doc):
item = doc.sensitivity_test_items[0]
if item.antibiotic and item.antibiotic_sensitivity:
table_row = item.antibiotic +" "+ item.antibiotic_sensitivity
table_row = item.antibiotic + " " + item.antibiotic_sensitivity
if table_row:
subject += "<br/>"+table_row
subject += "<br>" + table_row
if doc.lab_test_comment:
subject += "<br/>"+ cstr(doc.lab_test_comment)
subject += "<br>" + cstr(doc.lab_test_comment)
medical_record = frappe.new_doc("Patient Medical Record")
medical_record.patient = doc.patient

View File

@ -18,6 +18,9 @@ class PatientEncounter(Document):
def after_insert(self):
insert_encounter_to_medical_record(self)
def on_submit(self):
update_encounter_medical_record(self)
def on_cancel(self):
if self.appointment:
frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Open')
@ -66,22 +69,26 @@ def delete_medical_record(encounter):
frappe.db.delete_doc_if_exists('Patient Medical Record', 'reference_name', encounter.name)
def set_subject_field(encounter):
subject = encounter.practitioner + '\n'
subject = frappe.bold(_('Healthcare Practitioner: ')) + encounter.practitioner + '<br>'
if encounter.symptoms:
subject += _('Symptoms: ') + cstr(encounter.symptoms) + '\n'
subject += frappe.bold(_('Symptoms: ')) + '<br>'
for entry in encounter.symptoms:
subject += cstr(entry.complaint) + '<br>'
else:
subject += _('No Symptoms') + '\n'
subject += frappe.bold(_('No Symptoms')) + '<br>'
if encounter.diagnosis:
subject += _('Diagnosis: ') + cstr(encounter.diagnosis) + '\n'
subject += frappe.bold(_('Diagnosis: ')) + '<br>'
for entry in encounter.diagnosis:
subject += cstr(entry.diagnosis) + '<br>'
else:
subject += _('No Diagnosis') + '\n'
subject += frappe.bold(_('No Diagnosis')) + '<br>'
if encounter.drug_prescription:
subject += '\n' + _('Drug(s) Prescribed.')
subject += '<br>' + _('Drug(s) Prescribed.')
if encounter.lab_test_prescription:
subject += '\n' + _('Test(s) Prescribed.')
subject += '<br>' + _('Test(s) Prescribed.')
if encounter.procedure_prescription:
subject += '\n' + _('Procedure(s) Prescribed.')
subject += '<br>' + _('Procedure(s) Prescribed.')
return subject

View File

@ -57,7 +57,7 @@
},
{
"fieldname": "subject",
"fieldtype": "Small Text",
"fieldtype": "Text Editor",
"ignore_xss_filter": 1,
"label": "Subject"
},
@ -125,7 +125,7 @@
],
"in_create": 1,
"links": [],
"modified": "2020-03-23 19:26:59.308383",
"modified": "2020-04-29 12:26:57.679402",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Patient Medical Record",

View File

@ -21,8 +21,14 @@ class TherapyPlan(Document):
self.status = 'Completed'
def set_totals(self):
total_sessions = sum([int(d.no_of_sessions) for d in self.get('therapy_plan_details')])
total_sessions_completed = sum([int(d.sessions_completed) for d in self.get('therapy_plan_details')])
total_sessions = 0
total_sessions_completed = 0
for entry in self.therapy_plan_details:
if entry.no_of_sessions:
total_sessions += entry.no_of_sessions
if entry.sessions_completed:
total_sessions_completed += entry.sessions_completed
self.db_set('total_sessions', total_sessions)
self.db_set('total_sessions_completed', total_sessions_completed)

View File

@ -13,23 +13,92 @@ frappe.ui.form.on('Therapy Session', {
refresh: function(frm) {
if (!frm.doc.__islocal) {
let target = 0;
let completed = 0;
$.each(frm.doc.exercises, function(_i, e) {
target += e.counts_target;
completed += e.counts_completed;
});
frm.dashboard.add_indicator(__('Counts Targetted: {0}', [target]), 'blue');
frm.dashboard.add_indicator(__('Counts Completed: {0}', [completed]), (completed < target) ? 'orange' : 'green');
frm.dashboard.add_indicator(__('Counts Targeted: {0}', [frm.doc.total_counts_targeted]), 'blue');
frm.dashboard.add_indicator(__('Counts Completed: {0}', [frm.doc.total_counts_completed]),
(frm.doc.total_counts_completed < frm.doc.total_counts_targeted) ? 'orange' : 'green');
}
if (frm.doc.docstatus === 1) {
frm.add_custom_button(__('Patient Assessment'),function() {
frm.add_custom_button(__('Patient Assessment'), function() {
frappe.model.open_mapped_doc({
method: 'erpnext.healthcare.doctype.patient_assessment.patient_assessment.create_patient_assessment',
frm: frm,
})
}, 'Create');
frm.add_custom_button(__('Sales Invoice'), function() {
frappe.model.open_mapped_doc({
method: 'erpnext.healthcare.doctype.therapy_session.therapy_session.invoice_therapy_session',
frm: frm,
})
}, 'Create');
}
},
patient: function(frm) {
if (frm.doc.patient) {
frappe.call({
'method': 'erpnext.healthcare.doctype.patient.patient.get_patient_detail',
args: {
patient: frm.doc.patient
},
callback: function (data) {
let age = '';
if (data.message.dob) {
age = calculate_age(data.message.dob);
} else if (data.message.age) {
age = data.message.age;
if (data.message.age_as_on) {
age = __('{0} as on {1}', [age, data.message.age_as_on]);
}
}
frm.set_value('patient_age', age);
frm.set_value('gender', data.message.sex);
frm.set_value('patient_name', data.message.patient_name);
}
});
} else {
frm.set_value('patient_age', '');
frm.set_value('gender', '');
frm.set_value('patient_name', '');
}
},
appointment: function(frm) {
if (frm.doc.appointment) {
frappe.call({
'method': 'frappe.client.get',
args: {
doctype: 'Patient Appointment',
name: frm.doc.appointment
},
callback: function(data) {
let values = {
'patient':data.message.patient,
'therapy_type': data.message.therapy_type,
'therapy_plan': data.message.therapy_plan,
'practitioner': data.message.practitioner,
'department': data.message.department,
'start_date': data.message.appointment_date,
'start_time': data.message.appointment_time,
'service_unit': data.message.service_unit,
'company': data.message.company
};
frm.set_value(values);
}
});
} else {
let values = {
'patient': '',
'therapy_type': '',
'therapy_plan': '',
'practitioner': '',
'department': '',
'start_date': '',
'start_time': '',
'service_unit': '',
};
frm.set_value(values);
}
},
@ -44,6 +113,8 @@ frappe.ui.form.on('Therapy Session', {
callback: function(data) {
frm.set_value('duration', data.message.default_duration);
frm.set_value('rate', data.message.rate);
frm.set_value('service_unit', data.message.healthcare_service_unit);
frm.set_value('department', data.message.medical_department);
frm.doc.exercises = [];
$.each(data.message.exercises, function(_i, e) {
let exercise = frm.add_child('exercises');

View File

@ -9,9 +9,11 @@
"naming_series",
"appointment",
"patient",
"patient_name",
"patient_age",
"gender",
"column_break_5",
"company",
"therapy_plan",
"therapy_type",
"practitioner",
@ -20,7 +22,6 @@
"duration",
"rate",
"location",
"company",
"column_break_12",
"service_unit",
"start_date",
@ -28,6 +29,10 @@
"invoiced",
"exercises_section",
"exercises",
"section_break_23",
"total_counts_targeted",
"column_break_25",
"total_counts_completed",
"amended_from"
],
"fields": [
@ -159,7 +164,8 @@
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company"
"options": "Company",
"reqd": 1
},
{
"default": "0",
@ -173,11 +179,38 @@
"fieldtype": "Data",
"label": "Patient Age",
"read_only": 1
},
{
"fieldname": "total_counts_targeted",
"fieldtype": "Int",
"label": "Total Counts Targeted",
"read_only": 1
},
{
"fieldname": "total_counts_completed",
"fieldtype": "Int",
"label": "Total Counts Completed",
"read_only": 1
},
{
"fieldname": "section_break_23",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_25",
"fieldtype": "Column Break"
},
{
"fetch_from": "patient.patient_name",
"fieldname": "patient_name",
"fieldtype": "Data",
"label": "Patient Name",
"read_only": 1
}
],
"is_submittable": 1,
"links": [],
"modified": "2020-04-21 13:16:46.378798",
"modified": "2020-04-29 16:49:16.286006",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Therapy Session",

View File

@ -6,10 +6,17 @@ from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe.model.mapper import get_mapped_doc
from frappe import _
from frappe.utils import cstr, getdate
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account, get_income_account
class TherapySession(Document):
def validate(self):
self.set_total_counts()
def on_submit(self):
self.update_sessions_count_in_therapy_plan()
insert_session_medical_record(self)
def on_cancel(self):
self.update_sessions_count_in_therapy_plan(on_cancel=True)
@ -24,6 +31,18 @@ class TherapySession(Document):
entry.sessions_completed += 1
therapy_plan.save()
def set_total_counts(self):
target_total = 0
counts_completed = 0
for entry in self.exercises:
if entry.counts_target:
target_total += entry.counts_target
if entry.counts_completed:
counts_completed += entry.counts_completed
self.db_set('total_counts_targeted', target_total)
self.db_set('total_counts_completed', counts_completed)
@frappe.whitelist()
def create_therapy_session(source_name, target_doc=None):
@ -52,4 +71,62 @@ def create_therapy_session(source_name, target_doc=None):
}
}, target_doc, set_missing_values)
return doc
return doc
@frappe.whitelist()
def invoice_therapy_session(source_name, target_doc=None):
def set_missing_values(source, target):
target.customer = frappe.db.get_value('Patient', source.patient, 'customer')
target.due_date = getdate()
target.debit_to = get_receivable_account(source.company)
item = target.append('items', {})
item = get_therapy_item(source, item)
target.set_missing_values(for_validate=True)
doc = get_mapped_doc('Therapy Session', source_name, {
'Therapy Session': {
'doctype': 'Sales Invoice',
'field_map': [
['patient', 'patient'],
['referring_practitioner', 'practitioner'],
['company', 'company'],
['due_date', 'start_date']
]
}
}, target_doc, set_missing_values)
return doc
def get_therapy_item(therapy, item):
item.item_code = frappe.db.get_value('Therapy Type', therapy.therapy_type, 'item')
item.description = _('Therapy Session Charges: {0}').format(therapy.practitioner)
item.income_account = get_income_account(therapy.practitioner, therapy.company)
item.cost_center = frappe.get_cached_value('Company', therapy.company, 'cost_center')
item.rate = therapy.rate
item.amount = therapy.rate
item.qty = 1
item.reference_dt = 'Therapy Session'
item.reference_dn = therapy.name
return item
def insert_session_medical_record(doc):
subject = frappe.bold(_('Therapy: ')) + cstr(doc.therapy_type) + '<br>'
if doc.therapy_plan:
subject += frappe.bold(_('Therapy Plan: ')) + cstr(doc.therapy_plan) + '<br>'
if doc.practitioner:
subject += frappe.bold(_('Healthcare Practitioner: ')) + doc.practitioner
subject += frappe.bold(_('Total Counts Targeted: ')) + cstr(doc.total_counts_targeted) + '<br>'
subject += frappe.bold(_('Total Counts Completed: ')) + cstr(doc.total_counts_completed) + '<br>'
medical_record = frappe.new_doc('Patient Medical Record')
medical_record.patient = doc.patient
medical_record.subject = subject
medical_record.status = 'Open'
medical_record.communication_date = doc.start_date
medical_record.reference_doctype = 'Therapy Session'
medical_record.reference_name = doc.name
medical_record.reference_owner = doc.owner
medical_record.save(ignore_permissions=True)

View File

@ -35,17 +35,17 @@ def delete_vital_signs_from_medical_record(doc):
def set_subject_field(doc):
subject = ''
if(doc.temperature):
subject += _('Temperature: ') + '\n'+ cstr(doc.temperature) + '. '
if(doc.pulse):
subject += _('Pulse: ') + '\n' + cstr(doc.pulse) + '. '
if(doc.respiratory_rate):
subject += _('Respiratory Rate: ') + '\n' + cstr(doc.respiratory_rate) + '. '
if(doc.bp):
subject += _('BP: ') + '\n' + cstr(doc.bp) + '. '
if(doc.bmi):
subject += _('BMI: ') + '\n' + cstr(doc.bmi) + '. '
if(doc.nutrition_note):
subject += _('Note: ') + '\n' + cstr(doc.nutrition_note) + '. '
if doc.temperature:
subject += frappe.bold(_('Temperature: ')) + cstr(doc.temperature) + '<br>'
if doc.pulse:
subject += frappe.bold(_('Pulse: ')) + cstr(doc.pulse) + '<br>'
if doc.respiratory_rate:
subject += frappe.bold(_('Respiratory Rate: ')) + cstr(doc.respiratory_rate) + '<br>'
if doc.bp:
subject += frappe.bold(_('BP: ')) + cstr(doc.bp) + '<br>'
if doc.bmi:
subject += frappe.bold(_('BMI: ')) + cstr(doc.bmi) + '<br>'
if doc.nutrition_note:
subject += frappe.bold(_('Note: ')) + cstr(doc.nutrition_note) + '<br>'
return subject

View File

@ -250,7 +250,8 @@ doc_events = {
},
"Contact": {
"on_trash": "erpnext.support.doctype.issue.issue.update_issue",
"after_insert": "erpnext.communication.doctype.call_log.call_log.set_caller_information"
"after_insert": "erpnext.communication.doctype.call_log.call_log.set_caller_information",
"validate": "erpnext.crm.utils.update_lead_phone_numbers"
},
"Lead": {
"after_insert": "erpnext.communication.doctype.call_log.call_log.set_caller_information"
@ -537,4 +538,4 @@ global_search_doctypes = {
{'doctype': 'Hotel Room Package', 'index': 3},
{'doctype': 'Hotel Room Type', 'index': 4}
]
}
}

View File

@ -23,7 +23,7 @@
{
"hidden": 0,
"label": "Payroll",
"links": "[\n {\n \"label\": \"Salary Structure\",\n \"name\": \"Salary Structure\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Salary Structure\",\n \"Employee\"\n ],\n \"label\": \"Salary Structure Assignment\",\n \"name\": \"Salary Structure Assignment\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Payroll Entry\",\n \"name\": \"Payroll Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Salary Slip\",\n \"name\": \"Salary Slip\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Payroll Period\",\n \"name\": \"Payroll Period\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Salary Component\",\n \"name\": \"Salary Component\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Additional Salary\",\n \"name\": \"Additional Salary\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Retention Bonus\",\n \"name\": \"Retention Bonus\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Incentive\",\n \"name\": \"Employee Incentive\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Salary Slip\"\n ],\n \"doctype\": \"Salary Slip\",\n \"is_query_report\": true,\n \"label\": \"Salary Register\",\n \"name\": \"Salary Register\",\n \"type\": \"report\"\n }\n]"
"links": "[\n {\n \"label\": \"Salary Structure\",\n \"name\": \"Salary Structure\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Salary Structure\",\n \"Employee\"\n ],\n \"label\": \"Salary Structure Assignment\",\n \"name\": \"Salary Structure Assignment\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Payroll Entry\",\n \"name\": \"Payroll Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Salary Slip\",\n \"name\": \"Salary Slip\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Payroll Period\",\n \"name\": \"Payroll Period\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Income Tax Slab\",\n \"name\": \"Income Tax Slab\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Salary Component\",\n \"name\": \"Salary Component\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Additional Salary\",\n \"name\": \"Additional Salary\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Retention Bonus\",\n \"name\": \"Retention Bonus\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Incentive\",\n \"name\": \"Employee Incentive\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Salary Slip\"\n ],\n \"doctype\": \"Salary Slip\",\n \"is_query_report\": true,\n \"label\": \"Salary Register\",\n \"name\": \"Salary Register\",\n \"type\": \"report\"\n }\n]"
},
{
"hidden": 0,
@ -73,7 +73,7 @@
{
"hidden": 0,
"label": "Employee Tax and Benefits",
"links": "[\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Tax Exemption Declaration\",\n \"name\": \"Employee Tax Exemption Declaration\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Tax Exemption Proof Submission\",\n \"name\": \"Employee Tax Exemption Proof Submission\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Benefit Application\",\n \"name\": \"Employee Benefit Application\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Benefit Claim\",\n \"name\": \"Employee Benefit Claim\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Tax Exemption Category\",\n \"name\": \"Employee Tax Exemption Category\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Tax Exemption Sub Category\",\n \"name\": \"Employee Tax Exemption Sub Category\",\n \"type\": \"doctype\"\n }\n]"
"links": "[\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Tax Exemption Declaration\",\n \"name\": \"Employee Tax Exemption Declaration\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Tax Exemption Proof Submission\",\n \"name\": \"Employee Tax Exemption Proof Submission\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\",\n \"Payroll Period\"\n ],\n \"label\": \"Employee Other Income\",\n \"name\": \"Employee Other Income\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Benefit Application\",\n \"name\": \"Employee Benefit Application\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Benefit Claim\",\n \"name\": \"Employee Benefit Claim\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Tax Exemption Category\",\n \"name\": \"Employee Tax Exemption Category\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Tax Exemption Sub Category\",\n \"name\": \"Employee Tax Exemption Sub Category\",\n \"type\": \"doctype\"\n }\n]"
}
],
"category": "Modules",
@ -88,7 +88,7 @@
"idx": 0,
"is_standard": 1,
"label": "HR",
"modified": "2020-04-01 11:28:50.860012",
"modified": "2020-04-29 20:29:22.114309",
"modified_by": "Administrator",
"module": "HR",
"name": "HR",

View File

@ -76,6 +76,7 @@ class ExpenseClaim(AccountsController):
def on_cancel(self):
self.update_task_and_project()
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
if self.payable_account:
self.make_gl_entries(cancel=True)
@ -260,10 +261,17 @@ class ExpenseClaim(AccountsController):
if not expense.default_account or not validate:
expense.default_account = get_expense_claim_account(expense.expense_type, self.company)["account"]
def update_reimbursed_amount(doc):
amt = frappe.db.sql("""select ifnull(sum(debit_in_account_currency), 0) as amt
def update_reimbursed_amount(doc, jv=None):
condition = ""
if jv:
condition += "and voucher_no = '{0}'".format(jv)
amt = frappe.db.sql("""select ifnull(sum(debit_in_account_currency), 0) - ifnull(sum(credit_in_account_currency), 0)as amt
from `tabGL Entry` where against_voucher_type = 'Expense Claim' and against_voucher = %s
and party = %s """, (doc.name, doc.employee) ,as_dict=1)[0].amt
and party = %s {condition}""".format(condition=condition), #nosec
(doc.name, doc.employee) ,as_dict=1)[0].amt
doc.total_amount_reimbursed = amt
frappe.db.set_value("Expense Claim", doc.name , "total_amount_reimbursed", amt)

View File

@ -80,6 +80,7 @@
},
{
"collapsible": 1,
"collapsible_depends_on": "other_taxes_and_charges",
"fieldname": "taxes_and_charges_on_income_tax_section",
"fieldtype": "Section Break",
"label": "Taxes and Charges on Income Tax"
@ -93,13 +94,15 @@
],
"is_submittable": 1,
"links": [],
"modified": "2020-04-24 12:28:36.805904",
"modified": "2020-04-29 15:08:21.436120",
"modified_by": "Administrator",
"module": "HR",
"name": "Income Tax Slab",
"owner": "Administrator",
"permissions": [
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
@ -109,9 +112,11 @@
"report": 1,
"role": "System Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
@ -126,6 +131,7 @@
"write": 1
},
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
@ -138,20 +144,6 @@
"share": 1,
"submit": 1,
"write": 1
},
{
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Administrator",
"share": 1,
"submit": 1,
"write": 1
}
],
"sort_field": "modified",

View File

@ -249,7 +249,7 @@ const submit_salary_slip = function (frm) {
let make_bank_entry = function (frm) {
var doc = frm.doc;
if (doc.company && doc.start_date && doc.end_date && doc.payment_account) {
if (doc.payment_account) {
return frappe.call({
doc: cur_frm.doc,
method: "make_payment_entry",
@ -262,7 +262,8 @@ let make_bank_entry = function (frm) {
freeze_message: __("Creating Payment Entries......")
});
} else {
frappe.msgprint(__("Company, Payment Account, From Date and To Date is mandatory"));
frappe.msgprint(__("Payment Account is mandatory"));
frm.scroll_to_field('payment_account');
}
};

View File

@ -227,7 +227,7 @@
{
"default": "0",
"depends_on": "eval:doc.type == \"Deduction\" && !doc.variable_based_on_taxable_salary",
"description": "If checked, the full amount will be deducted from taxable income before calculating income tax. Otherwise, it can be exempted via Employee Tax Exemption Declaration.",
"description": "If checked, the full amount will be deducted from taxable income before calculating income tax without any declaration or proof submission.",
"fieldname": "exempted_from_income_tax",
"fieldtype": "Check",
"label": "Exempted from Income Tax"
@ -235,7 +235,7 @@
],
"icon": "fa fa-flag",
"links": [],
"modified": "2020-04-24 14:50:28.994054",
"modified": "2020-04-28 15:46:45.252945",
"modified_by": "Administrator",
"module": "HR",
"name": "Salary Component",

View File

@ -549,15 +549,16 @@ class SalarySlip(TransactionBase):
remaining_sub_periods = get_period_factor(self.employee,
self.start_date, self.end_date, self.payroll_frequency, payroll_period)[1]
# get taxable_earnings, paid_taxes for previous period
previous_taxable_earnings = self.get_taxable_earnings_for_prev_period(payroll_period.start_date, self.start_date)
previous_taxable_earnings = self.get_taxable_earnings_for_prev_period(payroll_period.start_date,
self.start_date, tax_slab.allow_tax_exemption)
previous_total_paid_taxes = self.get_tax_paid_in_period(payroll_period.start_date, self.start_date, tax_component)
# get taxable_earnings for current period (all days)
current_taxable_earnings = self.get_taxable_earnings()
current_taxable_earnings = self.get_taxable_earnings(tax_slab.allow_tax_exemption)
future_structured_taxable_earnings = current_taxable_earnings.taxable_earnings * (math.ceil(remaining_sub_periods) - 1)
# get taxable_earnings, addition_earnings for current actual payment days
current_taxable_earnings_for_payment_days = self.get_taxable_earnings(based_on_payment_days=1)
current_taxable_earnings_for_payment_days = self.get_taxable_earnings(tax_slab.allow_tax_exemption, based_on_payment_days=1)
current_structured_taxable_earnings = current_taxable_earnings_for_payment_days.taxable_earnings
current_additional_earnings = current_taxable_earnings_for_payment_days.additional_income
current_additional_earnings_with_full_tax = current_taxable_earnings_for_payment_days.additional_income_with_full_tax
@ -616,7 +617,7 @@ class SalarySlip(TransactionBase):
return income_tax_slab_doc
def get_taxable_earnings_for_prev_period(self, start_date, end_date):
def get_taxable_earnings_for_prev_period(self, start_date, end_date, allow_tax_exemption=False):
taxable_earnings = frappe.db.sql("""
select sum(sd.amount)
from
@ -636,24 +637,26 @@ class SalarySlip(TransactionBase):
})
taxable_earnings = flt(taxable_earnings[0][0]) if taxable_earnings else 0
exempted_amount = frappe.db.sql("""
select sum(sd.amount)
from
`tabSalary Detail` sd join `tabSalary Slip` ss on sd.parent=ss.name
where
sd.parentfield='deductions'
and sd.exempted_from_income_tax=1
and is_flexible_benefit=0
and ss.docstatus=1
and ss.employee=%(employee)s
and ss.start_date between %(from_date)s and %(to_date)s
and ss.end_date between %(from_date)s and %(to_date)s
""", {
"employee": self.employee,
"from_date": start_date,
"to_date": end_date
})
exempted_amount = flt(exempted_amount[0][0]) if exempted_amount else 0
exempted_amount = 0
if allow_tax_exemption:
exempted_amount = frappe.db.sql("""
select sum(sd.amount)
from
`tabSalary Detail` sd join `tabSalary Slip` ss on sd.parent=ss.name
where
sd.parentfield='deductions'
and sd.exempted_from_income_tax=1
and is_flexible_benefit=0
and ss.docstatus=1
and ss.employee=%(employee)s
and ss.start_date between %(from_date)s and %(to_date)s
and ss.end_date between %(from_date)s and %(to_date)s
""", {
"employee": self.employee,
"from_date": start_date,
"to_date": end_date
})
exempted_amount = flt(exempted_amount[0][0]) if exempted_amount else 0
return taxable_earnings - exempted_amount
@ -681,7 +684,7 @@ class SalarySlip(TransactionBase):
return total_tax_paid
def get_taxable_earnings(self, based_on_payment_days=0):
def get_taxable_earnings(self, allow_tax_exemption=False, based_on_payment_days=0):
joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
["date_of_joining", "relieving_date"])
@ -715,12 +718,13 @@ class SalarySlip(TransactionBase):
else:
taxable_earnings += amount
for ded in self.deductions:
if ded.exempted_from_income_tax:
amount = ded.amount
if based_on_payment_days:
amount = self.get_amount_based_on_payment_days(ded, joining_date, relieving_date)[0]
taxable_earnings -= flt(amount)
if allow_tax_exemption:
for ded in self.deductions:
if ded.exempted_from_income_tax:
amount = ded.amount
if based_on_payment_days:
amount = self.get_amount_based_on_payment_days(ded, joining_date, relieving_date)[0]
taxable_earnings -= flt(amount)
return frappe._dict({
"taxable_earnings": taxable_earnings,
@ -822,13 +826,13 @@ class SalarySlip(TransactionBase):
for slab in tax_slab.slabs:
if slab.condition and not self.eval_tax_slab_condition(slab.condition, data):
continue
if not slab.to_amount and annual_taxable_earning > slab.from_amount:
tax_amount += (annual_taxable_earning - slab.from_amount) * slab.percent_deduction *.01
if not slab.to_amount and annual_taxable_earning >= slab.from_amount:
tax_amount += (annual_taxable_earning - slab.from_amount + 1) * slab.percent_deduction *.01
continue
if annual_taxable_earning > slab.from_amount and annual_taxable_earning < slab.to_amount:
tax_amount += (annual_taxable_earning - slab.from_amount) * slab.percent_deduction *.01
elif annual_taxable_earning > slab.from_amount and annual_taxable_earning > slab.to_amount:
tax_amount += (slab.to_amount - slab.from_amount) * slab.percent_deduction * .01
if annual_taxable_earning >= slab.from_amount and annual_taxable_earning < slab.to_amount:
tax_amount += (annual_taxable_earning - slab.from_amount + 1) * slab.percent_deduction *.01
elif annual_taxable_earning >= slab.from_amount and annual_taxable_earning >= slab.to_amount:
tax_amount += (slab.to_amount - slab.from_amount + 1) * slab.percent_deduction * .01
# other taxes and charges on income tax
for d in tax_slab.other_taxes_and_charges:

View File

@ -12,7 +12,8 @@ def execute(filters=None):
columns, data, chart = [], [], []
if filters.get('fiscal_year'):
company = erpnext.get_default_company()
period_list = get_period_list(filters.get('fiscal_year'), filters.get('fiscal_year'),"Monthly", company)
period_list = get_period_list(filters.get('fiscal_year'), filters.get('fiscal_year'),
'', '', 'Fiscal Year', 'Monthly', company=company)
columns=get_columns()
data=get_log_data(filters)
chart=get_chart_data(data,period_list)

View File

@ -1,18 +1,17 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "field:loan_security_name",
"creation": "2019-09-02 15:07:08.885593",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"loan_security_name",
"unit_of_measure",
"haircut",
"loan_security_code",
"column_break_3",
"loan_security_type",
"haircut",
"unit_of_measure",
"disabled"
],
"fields": [
@ -66,7 +65,7 @@
}
],
"links": [],
"modified": "2020-04-28 14:07:54.506896",
"modified": "2020-04-29 13:21:26.043492",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Security",

View File

@ -7,4 +7,5 @@ from __future__ import unicode_literals
from frappe.model.document import Document
class LoanSecurity(Document):
pass
def autoname(self):
self.name = self.loan_security_name

View File

@ -62,9 +62,9 @@ class TestProductionPlan(unittest.TestCase):
def test_production_plan_for_existing_ordered_qty(self):
sr1 = create_stock_reconciliation(item_code="Raw Material Item 1",
target="_Test Warehouse - _TC", qty=1, rate=100)
target="_Test Warehouse - _TC", qty=1, rate=110)
sr2 = create_stock_reconciliation(item_code="Raw Material Item 2",
target="_Test Warehouse - _TC", qty=1, rate=100)
target="_Test Warehouse - _TC", qty=1, rate=120)
pln = create_production_plan(item_code='Test Production Item 1', ignore_existing_ordered_qty=0)
self.assertTrue(len(pln.mr_items), 1)
@ -86,9 +86,9 @@ class TestProductionPlan(unittest.TestCase):
def test_production_plan_without_multi_level_for_existing_ordered_qty(self):
sr1 = create_stock_reconciliation(item_code="Raw Material Item 1",
target="_Test Warehouse - _TC", qty=1, rate=100)
target="_Test Warehouse - _TC", qty=1, rate=130)
sr2 = create_stock_reconciliation(item_code="Subassembly Item 1",
target="_Test Warehouse - _TC", qty=1, rate=100)
target="_Test Warehouse - _TC", qty=1, rate=140)
pln = create_production_plan(item_code='Test Production Item 1',
use_multi_level_bom=0, ignore_existing_ordered_qty=0)

View File

@ -62,11 +62,9 @@ def get_member_based_on_subscription(subscription_id, email):
'subscription_id': subscription_id,
'email_id': email
}, order_by="creation desc")
return frappe.get_doc("Member", members[0]['name'])
@frappe.whitelist()
@frappe.whitelist(allow_guest=True)
def trigger_razorpay_subscription(data):
if isinstance(data, six.string_types):
data = json.loads(data)
@ -88,10 +86,13 @@ def trigger_razorpay_subscription(data):
if data.event == "subscription.activated":
member.customer_id = payment.customer_id
member.subscription_start = datetime.fromtimestamp(subscription.start_at)
member.subscription_end = datetime.fromtimestamp(subscription.end_at)
member.subscription_activated = 1
member.save(ignore_permissions=True)
# Update these values anyway
member.subscription_start = datetime.fromtimestamp(subscription.start_at)
member.subscription_end = datetime.fromtimestamp(subscription.end_at)
member.subscription_activated = 1
member.save(ignore_permissions=True)
elif data.event == "subscription.charged":
membership = frappe.new_doc("Membership")
membership.update({

View File

@ -1,6 +1,7 @@
execute:import unidecode # new requirement
erpnext.patches.v8_0.move_perpetual_inventory_setting
erpnext.patches.v8_9.set_print_zero_amount_taxes
erpnext.patches.v12_0.update_is_cancelled_field
erpnext.patches.v11_0.rename_production_order_to_work_order
erpnext.patches.v11_0.refactor_naming_series
erpnext.patches.v11_0.refactor_autoname_naming
@ -261,7 +262,6 @@ erpnext.patches.v6_19.comment_feed_communication
erpnext.patches.v6_21.fix_reorder_level
erpnext.patches.v6_21.rename_material_request_fields
erpnext.patches.v6_23.update_stopped_status_to_closed
erpnext.patches.v6_24.repost_valuation_rate_for_serialized_items
erpnext.patches.v6_24.set_recurring_id
erpnext.patches.v6_20x.set_compact_print
execute:frappe.delete_doc_if_exists("Web Form", "contact") #2016-03-10
@ -315,7 +315,6 @@ erpnext.patches.v7_0.set_material_request_type_in_item
erpnext.patches.v7_0.rename_examination_to_assessment
erpnext.patches.v7_0.set_portal_settings
erpnext.patches.v7_0.update_change_amount_account
erpnext.patches.v7_0.repost_future_gle_for_purchase_invoice
erpnext.patches.v7_0.fix_duplicate_icons
erpnext.patches.v7_0.repost_gle_for_pos_sales_return
erpnext.patches.v7_1.update_total_billing_hours
@ -674,4 +673,5 @@ erpnext.patches.v12_0.repost_stock_ledger_entries_for_target_warehouse
erpnext.patches.v12_0.update_end_date_and_status_in_email_campaign
erpnext.patches.v13_0.move_tax_slabs_from_payroll_period_to_income_tax_slab #123
erpnext.patches.v12_0.fix_quotation_expired_status
erpnext.patches.v12_0.update_appointment_reminder_scheduler_entry
erpnext.patches.v12_0.update_appointment_reminder_scheduler_entry
erpnext.patches.v12_0.retain_permission_rules_for_video_doctype

View File

@ -24,9 +24,9 @@ def execute():
doc = frappe.get_doc("Purchase Receipt", d.name)
doc.docstatus = 2
doc.make_gl_entries_on_cancel(repost_future_gle=False)
doc.make_gl_entries_on_cancel()
# update gl entries for submit state of PR
doc.docstatus = 1
doc.make_gl_entries(repost_future_gle=False)
doc.make_gl_entries()

View File

@ -19,7 +19,7 @@ def execute():
doc.db_update()
delete_gle_for_voucher(doc.name)
doc.make_gl_entries(repost_future_gle=False)
doc.make_gl_entries()
def delete_gle_for_voucher(voucher_no):
frappe.db.sql("""delete from `tabGL Entry` where voucher_no = %(voucher_no)s""",

View File

@ -0,0 +1,21 @@
from __future__ import unicode_literals
import frappe
def execute():
# to retain the roles and permissions from Education Module
# after moving doctype to core
permissions = frappe.db.sql("""
SELECT
*
FROM
`tabDocPerm`
WHERE
parent='Video'
""", as_dict=True)
frappe.reload_doc('core', 'doctype', 'video')
doc = frappe.get_doc('DocType', 'Video')
doc.permissions = []
for perm in permissions:
doc.append('permissions', perm)
doc.save()

View File

@ -6,6 +6,6 @@ def execute():
for batch in frappe.get_all("Batch", fields=["name", "batch_id"]):
batch_qty = frappe.db.get_value("Stock Ledger Entry",
{"docstatus": 1, "batch_no": batch.batch_id, "is_cancelled": "No"},
{"docstatus": 1, "batch_no": batch.batch_id, "is_cancelled": 0},
"sum(actual_qty)") or 0.0
frappe.db.set_value("Batch", batch.name, "batch_qty", batch_qty, update_modified=False)

View File

@ -0,0 +1,15 @@
from __future__ import unicode_literals
import frappe
def execute():
try:
frappe.db.sql("UPDATE `tabStock Ledger Entry` SET is_cancelled = 0 where is_cancelled in ('', NULL, 'No')")
frappe.db.sql("UPDATE `tabSerial No` SET is_cancelled = 0 where is_cancelled in ('', NULL, 'No')")
frappe.db.sql("UPDATE `tabStock Ledger Entry` SET is_cancelled = 1 where is_cancelled = 'Yes'")
frappe.db.sql("UPDATE `tabSerial No` SET is_cancelled = 1 where is_cancelled = 'Yes'")
frappe.reload_doc("stock", "doctype", "stock_ledger_entry")
frappe.reload_doc("stock", "doctype", "serial_no")
except:
pass

View File

@ -8,7 +8,7 @@ from frappe.utils import flt
def execute():
from erpnext.stock.stock_balance import repost
repost(allow_zero_rate=True, only_actual=True)
frappe.reload_doctype("Account")
warehouse_account = frappe.db.sql("""select name, master_name from tabAccount
@ -43,7 +43,7 @@ def execute():
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
voucher = frappe.get_doc(voucher_type, voucher_no)
voucher.make_gl_entries(repost_future_gle=False)
voucher.make_gl_entries()
frappe.db.commit()
except Exception as e:
print(frappe.get_traceback())

View File

@ -1,28 +0,0 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from frappe.utils import today
from erpnext.accounts.utils import get_fiscal_year
from erpnext.stock.stock_ledger import update_entries_after
def execute():
try:
year_start_date = get_fiscal_year(today())[1]
except:
return
if year_start_date:
items = frappe.db.sql("""select distinct item_code, warehouse from `tabStock Ledger Entry`
where ifnull(serial_no, '') != '' and actual_qty > 0 and incoming_rate=0""", as_dict=1)
for d in items:
try:
update_entries_after({
"item_code": d.item_code,
"warehouse": d.warehouse,
"posting_date": year_start_date
}, allow_zero_rate=True)
except:
pass

View File

@ -1,24 +0,0 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from frappe.utils import cint
from erpnext.stock import get_warehouse_account_map
from erpnext.controllers.stock_controller import update_gl_entries_after
def execute():
company_list = frappe.db.sql_list("""Select name from tabCompany where enable_perpetual_inventory = 1""")
frappe.reload_doc('accounts', 'doctype', 'sales_invoice')
frappe.reload_doctype("Purchase Invoice")
wh_account = get_warehouse_account_map()
for pi in frappe.get_all("Purchase Invoice", fields=["name", "company"], filters={"docstatus": 1, "update_stock": 1}):
if pi.company in company_list:
pi_doc = frappe.get_doc("Purchase Invoice", pi.name)
items, warehouses = pi_doc.get_items_and_warehouses()
update_gl_entries_after(pi_doc.posting_date, pi_doc.posting_time,
warehouses, items, wh_account, company = pi.company)
frappe.db.commit()

View File

@ -8,13 +8,13 @@ from frappe.utils import cint
def execute():
frappe.reload_doctype("Purchase Invoice")
for pi in frappe.db.sql("""select name from `tabPurchase Invoice`
where company in(select name from tabCompany where enable_perpetual_inventory = 1) and
for pi in frappe.db.sql("""select name from `tabPurchase Invoice`
where company in(select name from tabCompany where enable_perpetual_inventory = 1) and
update_stock=1 and docstatus=1 order by posting_date asc""", as_dict=1):
frappe.db.sql("""delete from `tabGL Entry`
frappe.db.sql("""delete from `tabGL Entry`
where voucher_type = 'Purchase Invoice' and voucher_no = %s""", pi.name)
pi_doc = frappe.get_doc("Purchase Invoice", pi.name)
pi_doc.make_gl_entries(repost_future_gle=False)
pi_doc.make_gl_entries()
frappe.db.commit()

View File

@ -50,7 +50,7 @@ erpnext.stock.StockController = frappe.ui.form.Controller.extend({
show_stock_ledger: function() {
var me = this;
if(this.frm.doc.docstatus===1) {
if(this.frm.doc.docstatus > 0) {
cur_frm.add_custom_button(__("Stock Ledger"), function() {
frappe.route_options = {
voucher_no: me.frm.doc.name,
@ -66,7 +66,7 @@ erpnext.stock.StockController = frappe.ui.form.Controller.extend({
show_general_ledger: function() {
var me = this;
if(this.frm.doc.docstatus===1) {
if(this.frm.doc.docstatus > 0) {
cur_frm.add_custom_button(__('Accounting Ledger'), function() {
frappe.route_options = {
voucher_no: me.frm.doc.name,

View File

@ -28,7 +28,8 @@
"fieldtype": "Data",
"in_list_view": 1,
"label": "Client ID",
"reqd": 1
"reqd": 1,
"length": 5
},
{
"fieldname": "consultant",
@ -42,7 +43,8 @@
"fieldtype": "Data",
"in_list_view": 1,
"label": "Consultant ID",
"reqd": 1
"reqd": 1,
"length": 7
},
{
"fieldname": "column_break_2",
@ -102,4 +104,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}
}

View File

@ -335,7 +335,7 @@ class TestSalesOrder(unittest.TestCase):
self.assertEqual(so.get("items")[-1].qty, 7)
self.assertEqual(so.get("items")[-1].amount, 1400)
self.assertEqual(so.status, 'To Deliver and Bill')
def test_remove_item_in_update_child_qty_rate(self):
so = make_sales_order(**{
"item_list": [{
@ -373,7 +373,7 @@ class TestSalesOrder(unittest.TestCase):
"docname": so.get("items")[0].name
}])
update_child_qty_rate('Sales Order', trans_item, so.name)
so.reload()
self.assertEqual(len(so.get("items")), 1)
self.assertEqual(so.status, 'To Deliver and Bill')
@ -760,10 +760,9 @@ class TestSalesOrder(unittest.TestCase):
self.assertEqual(reserved_serial_no, dn.get("items")[0].serial_no)
item_line = dn.get("items")[0]
item_line.serial_no = item_serial_no.name
self.assertRaises(frappe.ValidationError, dn.submit)
item_line = dn.get("items")[0]
item_line.serial_no = reserved_serial_no
self.assertTrue(dn.submit)
dn.submit()
dn.load_from_db()
dn.cancel()
si = make_sales_invoice(so.name)

View File

@ -11,8 +11,8 @@ from erpnext.accounts.doctype.monthly_distribution.monthly_distribution import g
def get_data_column(filters, partner_doctype):
data = []
period_list = get_period_list(filters.fiscal_year, filters.fiscal_year,
filters.period, company=filters.company)
period_list = get_period_list(filters.fiscal_year, filters.fiscal_year, '', '',
'Fiscal Year', filters.period, company=filters.company)
rows = get_data(filters, period_list, partner_doctype)
columns = get_columns(filters, period_list, partner_doctype)

View File

@ -32,7 +32,7 @@ def install(country=None):
{ 'doctype': 'Domain', 'domain': 'Agriculture'},
{ 'doctype': 'Domain', 'domain': 'Non Profit'},
# ensure at least an empty Address Template exists for this Country
# ensure at least an empty Address Template exists for this Country
{'doctype':"Address Template", "country": country},
# item group
@ -271,7 +271,7 @@ def install(country=None):
# Records for the Supplier Scorecard
from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import make_default_records
make_default_records()
make_records(records)
set_up_address_templates(default_country=country)

View File

@ -23,22 +23,19 @@ class Bin(Document):
if not args.get("posting_date"):
args["posting_date"] = nowdate()
# update valuation and qty after transaction for post dated entry
if args.get("is_cancelled") == "Yes" and via_landed_cost_voucher:
return
update_entries_after({
"item_code": self.item_code,
"warehouse": self.warehouse,
"posting_date": args.get("posting_date"),
"posting_time": args.get("posting_time"),
"voucher_no": args.get("voucher_no")
"voucher_no": args.get("voucher_no"),
"sle_id": args.sle_id
}, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher)
def update_qty(self, args):
# update the stock values (for current quantities)
if args.get("voucher_type")=="Stock Reconciliation":
if args.get('is_cancelled') == 'No':
self.actual_qty = args.get("qty_after_transaction")
self.actual_qty = args.get("qty_after_transaction")
else:
self.actual_qty = flt(self.actual_qty) + flt(args.get("actual_qty"))

View File

@ -188,7 +188,7 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend(
}
}
if (doc.docstatus==1) {
if (doc.docstatus > 0) {
this.show_stock_ledger();
if (erpnext.is_perpetual_inventory_enabled(doc.company)) {
this.show_general_ledger();

View File

@ -222,6 +222,7 @@ class DeliveryNote(SellingController):
self.cancel_packing_slips()
self.make_gl_entries_on_cancel()
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
def check_credit_limit(self):
from erpnext.selling.doctype.customer.customer import check_credit_limit

View File

@ -61,54 +61,55 @@ class TestDeliveryNote(unittest.TestCase):
self.assertFalse(get_gl_entries("Delivery Note", dn.name))
def test_delivery_note_gl_entry(self):
company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
# def test_delivery_note_gl_entry(self):
# company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
set_valuation_method("_Test Item", "FIFO")
# set_valuation_method("_Test Item", "FIFO")
make_stock_entry(target="Stores - TCP1", qty=5, basic_rate=100)
# make_stock_entry(target="Stores - TCP1", qty=5, basic_rate=100)
stock_in_hand_account = get_inventory_account('_Test Company with perpetual inventory')
prev_bal = get_balance_on(stock_in_hand_account)
# stock_in_hand_account = get_inventory_account('_Test Company with perpetual inventory')
# prev_bal = get_balance_on(stock_in_hand_account)
dn = create_delivery_note(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', cost_center = 'Main - TCP1', expense_account = "Cost of Goods Sold - TCP1")
# dn = create_delivery_note(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', cost_center = 'Main - TCP1', expense_account = "Cost of Goods Sold - TCP1")
gl_entries = get_gl_entries("Delivery Note", dn.name)
self.assertTrue(gl_entries)
# gl_entries = get_gl_entries("Delivery Note", dn.name)
# self.assertTrue(gl_entries)
stock_value_difference = abs(frappe.db.get_value("Stock Ledger Entry",
{"voucher_type": "Delivery Note", "voucher_no": dn.name}, "stock_value_difference"))
# stock_value_difference = abs(frappe.db.get_value("Stock Ledger Entry",
# {"voucher_type": "Delivery Note", "voucher_no": dn.name}, "stock_value_difference"))
expected_values = {
stock_in_hand_account: [0.0, stock_value_difference],
"Cost of Goods Sold - TCP1": [stock_value_difference, 0.0]
}
for i, gle in enumerate(gl_entries):
self.assertEqual([gle.debit, gle.credit], expected_values.get(gle.account))
# expected_values = {
# stock_in_hand_account: [0.0, stock_value_difference],
# "Cost of Goods Sold - TCP1": [stock_value_difference, 0.0]
# }
# for i, gle in enumerate(gl_entries):
# self.assertEqual([gle.debit, gle.credit], expected_values.get(gle.account))
# check stock in hand balance
bal = get_balance_on(stock_in_hand_account)
self.assertEqual(bal, prev_bal - stock_value_difference)
# # check stock in hand balance
# bal = get_balance_on(stock_in_hand_account)
# self.assertEqual(bal, prev_bal - stock_value_difference)
# back dated incoming entry
make_stock_entry(posting_date=add_days(nowdate(), -2), target="Stores - TCP1",
qty=5, basic_rate=100)
# # back dated incoming entry
# make_stock_entry(posting_date=add_days(nowdate(), -2), target="Stores - TCP1",
# qty=5, basic_rate=100)
gl_entries = get_gl_entries("Delivery Note", dn.name)
self.assertTrue(gl_entries)
# gl_entries = get_gl_entries("Delivery Note", dn.name)
# self.assertTrue(gl_entries)
stock_value_difference = abs(frappe.db.get_value("Stock Ledger Entry",
{"voucher_type": "Delivery Note", "voucher_no": dn.name}, "stock_value_difference"))
# stock_value_difference = abs(frappe.db.get_value("Stock Ledger Entry",
# {"voucher_type": "Delivery Note", "voucher_no": dn.name}, "stock_value_difference"))
expected_values = {
stock_in_hand_account: [0.0, stock_value_difference],
"Cost of Goods Sold - TCP1": [stock_value_difference, 0.0]
}
for i, gle in enumerate(gl_entries):
self.assertEqual([gle.debit, gle.credit], expected_values.get(gle.account))
# expected_values = {
# stock_in_hand_account: [0.0, stock_value_difference],
# "Cost of Goods Sold - TCP1": [stock_value_difference, 0.0]
# }
# for i, gle in enumerate(gl_entries):
# self.assertEqual([gle.debit, gle.credit], expected_values.get(gle.account))
dn.cancel()
self.assertFalse(get_gl_entries("Delivery Note", dn.name))
# dn.cancel()
# self.assertTrue(get_gl_entries("Delivery Note", dn.name))
# set_perpetual_inventory(0, company)
def test_delivery_note_gl_entry_packing_item(self):
company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
@ -147,7 +148,6 @@ class TestDeliveryNote(unittest.TestCase):
self.assertEqual(flt(bal, 2), flt(prev_bal - stock_value_diff, 2))
dn.cancel()
self.assertFalse(get_gl_entries("Delivery Note", dn.name))
def test_serialized(self):
se = make_serialized_item()
@ -464,27 +464,19 @@ class TestDeliveryNote(unittest.TestCase):
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
dn1 = make_delivery_note(so.name)
dn1.set_posting_time = 1
dn1.posting_time = "10:00"
dn1.get("items")[0].qty = 2
dn1.submit()
dn2 = make_delivery_note(so.name)
dn2.get("items")[0].qty = 3
dn2.submit()
dn1.load_from_db()
self.assertEqual(dn1.get("items")[0].billed_amt, 200)
self.assertEqual(dn1.per_billed, 100)
self.assertEqual(dn1.status, "Completed")
dn2 = make_delivery_note(so.name)
dn2.set_posting_time = 1
dn2.posting_time = "08:00"
dn2.get("items")[0].qty = 4
dn2.submit()
dn1.load_from_db()
self.assertEqual(dn1.get("items")[0].billed_amt, 100)
self.assertEqual(dn1.per_billed, 50)
self.assertEqual(dn1.status, "To Bill")
self.assertEqual(dn2.get("items")[0].billed_amt, 400)
self.assertEqual(dn2.get("items")[0].billed_amt, 300)
self.assertEqual(dn2.per_billed, 100)
self.assertEqual(dn2.status, "Completed")
@ -497,8 +489,6 @@ class TestDeliveryNote(unittest.TestCase):
so = make_sales_order()
dn1 = make_delivery_note(so.name)
dn1.set_posting_time = 1
dn1.posting_time = "10:00"
dn1.get("items")[0].qty = 2
dn1.submit()
@ -513,7 +503,6 @@ class TestDeliveryNote(unittest.TestCase):
si2.submit()
dn2 = make_delivery_note(so.name)
dn2.posting_time = "08:00"
dn2.get("items")[0].qty = 5
dn2.submit()

View File

@ -137,7 +137,7 @@ class LandedCostVoucher(Document):
# update stock & gl entries for cancelled state of PR
doc.docstatus = 2
doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True)
doc.make_gl_entries_on_cancel(repost_future_gle=False)
doc.make_gl_entries_on_cancel()
# update stock & gl entries for submit state of PR
doc.docstatus = 1

View File

@ -15,8 +15,9 @@ class TestLandedCostVoucher(unittest.TestCase):
def test_landed_cost_voucher(self):
frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1)
pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", get_multiple_items = True, get_taxes_and_charges = True)
pr = make_purchase_receipt(company="_Test Company with perpetual inventory",
warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1",
get_multiple_items = True, get_taxes_and_charges = True)
last_sle = frappe.db.get_value("Stock Ledger Entry", {
"voucher_type": pr.doctype,
@ -26,7 +27,7 @@ class TestLandedCostVoucher(unittest.TestCase):
},
fieldname=["qty_after_transaction", "stock_value"], as_dict=1)
submit_landed_cost_voucher("Purchase Receipt", pr.name)
submit_landed_cost_voucher("Purchase Receipt", pr.name, pr.company)
pr_lc_value = frappe.db.get_value("Purchase Receipt Item", {"parent": pr.name}, "landed_cost_voucher_amount")
self.assertEqual(pr_lc_value, 25.0)
@ -67,8 +68,9 @@ class TestLandedCostVoucher(unittest.TestCase):
}
for gle in gl_entries:
self.assertEqual(expected_values[gle.account][0], gle.debit)
self.assertEqual(expected_values[gle.account][1], gle.credit)
if not gle.get('is_cancelled'):
self.assertEqual(expected_values[gle.account][0], gle.debit)
self.assertEqual(expected_values[gle.account][1], gle.credit)
def test_landed_cost_voucher_against_purchase_invoice(self):
@ -87,7 +89,7 @@ class TestLandedCostVoucher(unittest.TestCase):
},
fieldname=["qty_after_transaction", "stock_value"], as_dict=1)
submit_landed_cost_voucher("Purchase Invoice", pi.name)
submit_landed_cost_voucher("Purchase Invoice", pi.name, pi.company)
pi_lc_value = frappe.db.get_value("Purchase Invoice Item", {"parent": pi.name},
"landed_cost_voucher_amount")
@ -118,8 +120,9 @@ class TestLandedCostVoucher(unittest.TestCase):
}
for gle in gl_entries:
self.assertEqual(expected_values[gle.account][0], gle.debit)
self.assertEqual(expected_values[gle.account][1], gle.credit)
if not gle.get('is_cancelled'):
self.assertEqual(expected_values[gle.account][0], gle.debit)
self.assertEqual(expected_values[gle.account][1], gle.credit)
def test_landed_cost_voucher_for_serialized_item(self):
@ -134,7 +137,7 @@ class TestLandedCostVoucher(unittest.TestCase):
serial_no_rate = frappe.db.get_value("Serial No", "SN001", "purchase_rate")
submit_landed_cost_voucher("Purchase Receipt", pr.name)
submit_landed_cost_voucher("Purchase Receipt", pr.name, pr.company)
serial_no = frappe.db.get_value("Serial No", "SN001",
["warehouse", "purchase_rate"], as_dict=1)
@ -157,13 +160,13 @@ class TestLandedCostVoucher(unittest.TestCase):
})
pr.submit()
lcv = submit_landed_cost_voucher("Purchase Receipt", pr.name, 123.22)
lcv = submit_landed_cost_voucher("Purchase Receipt", pr.name, pr.company, 123.22)
self.assertEqual(lcv.items[0].applicable_charges, 41.07)
self.assertEqual(lcv.items[2].applicable_charges, 41.08)
def test_multiple_landed_cost_voucher_against_pr(self):
pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1",
pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1",
supplier_warehouse = "Stores - TCP1", do_not_save=True)
pr.append("items", {
@ -176,7 +179,7 @@ class TestLandedCostVoucher(unittest.TestCase):
pr.submit()
lcv1 = make_landed_cost_voucher(receipt_document_type = 'Purchase Receipt',
lcv1 = make_landed_cost_voucher(company = pr.company, receipt_document_type = 'Purchase Receipt',
receipt_document=pr.name, charges=100, do_not_save=True)
lcv1.insert()
@ -187,7 +190,7 @@ class TestLandedCostVoucher(unittest.TestCase):
lcv1.submit()
lcv2 = make_landed_cost_voucher(receipt_document_type = 'Purchase Receipt',
lcv2 = make_landed_cost_voucher(company = pr.company, receipt_document_type = 'Purchase Receipt',
receipt_document=pr.name, charges=100, do_not_save=True)
lcv2.insert()
@ -208,7 +211,7 @@ def make_landed_cost_voucher(** args):
ref_doc = frappe.get_doc(args.receipt_document_type, args.receipt_document)
lcv = frappe.new_doc('Landed Cost Voucher')
lcv.company = '_Test Company'
lcv.company = args.company or '_Test Company'
lcv.distribute_charges_based_on = 'Amount'
lcv.set('purchase_receipts', [{
@ -233,11 +236,11 @@ def make_landed_cost_voucher(** args):
return lcv
def submit_landed_cost_voucher(receipt_document_type, receipt_document, charges=50):
def submit_landed_cost_voucher(receipt_document_type, receipt_document, company, charges=50):
ref_doc = frappe.get_doc(receipt_document_type, receipt_document)
lcv = frappe.new_doc("Landed Cost Voucher")
lcv.company = "_Test Company"
lcv.company = company
lcv.distribute_charges_based_on = 'Amount'
lcv.set("purchase_receipts", [{

View File

@ -92,7 +92,7 @@ erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend
refresh: function() {
var me = this;
this._super();
if(this.frm.doc.docstatus===1) {
if(this.frm.doc.docstatus > 0) {
this.show_stock_ledger();
//removed for temporary
this.show_general_ledger();

View File

@ -196,6 +196,7 @@ class PurchaseReceipt(BuyingController):
# because updating ordered qty in bin depends upon updated ordered qty in PO
self.update_stock_ledger()
self.make_gl_entries_on_cancel()
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
self.delete_auto_created_batches()
def get_current_stock(self):

View File

@ -51,7 +51,7 @@ class TestPurchaseReceipt(unittest.TestCase):
self.assertEqual(current_bin_stock_value, existing_bin_stock_value + 250)
self.assertFalse(get_gl_entries("Purchase Receipt", pr.name))
def test_batched_serial_no_purchase(self):
item = frappe.db.exists("Item", {'item_name': 'Batched Serialized Item'})
if not item:
@ -68,7 +68,7 @@ class TestPurchaseReceipt(unittest.TestCase):
pr = make_purchase_receipt(item_code=item.name, qty=5, rate=500)
self.assertTrue(frappe.db.get_value('Batch', {'item': item.name, 'reference_name': pr.name}))
pr.load_from_db()
batch_no = pr.items[0].batch_no
pr.cancel()
@ -106,7 +106,7 @@ class TestPurchaseReceipt(unittest.TestCase):
self.assertEqual(expected_values[gle.account][1], gle.credit)
pr.cancel()
self.assertFalse(get_gl_entries("Purchase Receipt", pr.name))
self.assertTrue(get_gl_entries("Purchase Receipt", pr.name))
def test_subcontracting(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
@ -375,7 +375,7 @@ class TestPurchaseReceipt(unittest.TestCase):
location = frappe.db.get_value('Asset', assets[0].name, 'location')
self.assertEquals(location, "Test Location")
def test_purchase_return_with_submitted_asset(self):
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_return
@ -397,10 +397,10 @@ class TestPurchaseReceipt(unittest.TestCase):
pr_return = make_purchase_return(pr.name)
self.assertRaises(frappe.exceptions.ValidationError, pr_return.submit)
asset.load_from_db()
asset.cancel()
pr_return.submit()
def test_purchase_receipt_for_enable_allow_cost_center_in_entry_of_bs_account(self):
@ -505,10 +505,13 @@ class TestPurchaseReceipt(unittest.TestCase):
self.assertEquals(pi2.items[1].qty, 1)
def test_stock_transfer_from_purchase_receipt(self):
set_perpetual_inventory(1)
pr = make_purchase_receipt(do_not_save=1)
pr1 = make_purchase_receipt(warehouse = 'Work In Progress - TCP1', company="_Test Company with perpetual inventory")
pr = make_purchase_receipt(company="_Test Company with perpetual inventory",
warehouse = "Stores - TCP1", do_not_save=1)
pr.supplier_warehouse = ''
pr.items[0].from_warehouse = '_Test Warehouse 2 - _TC'
pr.items[0].from_warehouse = 'Work In Progress - TCP1'
pr.submit()
@ -518,31 +521,33 @@ class TestPurchaseReceipt(unittest.TestCase):
self.assertFalse(gl_entries)
expected_sle = {
'_Test Warehouse 2 - _TC': -5,
'_Test Warehouse - _TC': 5
'Work In Progress - TCP1': -5,
'Stores - TCP1': 5
}
for sle in sl_entries:
self.assertEqual(expected_sle[sle.warehouse], sle.actual_qty)
set_perpetual_inventory(0)
def test_stock_transfer_from_purchase_receipt_with_valuation(self):
set_perpetual_inventory(1)
warehouse = frappe.get_doc('Warehouse', '_Test Warehouse 2 - _TC')
warehouse.account = '_Test Account Stock In Hand - _TC'
warehouse = frappe.get_doc('Warehouse', 'Work In Progress - TCP1')
warehouse.account = '_Test Account Stock In Hand - TCP1'
warehouse.save()
pr = make_purchase_receipt(do_not_save=1)
pr.items[0].from_warehouse = '_Test Warehouse 2 - _TC'
pr1 = make_purchase_receipt(warehouse = 'Work In Progress - TCP1',
company="_Test Company with perpetual inventory")
pr = make_purchase_receipt(company="_Test Company with perpetual inventory",
warehouse = "Stores - TCP1", do_not_save=1)
pr.items[0].from_warehouse = 'Work In Progress - TCP1'
pr.supplier_warehouse = ''
pr.append('taxes', {
'charge_type': 'On Net Total',
'account_head': '_Test Account Shipping Charges - _TC',
'account_head': '_Test Account Shipping Charges - TCP1',
'category': 'Valuation and Total',
'cost_center': 'Main - _TC',
'cost_center': 'Main - TCP1',
'description': 'Test',
'rate': 9
})
@ -553,14 +558,14 @@ class TestPurchaseReceipt(unittest.TestCase):
sl_entries = get_sl_entries('Purchase Receipt', pr.name)
expected_gle = [
['Stock In Hand - _TC', 272.5, 0.0],
['_Test Account Stock In Hand - _TC', 0.0, 250.0],
['_Test Account Shipping Charges - _TC', 0.0, 22.5]
['Stock In Hand - TCP1', 272.5, 0.0],
['_Test Account Stock In Hand - TCP1', 0.0, 250.0],
['_Test Account Shipping Charges - TCP1', 0.0, 22.5]
]
expected_sle = {
'_Test Warehouse 2 - _TC': -5,
'_Test Warehouse - _TC': 5
'Work In Progress - TCP1': -5,
'Stores - TCP1': 5
}
for sle in sl_entries:
@ -573,8 +578,6 @@ class TestPurchaseReceipt(unittest.TestCase):
warehouse.account = ''
warehouse.save()
set_perpetual_inventory(0)
def get_sl_entries(voucher_type, voucher_no):
return frappe.db.sql(""" select actual_qty, warehouse, stock_value_difference
@ -582,7 +585,7 @@ def get_sl_entries(voucher_type, voucher_no):
order by posting_time desc""", (voucher_type, voucher_no), as_dict=1)
def get_gl_entries(voucher_type, voucher_no):
return frappe.db.sql("""select account, debit, credit, cost_center
return frappe.db.sql("""select account, debit, credit, cost_center, is_cancelled
from `tabGL Entry` where voucher_type=%s and voucher_no=%s
order by account desc""", (voucher_type, voucher_no), as_dict=1)

View File

@ -83,5 +83,37 @@
}
],
"supplier": "_Test Supplier"
},
{
"buying_price_list": "_Test Price List",
"company": "_Test Company",
"conversion_rate": 1.0,
"currency": "INR",
"doctype": "Purchase Receipt",
"base_grand_total": 5000.0,
"is_subcontracted": "Yes",
"base_net_total": 5000.0,
"items": [
{
"base_amount": 5000.0,
"conversion_factor": 1.0,
"description": "_Test FG Item",
"doctype": "Purchase Receipt Item",
"item_code": "_Test FG Item",
"item_name": "_Test FG Item",
"parentfield": "items",
"qty": 10.0,
"rate": 500.0,
"received_qty": 10.0,
"rejected_qty": 0.0,
"stock_uom": "_Test UOM",
"uom": "_Test UOM",
"warehouse": "_Test Warehouse - _TC",
"cost_center": "Main - _TC"
}
],
"supplier": "_Test Supplier",
"supplier_warehouse": "_Test Warehouse - _TC"
}
]

View File

@ -130,13 +130,17 @@ class SerialNo(StockController):
sle_dict = self.get_stock_ledger_entries(serial_no)
if sle_dict:
if sle_dict.get("incoming", []):
entries["purchase_sle"] = sle_dict["incoming"][0]
sle_list = [sle for sle in sle_dict["incoming"] if sle.is_cancelled == 0]
if sle_list:
entries["purchase_sle"] = sle_list[0]
if len(sle_dict.get("incoming", [])) - len(sle_dict.get("outgoing", [])) > 0:
entries["last_sle"] = sle_dict["incoming"][0]
else:
entries["last_sle"] = sle_dict["outgoing"][0]
entries["delivery_sle"] = sle_dict["outgoing"][0]
sle_list = [sle for sle in sle_dict["outgoing"] if sle.is_cancelled == 0]
if sle_list:
entries["delivery_sle"] = sle_list[0]
return entries
@ -147,11 +151,11 @@ class SerialNo(StockController):
for sle in frappe.db.sql("""
SELECT voucher_type, voucher_no,
posting_date, posting_time, incoming_rate, actual_qty, serial_no
posting_date, posting_time, incoming_rate, actual_qty, serial_no, is_cancelled
FROM
`tabStock Ledger Entry`
WHERE
item_code=%s AND company = %s AND ifnull(is_cancelled, 'No')='No'
item_code=%s AND company = %s
AND (serial_no = %s
OR serial_no like %s
OR serial_no like %s
@ -171,7 +175,7 @@ class SerialNo(StockController):
def on_trash(self):
sl_entries = frappe.db.sql("""select serial_no from `tabStock Ledger Entry`
where serial_no like %s and item_code=%s and ifnull(is_cancelled, 'No')='No'""",
where serial_no like %s and item_code=%s""",
("%%%s%%" % self.name, self.item_code), as_dict=True)
# Find the exact match
@ -221,7 +225,7 @@ def validate_serial_no(sle, item_det):
if serial_nos:
frappe.throw(_("Item {0} is not setup for Serial Nos. Column must be blank").format(sle.item_code),
SerialNoNotRequiredError)
elif sle.is_cancelled == "No":
else:
if serial_nos:
if cint(sle.actual_qty) != flt(sle.actual_qty):
frappe.throw(_("Serial No {0} quantity {1} cannot be a fraction").format(sle.item_code, sle.actual_qty))
@ -239,6 +243,10 @@ def validate_serial_no(sle, item_det):
"delivery_document_no", "delivery_document_type", "warehouse",
"purchase_document_no", "company"], as_dict=1)
if sr and cint(sle.actual_qty) < 0 and sr.warehouse != sle.warehouse:
frappe.throw(_("Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3}")
.format(sle.voucher_type, sle.voucher_no, serial_no, sle.warehouse), SerialNoWarehouseError)
if sr.item_code!=sle.item_code:
if not allow_serial_nos_with_different_item(serial_no, sle):
frappe.throw(_("Serial No {0} does not belong to Item {1}").format(serial_no,
@ -265,7 +273,7 @@ def validate_serial_no(sle, item_det):
frappe.throw(_("Serial No {0} does not belong to Batch {1}").format(serial_no,
sle.batch_no), SerialNoBatchError)
if sle.is_cancelled=="No" and not sr.warehouse:
if not sr.warehouse:
frappe.throw(_("Serial No {0} does not belong to any Warehouse")
.format(serial_no), SerialNoWarehouseError)
@ -311,12 +319,6 @@ def validate_serial_no(sle, item_det):
elif cint(sle.actual_qty) < 0 or not item_det.serial_no_series:
frappe.throw(_("Serial Nos Required for Serialized Item {0}").format(sle.item_code),
SerialNoRequiredError)
elif serial_nos:
for serial_no in serial_nos:
sr = frappe.db.get_value("Serial No", serial_no, ["name", "warehouse"], as_dict=1)
if sr and cint(sle.actual_qty) < 0 and sr.warehouse != sle.warehouse:
frappe.throw(_("Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3}")
.format(sle.voucher_type, sle.voucher_no, serial_no, sle.warehouse))
def validate_material_transfer_entry(sle_doc):
sle_doc.update({
@ -324,7 +326,7 @@ def validate_material_transfer_entry(sle_doc):
"skip_serial_no_validaiton": False
})
if (sle_doc.voucher_type == "Stock Entry" and sle_doc.is_cancelled == "No" and
if (sle_doc.voucher_type == "Stock Entry" and
frappe.get_cached_value("Stock Entry", sle_doc.voucher_no, "purpose") == "Material Transfer"):
if sle_doc.actual_qty < 0:
sle_doc.skip_update_serial_no = True
@ -367,7 +369,7 @@ def allow_serial_nos_with_different_item(sle_serial_no, sle):
stock_entry = frappe.get_cached_doc("Stock Entry", sle.voucher_no)
if stock_entry.purpose in ("Repack", "Manufacture"):
for d in stock_entry.get("items"):
if d.serial_no and (d.s_warehouse if sle.is_cancelled=="No" else d.t_warehouse):
if d.serial_no and (d.s_warehouse or d.t_warehouse):
serial_nos = get_serial_nos(d.serial_no)
if sle_serial_no in serial_nos:
allow_serial_nos = True
@ -376,7 +378,7 @@ def allow_serial_nos_with_different_item(sle_serial_no, sle):
def update_serial_nos(sle, item_det):
if sle.skip_update_serial_no: return
if sle.is_cancelled == "No" and not sle.serial_no and cint(sle.actual_qty) > 0 \
if not sle.serial_no and cint(sle.actual_qty) > 0 \
and item_det.has_serial_no == 1 and item_det.serial_no_series:
serial_nos = get_auto_serial_nos(item_det.serial_no_series, sle.actual_qty)
frappe.db.set(sle, "serial_no", serial_nos)

View File

@ -107,6 +107,9 @@ class StockEntry(StockController):
self.update_work_order()
self.update_stock_ledger()
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
self.make_gl_entries_on_cancel()
self.update_cost_in_project()
self.update_transferred_qty()
@ -651,7 +654,7 @@ class StockEntry(StockController):
if self.docstatus == 2:
sl_entries.reverse()
self.make_sl_entries(sl_entries, self.amended_from and 'Yes' or 'No')
self.make_sl_entries(sl_entries)
def get_gl_entries(self, warehouse_account):
gl_entries = super(StockEntry, self).get_gl_entries(warehouse_account)
@ -674,7 +677,7 @@ class StockEntry(StockController):
multiply_based_on = d.basic_amount if total_basic_amount else d.qty
item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account] += \
(t.amount * multiply_based_on) / divide_based_on
flt(t.amount * multiply_based_on) / divide_based_on
if item_account_wise_additional_cost:
for d in self.get("items"):

View File

@ -24,7 +24,6 @@
{
"company": "_Test Company",
"doctype": "Stock Entry",
"posting_date": "2013-01-25",
"purpose": "Material Issue",
"stock_entry_type": "Material Issue",
"items": [
@ -47,7 +46,6 @@
{
"company": "_Test Company",
"doctype": "Stock Entry",
"posting_date": "2013-01-25",
"purpose": "Material Transfer",
"stock_entry_type": "Material Transfer",
"items": [

View File

@ -149,10 +149,10 @@ class TestStockEntry(unittest.TestCase):
mr.cancel()
self.assertFalse(frappe.db.sql("""select * from `tabStock Ledger Entry`
self.assertTrue(frappe.db.sql("""select * from `tabStock Ledger Entry`
where voucher_type='Stock Entry' and voucher_no=%s""", mr.name))
self.assertFalse(frappe.db.sql("""select * from `tabGL Entry`
self.assertTrue(frappe.db.sql("""select * from `tabGL Entry`
where voucher_type='Stock Entry' and voucher_no=%s""", mr.name))
def test_material_issue_gl_entry(self):
@ -178,12 +178,6 @@ class TestStockEntry(unittest.TestCase):
)
mi.cancel()
self.assertFalse(frappe.db.sql("""select name from `tabStock Ledger Entry`
where voucher_type='Stock Entry' and voucher_no=%s""", mi.name))
self.assertFalse(frappe.db.sql("""select name from `tabGL Entry`
where voucher_type='Stock Entry' and voucher_no=%s""", mi.name))
def test_material_transfer_gl_entry(self):
company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
@ -216,11 +210,6 @@ class TestStockEntry(unittest.TestCase):
)
mtn.cancel()
self.assertFalse(frappe.db.sql("""select * from `tabStock Ledger Entry`
where voucher_type='Stock Entry' and voucher_no=%s""", mtn.name))
self.assertFalse(frappe.db.sql("""select * from `tabGL Entry`
where voucher_type='Stock Entry' and voucher_no=%s""", mtn.name))
def test_repack_no_change_in_valuation(self):
company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company')
@ -544,10 +533,10 @@ class TestStockEntry(unittest.TestCase):
frappe.db.set_value("Stock Settings", None, "stock_frozen_upto", '')
# test freeze_stocks_upto_days
frappe.db.set_value("Stock Settings", None, "stock_frozen_upto_days", 7)
frappe.db.set_value("Stock Settings", None, "stock_frozen_upto_days", -1)
se = frappe.copy_doc(test_records[0])
se.set_posting_time = 1
se.posting_date = add_days(nowdate(), -15)
se.posting_date = nowdate()
se.set_stock_entry_type()
se.insert()
self.assertRaises(StockFreezeError, se.submit)

View File

@ -1,4 +1,5 @@
{
"actions": [],
"allow_copy": 1,
"autoname": "MAT-SLE-.YYYY.-.#####",
"creation": "2013-01-29 19:25:42",
@ -255,11 +256,10 @@
"width": "150px"
},
{
"default": "0",
"fieldname": "is_cancelled",
"fieldtype": "Select",
"hidden": 1,
"fieldtype": "Check",
"label": "Is Cancelled",
"options": "\nNo\nYes",
"report_hide": 1
},
{
@ -275,7 +275,8 @@
"icon": "fa fa-list",
"idx": 1,
"in_create": 1,
"modified": "2020-02-25 22:53:33.504681",
"links": [],
"modified": "2020-04-23 05:57:03.985520",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Ledger Entry",

View File

@ -46,7 +46,7 @@ class StockLedgerEntry(Document):
def calculate_batch_qty(self):
if self.batch_no:
batch_qty = frappe.db.get_value("Stock Ledger Entry",
{"docstatus": 1, "batch_no": self.batch_no, "is_cancelled": "No"},
{"docstatus": 1, "batch_no": self.batch_no},
"sum(actual_qty)") or 0
frappe.db.set_value("Batch", self.batch_no, "batch_qty", batch_qty)
@ -93,7 +93,7 @@ class StockLedgerEntry(Document):
elif not frappe.db.get_value("Batch",{"item": self.item_code, "name": self.batch_no}):
frappe.throw(_("{0} is not a valid Batch Number for Item {1}").format(self.batch_no, batch_item))
elif item_det.has_batch_no ==0 and self.batch_no and self.is_cancelled == "No":
elif item_det.has_batch_no ==0 and self.batch_no:
frappe.throw(_("The Item {0} cannot have Batch").format(self.item_code))
if item_det.has_variants:

View File

@ -227,7 +227,7 @@ erpnext.stock.StockReconciliation = erpnext.stock.StockController.extend({
},
refresh: function() {
if(this.frm.doc.docstatus==1) {
if(this.frm.doc.docstatus > 0) {
this.show_stock_ledger();
if (erpnext.is_perpetual_inventory_enabled(this.frm.doc.company)) {
this.show_general_ledger();

View File

@ -6,7 +6,6 @@ import frappe, erpnext
import frappe.defaults
from frappe import msgprint, _
from frappe.utils import cstr, flt, cint
from erpnext.stock.stock_ledger import update_entries_after
from erpnext.controllers.stock_controller import StockController
from erpnext.accounts.utils import get_company_default
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@ -43,7 +42,8 @@ class StockReconciliation(StockController):
update_serial_nos_after_submit(self, "items")
def on_cancel(self):
self.delete_and_repost_sle()
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
self.make_sle_on_cancel()
self.make_gl_entries_on_cancel()
def remove_items_with_no_change(self):
@ -193,6 +193,7 @@ class StockReconciliation(StockController):
if row.serial_no or row.batch_no:
frappe.throw(_("Row #{0}: Item {1} is not a Serialized/Batched Item. It cannot have a Serial No/Batch No against it.") \
.format(row.idx, frappe.bold(row.item_code)))
previous_sle = get_previous_sle({
"item_code": row.item_code,
"warehouse": row.warehouse,
@ -319,7 +320,7 @@ class StockReconciliation(StockController):
"voucher_detail_no": row.name,
"company": self.company,
"stock_uom": frappe.db.get_value("Item", row.item_code, "stock_uom"),
"is_cancelled": "No" if self.docstatus != 2 else "Yes",
"is_cancelled": 1 if self.docstatus == 2 else 0,
"serial_no": '\n'.join(serial_nos) if serial_nos else '',
"batch_no": row.batch_no,
"valuation_rate": flt(row.valuation_rate, row.precision("valuation_rate"))
@ -328,27 +329,35 @@ class StockReconciliation(StockController):
if not row.batch_no:
data.qty_after_transaction = flt(row.qty, row.precision("qty"))
if self.docstatus == 2 and not row.batch_no:
if row.current_qty:
data.actual_qty = -1 * row.current_qty
data.qty_after_transaction = flt(row.current_qty)
data.valuation_rate = flt(row.current_valuation_rate)
data.stock_value = data.qty_after_transaction * data.valuation_rate
data.stock_value_difference = -1 * flt(row.amount_difference)
else:
data.actual_qty = row.qty
data.qty_after_transaction = 0.0
data.valuation_rate = flt(row.valuation_rate)
data.stock_value_difference = -1 * flt(row.amount_difference)
return data
def delete_and_repost_sle(self):
""" Delete Stock Ledger Entries related to this voucher
and repost future Stock Ledger Entries"""
existing_entries = frappe.db.sql("""select distinct item_code, warehouse
from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s""",
(self.doctype, self.name), as_dict=1)
# delete entries
frappe.db.sql("""delete from `tabStock Ledger Entry`
where voucher_type=%s and voucher_no=%s""", (self.doctype, self.name))
def make_sle_on_cancel(self):
sl_entries = []
has_serial_no = False
for row in self.items:
if row.serial_no or row.batch_no or row.current_serial_no:
has_serial_no = True
self.get_sle_for_serialized_items(row, sl_entries)
serial_nos = ''
if row.current_serial_no:
serial_nos = get_serial_nos(row.current_serial_no)
sl_entries.append(self.get_sle_for_items(row, serial_nos))
else:
sl_entries.append(self.get_sle_for_items(row))
if sl_entries:
if has_serial_no:
@ -358,14 +367,6 @@ class StockReconciliation(StockController):
allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock")
self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock)
# repost future entries for selected item_code, warehouse
for entries in existing_entries:
update_entries_after({
"item_code": entries.item_code,
"warehouse": entries.warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time
})
def merge_similar_item_serial_nos(self, sl_entries):
# If user has put the same item in multiple row with different serial no
@ -434,12 +435,6 @@ class StockReconciliation(StockController):
else:
self._submit()
def cancel(self):
if len(self.items) > 100:
self.queue_action('cancel')
else:
self._cancel()
@frappe.whitelist()
def get_items(warehouse, posting_date, posting_time, company):
lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"])

View File

@ -34,11 +34,11 @@ class TestStockReconciliation(unittest.TestCase):
# [[qty, valuation_rate, posting_date,
# posting_time, expected_stock_value, bin_qty, bin_valuation]]
input_data = [
[50, 1000, "2012-12-26", "12:00"],
[25, 900, "2012-12-26", "12:00"],
["", 1000, "2012-12-20", "12:05"],
[20, "", "2012-12-26", "12:05"],
[0, "", "2012-12-31", "12:10"]
[50, 1000],
[25, 900],
["", 1000],
[20, ""],
[0, ""]
]
for d in input_data:
@ -47,13 +47,13 @@ class TestStockReconciliation(unittest.TestCase):
last_sle = get_previous_sle({
"item_code": "_Test Item",
"warehouse": "Stores - TCP1",
"posting_date": d[2],
"posting_time": d[3]
"posting_date": nowdate(),
"posting_time": nowtime()
})
# submit stock reconciliation
stock_reco = create_stock_reconciliation(qty=d[0], rate=d[1],
posting_date=d[2], posting_time=d[3], warehouse="Stores - TCP1",
posting_date=nowdate(), posting_time=nowtime(), warehouse="Stores - TCP1",
company=company, expense_account = "Stock Adjustment - TCP1")
# check stock value
@ -68,8 +68,8 @@ class TestStockReconciliation(unittest.TestCase):
and valuation_rate == last_sle.get("valuation_rate"):
self.assertFalse(sle)
else:
self.assertEqual(sle[0].qty_after_transaction, qty_after_transaction)
self.assertEqual(sle[0].stock_value, qty_after_transaction * valuation_rate)
self.assertEqual(flt(sle[0].qty_after_transaction, 1), flt(qty_after_transaction, 1))
self.assertEqual(flt(sle[0].stock_value, 1), flt(qty_after_transaction * valuation_rate, 1))
# no gl entries
self.assertTrue(frappe.db.get_value("Stock Ledger Entry",
@ -77,16 +77,10 @@ class TestStockReconciliation(unittest.TestCase):
acc_bal, stock_bal, wh_list = get_stock_and_account_balance("Stock In Hand - TCP1",
stock_reco.posting_date, stock_reco.company)
self.assertEqual(acc_bal, stock_bal)
self.assertEqual(flt(acc_bal, 1), flt(stock_bal, 1))
stock_reco.cancel()
self.assertFalse(frappe.db.get_value("Stock Ledger Entry",
{"voucher_type": "Stock Reconciliation", "voucher_no": stock_reco.name}))
self.assertFalse(frappe.db.get_value("GL Entry",
{"voucher_type": "Stock Reconciliation", "voucher_no": stock_reco.name}))
def test_get_items(self):
create_warehouse("_Test Warehouse Group 1", {"is_group": 1})
create_warehouse("_Test Warehouse Ledger 1",
@ -113,7 +107,6 @@ class TestStockReconciliation(unittest.TestCase):
sr = create_stock_reconciliation(item_code=serial_item_code,
warehouse = serial_warehouse, qty=5, rate=200)
# print(sr.name)
serial_nos = get_serial_nos(sr.items[0].serial_no)
self.assertEqual(len(serial_nos), 5)
@ -133,7 +126,6 @@ class TestStockReconciliation(unittest.TestCase):
sr = create_stock_reconciliation(item_code=serial_item_code,
warehouse = serial_warehouse, qty=5, rate=300, serial_no = '\n'.join(serial_nos))
# print(sr.name)
serial_nos1 = get_serial_nos(sr.items[0].serial_no)
self.assertEqual(len(serial_nos1), 5)
@ -155,10 +147,6 @@ class TestStockReconciliation(unittest.TestCase):
stock_doc = frappe.get_doc("Stock Reconciliation", d)
stock_doc.cancel()
for d in serial_nos + serial_nos1:
if frappe.db.exists("Serial No", d):
frappe.delete_doc("Serial No", d)
def test_stock_reco_for_batch_item(self):
set_perpetual_inventory()
@ -208,13 +196,13 @@ class TestStockReconciliation(unittest.TestCase):
def insert_existing_sle(warehouse):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
make_stock_entry(posting_date="2012-12-15", posting_time="02:00", item_code="_Test Item",
make_stock_entry(posting_date=nowdate(), posting_time=nowtime(), item_code="_Test Item",
target=warehouse, qty=10, basic_rate=700)
make_stock_entry(posting_date="2012-12-25", posting_time="03:00", item_code="_Test Item",
make_stock_entry(posting_date=nowdate(), posting_time=nowtime(), item_code="_Test Item",
source=warehouse, qty=15)
make_stock_entry(posting_date="2013-01-05", posting_time="07:00", item_code="_Test Item",
make_stock_entry(posting_date=nowdate(), posting_time=nowtime(), item_code="_Test Item",
target=warehouse, qty=15, basic_rate=1200)
def create_batch_or_serial_no_items():

View File

@ -32,7 +32,7 @@ frappe.query_reports["Stock Ledger"] = {
"options": "Warehouse",
"get_query": function() {
const company = frappe.query_report.get_filter_value('company');
return {
return {
filters: { 'company': company }
}
}
@ -82,6 +82,11 @@ frappe.query_reports["Stock Ledger"] = {
"label": __("Include UOM"),
"fieldtype": "Link",
"options": "UOM"
},
{
"fieldname": "show_cancelled_entries",
"label": __("Show Cancelled Entries"),
"fieldtype": "Check"
}
],
"formatter": function (value, row, column, data, default_formatter) {
@ -96,9 +101,3 @@ frappe.query_reports["Stock Ledger"] = {
return value;
},
}
// $(function() {
// $(wrapper).bind("show", function() {
// frappe.query_report.load();
// });
// });

View File

@ -181,6 +181,9 @@ def get_sle_conditions(filters):
if filters.get("project"):
conditions.append("project=%(project)s")
if not filters.get("show_cancelled_entries"):
conditions.append("is_cancelled = 0")
return "and {}".format(" and ".join(conditions)) if conditions else ""

View File

@ -6,7 +6,6 @@ import frappe
from frappe.utils import flt, cstr, nowdate, nowtime
from erpnext.stock.utils import update_bin
from erpnext.stock.stock_ledger import update_entries_after
from erpnext.controllers.stock_controller import update_gl_entries_after
def repost(only_actual=False, allow_negative_stock=False, allow_zero_rate=False, only_bin=False):
"""
@ -56,12 +55,13 @@ def repost_stock(item_code, warehouse, allow_zero_rate=False,
update_bin_qty(item_code, warehouse, qty_dict)
def repost_actual_qty(item_code, warehouse, allow_zero_rate=False, allow_negative_stock=False): update_entries_after({ "item_code": item_code, "warehouse": warehouse },
def repost_actual_qty(item_code, warehouse, allow_zero_rate=False, allow_negative_stock=False):
update_entries_after({ "item_code": item_code, "warehouse": warehouse },
allow_zero_rate=allow_zero_rate, allow_negative_stock=allow_negative_stock)
def get_balance_qty_from_sle(item_code, warehouse):
balance_qty = frappe.db.sql("""select qty_after_transaction from `tabStock Ledger Entry`
where item_code=%s and warehouse=%s and is_cancelled='No'
where item_code=%s and warehouse=%s
order by posting_date desc, posting_time desc, creation desc
limit 1""", (item_code, warehouse))
@ -191,7 +191,7 @@ def set_stock_balance_as_per_serial_no(item_code=None, posting_date=None, postin
print(d[0], d[1], d[2], serial_nos[0][0])
sle = frappe.db.sql("""select valuation_rate, company from `tabStock Ledger Entry`
where item_code = %s and warehouse = %s and ifnull(is_cancelled, 'No') = 'No'
where item_code = %s and warehouse = %s
order by posting_date desc limit 1""", (d[0], d[1]))
sle_dict = {
@ -208,7 +208,6 @@ def set_stock_balance_as_per_serial_no(item_code=None, posting_date=None, postin
'stock_uom' : d[3],
'incoming_rate' : sle and flt(serial_nos[0][0]) > flt(d[2]) and flt(sle[0][0]) or 0,
'company' : sle and cstr(sle[0][1]) or 0,
'is_cancelled' : 'No',
'batch_no' : '',
'serial_no' : ''
}
@ -220,8 +219,7 @@ def set_stock_balance_as_per_serial_no(item_code=None, posting_date=None, postin
args = sle_dict.copy()
args.update({
"sle_id": sle_doc.name,
"is_amended": 'No'
"sle_id": sle_doc.name
})
update_bin(args)
@ -246,15 +244,3 @@ def reset_serial_no_status_and_warehouse(serial_nos=None):
sr.save()
except:
pass
def repost_gle_for_stock_transactions(posting_date=None, posting_time=None, for_warehouses=None):
frappe.db.auto_commit_on_many_writes = 1
if not posting_date:
posting_date = "1900-01-01"
if not posting_time:
posting_time = "00:00"
update_gl_entries_after(posting_date, posting_time, for_warehouses=for_warehouses)
frappe.db.auto_commit_on_many_writes = 0

View File

@ -4,8 +4,8 @@ from __future__ import unicode_literals
import frappe, erpnext
from frappe import _
from frappe.utils import cint, flt, cstr, now
from erpnext.stock.utils import get_valuation_method
from frappe.utils import cint, flt, cstr, now, now_datetime
from erpnext.stock.utils import get_valuation_method, get_incoming_outgoing_rate_for_cancel
import json
from six import iteritems
@ -16,36 +16,48 @@ class NegativeStockError(frappe.ValidationError): pass
_exceptions = frappe.local('stockledger_exceptions')
# _exceptions = []
def make_sl_entries(sl_entries, is_amended=None, allow_negative_stock=False, via_landed_cost_voucher=False):
def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False):
if sl_entries:
from erpnext.stock.utils import update_bin
cancel = True if sl_entries[0].get("is_cancelled") == "Yes" else False
cancel = sl_entries[0].get("is_cancelled")
if cancel:
set_as_cancel(sl_entries[0].get('voucher_no'), sl_entries[0].get('voucher_type'))
set_as_cancel(sl_entries[0].get('voucher_type'), sl_entries[0].get('voucher_no'))
for sle in sl_entries:
sle_id = None
if sle.get('is_cancelled') == 'Yes':
sle['actual_qty'] = -flt(sle['actual_qty'])
if via_landed_cost_voucher or cancel:
sle['posting_date'] = now_datetime().strftime('%Y-%m-%d')
sle['posting_time'] = now_datetime().strftime('%H:%M:%S.%f')
if cancel:
sle['actual_qty'] = -flt(sle.get('actual_qty'), 0)
if sle['actual_qty'] < 0 and not sle.get('outgoing_rate'):
sle['outgoing_rate'] = get_incoming_outgoing_rate_for_cancel(sle.item_code,
sle.voucher_type, sle.voucher_no, sle.voucher_detail_no)
sle['incoming_rate'] = 0.0
if sle['actual_qty'] > 0 and not sle.get('incoming_rate'):
sle['incoming_rate'] = get_incoming_outgoing_rate_for_cancel(sle.item_code,
sle.voucher_type, sle.voucher_no, sle.voucher_detail_no)
sle['outgoing_rate'] = 0.0
if sle.get("actual_qty") or sle.get("voucher_type")=="Stock Reconciliation":
sle_id = make_entry(sle, allow_negative_stock, via_landed_cost_voucher)
args = sle.copy()
args.update({
"sle_id": sle_id,
"is_amended": is_amended
"sle_id": sle_id
})
update_bin(args, allow_negative_stock, via_landed_cost_voucher)
if cancel:
delete_cancelled_entry(sl_entries[0].get('voucher_type'), sl_entries[0].get('voucher_no'))
def set_as_cancel(voucher_type, voucher_no):
frappe.db.sql("""update `tabStock Ledger Entry` set is_cancelled='Yes',
frappe.db.sql("""update `tabStock Ledger Entry` set is_cancelled=1,
modified=%s, modified_by=%s
where voucher_no=%s and voucher_type=%s""",
where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
(now(), frappe.session.user, voucher_type, voucher_no))
def make_entry(args, allow_negative_stock=False, via_landed_cost_voucher=False):
@ -58,9 +70,6 @@ def make_entry(args, allow_negative_stock=False, via_landed_cost_voucher=False):
sle.submit()
return sle.name
def delete_cancelled_entry(voucher_type, voucher_no):
frappe.db.sql("""delete from `tabStock Ledger Entry`
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
class update_entries_after(object):
"""
@ -106,14 +115,17 @@ class update_entries_after(object):
self.stock_queue = json.loads(self.previous_sle.stock_queue or "[]")
self.valuation_method = get_valuation_method(self.item_code)
self.stock_value_difference = 0.0
self.build()
self.build(args.get('sle_id'))
def build(self):
# includes current entry!
entries_to_fix = self.get_sle_after_datetime()
for sle in entries_to_fix:
def build(self, sle_id):
if sle_id:
sle = get_sle_by_id(sle_id)
self.process_sle(sle)
else:
# includes current entry!
entries_to_fix = self.get_sle_after_datetime()
for sle in entries_to_fix:
self.process_sle(sle)
if self.exceptions:
self.raise_exceptions()
@ -403,7 +415,10 @@ class update_entries_after(object):
def get_sle_before_datetime(self):
"""get previous stock ledger entry before current time-bucket"""
return get_stock_ledger_entries(self.args, "<", "desc", "limit 1", for_update=False)
if self.args.get('sle_id'):
self.args['name'] = self.args.get('sle_id')
return get_stock_ledger_entries(self.args, "<=", "desc", "limit 1", for_update=False)
def get_sle_after_datetime(self):
"""get Stock Ledger Entries after a particular datetime, for reposting"""
@ -470,9 +485,10 @@ def get_stock_ledger_entries(previous_sle, operator=None,
if operator in (">", "<=") and previous_sle.get("name"):
conditions += " and name!=%(name)s"
return frappe.db.sql("""select *, timestamp(posting_date, posting_time) as "timestamp" from `tabStock Ledger Entry`
return frappe.db.sql("""
select *, timestamp(posting_date, posting_time) as "timestamp"
from `tabStock Ledger Entry`
where item_code = %%(item_code)s
and ifnull(is_cancelled, 'No')='No'
%(conditions)s
order by timestamp(posting_date, posting_time) %(order)s, creation %(order)s
%(limit)s %(for_update)s""" % {
@ -482,6 +498,11 @@ def get_stock_ledger_entries(previous_sle, operator=None,
"order": order
}, previous_sle, as_dict=1, debug=debug)
def get_sle_by_id(sle_id):
return frappe.db.get_all('Stock Ledger Entry',
fields=['*', 'timestamp(posting_date, posting_time) as timestamp'],
filters={'name': sle_id})[0]
def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no,
allow_zero_rate=False, currency=None, company=None, raise_error_if_no_rate=True):
# Get valuation rate from last sle for the same item and warehouse

View File

@ -364,4 +364,16 @@ def add_additional_uom_columns(columns, result, include_uom, conversion_factors)
else:
row[data.converted_col] = flt(value_before_conversion) / conversion_factor
result[row_idx] = row
result[row_idx] = row
def get_incoming_outgoing_rate_for_cancel(item_code, voucher_type, voucher_no, voucher_detail_no):
outgoing_rate = frappe.db.sql("""SELECT abs(stock_value_difference / actual_qty)
FROM `tabStock Ledger Entry`
WHERE voucher_type = %s and voucher_no = %s
and item_code = %s and voucher_detail_no = %s
ORDER BY CREATION DESC limit 1""",
(voucher_type, voucher_no, item_code, voucher_detail_no))
outgoing_rate = outgoing_rate[0][0] if outgoing_rate else 0.0
return outgoing_rate

View File

@ -5,8 +5,9 @@ from __future__ import unicode_literals
import frappe
import frappe.share
from frappe import _
from frappe.utils import cstr, now_datetime, cint, flt, get_time, get_link_to_form
from frappe.utils import cstr, now_datetime, cint, flt, get_time, get_datetime, get_link_to_form
from erpnext.controllers.status_updater import StatusUpdater
from erpnext.accounts.utils import get_fiscal_year
from six import string_types
@ -28,6 +29,8 @@ class TransactionBase(StatusUpdater):
except ValueError:
frappe.throw(_('Invalid Posting Time'))
self.validate_with_last_transaction_posting_time()
def add_calendar_event(self, opts, force=False):
if cstr(self.contact_by) != cstr(self._prev.contact_by) or \
cstr(self.contact_date) != cstr(self._prev.contact_date) or force or \
@ -148,6 +151,30 @@ class TransactionBase(StatusUpdater):
return ret
def validate_with_last_transaction_posting_time(self):
if self.doctype not in ["Sales Invoice", "Purchase Invoice", "Stock Entry", "Stock Reconciliation",
"Delivery Note", "Purchase Receipt", "Fees"]:
return
if self.doctype in ["Sales Invoice", "Purchase Invoice"]:
if not (self.get("update_stock") or self.get("is_pos")):
return
fiscal_year = get_fiscal_year(self.get('posting_date'), as_dict=True).name
last_transaction_time = frappe.db.sql("""
select MAX(timestamp(posting_date, posting_time)) as posting_time
from `tabStock Ledger Entry`
where docstatus = 1 and fiscal_year = %s""", (fiscal_year))[0][0]
cur_doc_posting_datetime = "%s %s" % (self.posting_date, self.get("posting_time") or "00:00:00")
if last_transaction_time and get_datetime(cur_doc_posting_datetime) < get_datetime(last_transaction_time):
frappe.throw(_("""Posting timestamp of current transaction
must be after last Stock transaction's timestamp which is {0}""").format(frappe.bold(last_transaction_time)),
title=_("Backdated Stock Entry"))
def delete_events(ref_type, ref_name):
events = frappe.db.sql_list(""" SELECT
distinct `tabEvent`.name