diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 502a484236..fb44694849 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe -from frappe.utils import flt, cstr, cint +from frappe.utils import flt, cstr, cint, getdate, add_days, formatdate from frappe import msgprint, throw, _ from frappe.model.document import Document @@ -151,6 +151,27 @@ class Account(Document): and not self.get_authorized_user(): throw(_("{0} Credit limit {0} crossed").format(_(credit_limit_from), credit_limit)) + def validate_due_date(self, posting_date, due_date): + credit_days = (self.credit_days or frappe.db.get_value("Company", self.company, "credit_days")) + if credit_days is None: + return + + posting_date, due_date = getdate(posting_date), getdate(due_date) + diff = (due_date - posting_date).days + + if diff < 0: + frappe.throw(_("Due Date cannot be before Posting Date")) + elif diff > credit_days: + is_credit_controller = frappe.db.get_value("Accounts Settings", None, + "credit_controller") in frappe.user.get_roles() + + if is_credit_controller: + msgprint(_("Note: Due Date exceeds the allowed credit days by {0} day(s)").format( + diff - credit_days)) + else: + max_due_date = formatdate(add_days(posting_date, credit_days)) + frappe.throw(_("Due Date cannot be after {0}").format(max_due_date)) + def validate_trash(self): """checks gl entries and if child exists""" if not self.parent_account: diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 93d1ae3e42..2390e770c9 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -25,8 +25,7 @@ class GLEntry(Document): validate_balance_type(self.account, adv_adj) # Update outstanding amt on against voucher - if self.against_voucher and self.against_voucher_type != "POS" \ - and update_outstanding == 'Yes': + if self.against_voucher and update_outstanding == 'Yes': update_outstanding_amt(self.account, self.against_voucher_type, self.against_voucher) diff --git a/erpnext/accounts/doctype/journal_voucher/journal_voucher.py b/erpnext/accounts/doctype/journal_voucher/journal_voucher.py index 223be3c66f..94882e2a79 100644 --- a/erpnext/accounts/doctype/journal_voucher/journal_voucher.py +++ b/erpnext/accounts/doctype/journal_voucher/journal_voucher.py @@ -212,7 +212,7 @@ class JournalVoucher(AccountsController): self.is_approving_authority = 0 # Fetch credit controller role - approving_authority = frappe.db.get_value("Global Defaults", None, + approving_authority = frappe.db.get_value("Accounts Settings", None, "credit_controller") # Check logged-in user is authorized diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 36514ff963..b9dda025a6 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -50,6 +50,7 @@ class PurchaseInvoice(BuyingController): self.validate_with_previous_doc() self.validate_uom_is_integer("uom", "qty") self.set_aging_date() + frappe.get_doc("Account", self.credit_to).validate_due_date(self.posting_date, self.due_date) self.set_against_expense_account() self.validate_write_off_account() self.update_valuation_rate("entries") diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index c4ef3e7ec0..ca73518bd9 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -138,7 +138,7 @@ class TestPurchaseInvoice(unittest.TestCase): wrapper.load_from_db() expected_values = [ - ["_Test FG Item", 90, 7059], + ["_Test FG Item", 90, 59], ["_Test Item Home Desktop 200", 135, 177] ] for i, item in enumerate(wrapper.get("entries")): diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index eeb2425b0b..fcb472851c 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -23,7 +23,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte // show debit_to in print format this.frm.set_df_property("debit_to", "print_hide", 0); } - + // toggle to pos view if is_pos is 1 in user_defaults if ((cint(frappe.defaults.get_user_defaults("is_pos"))===1 || this.frm.doc.is_pos)) { if(this.frm.doc.__islocal && !this.frm.doc.amended_from && !this.frm.doc.customer) { @@ -34,14 +34,14 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte }); } } - + // if document is POS then change default print format to "POS Invoice" if(cur_frm.doc.is_pos && cur_frm.doc.docstatus===1) { locals.DocType[cur_frm.doctype].default_print_format = "POS Invoice"; cur_frm.setup_print_layout(); } }, - + refresh: function(doc, dt, dn) { this._super(); @@ -59,7 +59,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte }; frappe.set_route("query-report", "General Ledger"); }, "icon-table"); - + var percent_paid = cint(flt(doc.grand_total - doc.outstanding_amount) / flt(doc.grand_total) * 100); cur_frm.dashboard.add_progress(percent_paid + "% Paid", percent_paid); @@ -69,10 +69,10 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte // show Make Delivery Note button only if Sales Invoice is not created from Delivery Note var from_delivery_note = false; from_delivery_note = cur_frm.doc.entries - .some(function(item) { - return item.delivery_note ? true : false; + .some(function(item) { + return item.delivery_note ? true : false; }); - + if(!from_delivery_note) cur_frm.appframe.add_primary_action(__('Make Delivery'), cur_frm.cscript['Make Delivery Note']) } @@ -89,7 +89,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte }, sales_order_btn: function() { - this.$sales_order_btn = cur_frm.appframe.add_primary_action(__('From Sales Order'), + this.$sales_order_btn = cur_frm.appframe.add_primary_action(__('From Sales Order'), function() { frappe.model.map_current_doc({ method: "erpnext.selling.doctype.sales_order.sales_order.make_sales_invoice", @@ -106,7 +106,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte }, delivery_note_btn: function() { - this.$delivery_note_btn = cur_frm.appframe.add_primary_action(__('From Delivery Note'), + this.$delivery_note_btn = cur_frm.appframe.add_primary_action(__('From Delivery Note'), function() { frappe.model.map_current_doc({ method: "erpnext.stock.doctype.delivery_note.delivery_note.make_sales_invoice", @@ -124,11 +124,11 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte }); }); }, - + tc_name: function() { this.get_terms(); }, - + is_pos: function(callback_fn) { cur_frm.cscript.hide_fields(this.frm.doc); if(cint(this.frm.doc.is_pos)) { @@ -146,7 +146,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte me.set_default_values(); me.set_dynamic_labels(); me.calculate_taxes_and_totals(); - + if(callback_fn) callback_fn(); } } @@ -154,11 +154,11 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte } } }, - + customer: function() { if(this.frm.updating_party_details) return; - erpnext.utils.get_party_details(this.frm, + erpnext.utils.get_party_details(this.frm, "erpnext.accounts.party.get_party_details", { posting_date: this.frm.doc.posting_date, party: this.frm.doc.customer, @@ -167,42 +167,42 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte price_list: this.frm.doc.selling_price_list, }) }, - + debit_to: function() { this.customer(); }, - + allocated_amount: function() { this.calculate_total_advance("Sales Invoice", "advance_adjustment_details"); this.frm.refresh_fields(); }, - + write_off_outstanding_amount_automatically: function() { if(cint(this.frm.doc.write_off_outstanding_amount_automatically)) { frappe.model.round_floats_in(this.frm.doc, ["grand_total", "paid_amount"]); // this will make outstanding amount 0 - this.frm.set_value("write_off_amount", - flt(this.frm.doc.grand_total - this.frm.doc.paid_amount), + this.frm.set_value("write_off_amount", + flt(this.frm.doc.grand_total - this.frm.doc.paid_amount), precision("write_off_amount")); } - - this.calculate_outstanding_amount(); + + this.calculate_outstanding_amount(false); this.frm.refresh_fields(); }, - + write_off_amount: function() { this.write_off_outstanding_amount_automatically(); }, - + paid_amount: function() { this.write_off_outstanding_amount_automatically(); }, - + entries_add: function(doc, cdt, cdn) { var row = frappe.get_doc(cdt, cdn); this.frm.script_manager.copy_from_first_row("entries", row, ["income_account", "cost_center"]); }, - + set_dynamic_labels: function() { this._super(); this.hide_fields(this.frm.doc); @@ -224,9 +224,9 @@ cur_frm.cscript.hide_fields = function(doc) { 'gross_profit_percent', 'get_advances_received', 'advance_adjustment_details', 'sales_partner', 'commission_rate', 'total_commission', 'advances']; - + item_flds_normal = ['sales_order', 'delivery_note'] - + if(cint(doc.is_pos) == 1) { hide_field(par_flds); unhide_field('payments_section'); @@ -239,15 +239,15 @@ cur_frm.cscript.hide_fields = function(doc) { } cur_frm.fields_dict['entries'].grid.set_column_disp(item_flds_normal, true); } - + item_flds_stock = ['serial_no', 'batch_no', 'actual_qty', 'expense_account', 'warehouse'] cur_frm.fields_dict['entries'].grid.set_column_disp(item_flds_stock, (cint(doc.update_stock)==1 ? true : false)); - + // 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']); - + cur_frm.refresh_fields(); } @@ -305,7 +305,7 @@ cur_frm.fields_dict.cash_bank_account.get_query = function(doc) { 'group_or_ledger': 'Ledger', 'company': doc.company } - } + } } cur_frm.fields_dict.write_off_account.get_query = function(doc) { @@ -326,7 +326,7 @@ cur_frm.fields_dict.write_off_cost_center.get_query = function(doc) { 'group_or_ledger': 'Ledger', 'company': doc.company } - } + } } //project name @@ -335,7 +335,7 @@ cur_frm.fields_dict['project_name'].get_query = function(doc, cdt, cdn) { return{ query: "erpnext.controllers.queries.get_project_name", filters: {'customer': doc.customer} - } + } } // Income Account in Details Table @@ -365,10 +365,10 @@ if (sys_defaults.auto_accounting_for_stock) { // ----------------------------- cur_frm.fields_dict["entries"].grid.get_field("cost_center").get_query = function(doc) { return { - filters: { + filters: { 'company': doc.company, 'group_or_ledger': 'Ledger' - } + } } } @@ -396,12 +396,12 @@ cur_frm.cscript.convert_into_recurring_invoice = function(doc, dt, dn) { var owner_email = doc.owner=="Administrator" ? frappe.user_info("Administrator").email : doc.owner; - + doc.notification_email_address = $.map([cstr(owner_email), cstr(doc.contact_email)], function(v) { return v || null; }).join(", "); doc.repeat_on_day_of_month = frappe.datetime.str_to_obj(doc.posting_date).getDate(); } - + refresh_many(["notification_email_address", "repeat_on_day_of_month"]); } diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 5d42459343..514f719f81 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -65,6 +65,7 @@ class SalesInvoice(SellingController): self.is_opening = 'No' self.set_aging_date() + frappe.get_doc("Account", self.debit_to).validate_due_date(self.posting_date, self.due_date) self.set_against_income_account() self.validate_c_form() self.validate_time_logs_are_submitted() @@ -464,6 +465,10 @@ class SalesInvoice(SellingController): make_gl_entries(gl_entries, cancel=(self.docstatus == 2), update_outstanding=update_outstanding, merge_entries=False) + if update_outstanding == "No": + from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt + update_outstanding_amt(self.debit_to, self.doctype, self.name) + if repost_future_gle and cint(self.update_stock) \ and cint(frappe.defaults.get_global_default("auto_accounting_for_stock")): items, warehouse_account = self.get_items_and_warehouse_accounts() diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index d321d00e1a..25ea78f3bb 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -10,7 +10,7 @@ from erpnext.accounts.utils import validate_expense_against_budget class StockAccountInvalidTransaction(frappe.ValidationError): pass -def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, +def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes'): if gl_map: if not cancel: @@ -18,11 +18,11 @@ def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, save_entries(gl_map, adv_adj, update_outstanding) else: delete_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding) - + def process_gl_map(gl_map, merge_entries=True): if merge_entries: gl_map = merge_similar_entries(gl_map) - + for entry in gl_map: # toggle debit, credit if negative entry if flt(entry.debit) < 0: @@ -33,11 +33,11 @@ def process_gl_map(gl_map, merge_entries=True): entry.credit = 0.0 return gl_map - + def merge_similar_entries(gl_map): merged_gl_map = [] for entry in gl_map: - # if there is already an entry in this account then just add it + # if there is already an entry in this account then just add it # to that entry same_head = check_if_in_list(entry, merged_gl_map) if same_head: @@ -45,7 +45,7 @@ def merge_similar_entries(gl_map): same_head.credit = flt(same_head.credit) + flt(entry.credit) else: merged_gl_map.append(entry) - + # filter zero debit and credit entries merged_gl_map = filter(lambda x: flt(x.debit)!=0 or flt(x.credit)!=0, merged_gl_map) return merged_gl_map @@ -61,20 +61,20 @@ def check_if_in_list(gle, gl_map): def save_entries(gl_map, adv_adj, update_outstanding): validate_account_for_auto_accounting_for_stock(gl_map) - + total_debit = total_credit = 0.0 for entry in gl_map: make_entry(entry, adv_adj, update_outstanding) # check against budget validate_expense_against_budget(entry) - + # update total debit / credit total_debit += flt(entry.debit) total_credit += flt(entry.credit) - + validate_total_debit_credit(total_debit, total_credit) - + def make_entry(args, adv_adj, update_outstanding): args.update({"doctype": "GL Entry"}) gle = frappe.get_doc(args) @@ -82,45 +82,44 @@ def make_entry(args, adv_adj, update_outstanding): gle.insert() gle.run_method("on_update_with_args", adv_adj, update_outstanding) gle.submit() - + def validate_total_debit_credit(total_debit, total_credit): if abs(total_debit - total_credit) > 0.005: frappe.throw(_("Debit and Credit not equal for this voucher: Diff (Debit) is ") + cstr(total_debit - total_credit)) - + def validate_account_for_auto_accounting_for_stock(gl_map): if gl_map[0].voucher_type=="Journal Voucher": - aii_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount + aii_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount where account_type = 'Warehouse' and ifnull(master_name, '')!=''""")] - + for entry in gl_map: if entry.account in aii_accounts: - frappe.throw(_("Account") + ": " + entry.account + - _(" can only be debited/credited through Stock transactions"), + frappe.throw(_("Account") + ": " + entry.account + + _(" can only be debited/credited through Stock transactions"), StockAccountInvalidTransaction) - - -def delete_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None, + + +def delete_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None, adv_adj=False, update_outstanding="Yes"): - + from erpnext.accounts.doctype.gl_entry.gl_entry import validate_balance_type, \ check_freezing_date, update_outstanding_amt, validate_frozen_account - + if not gl_entries: - gl_entries = frappe.db.sql("""select * from `tabGL Entry` + gl_entries = frappe.db.sql("""select * from `tabGL Entry` where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no), as_dict=True) if gl_entries: 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""", + + 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: validate_frozen_account(entry["account"], adv_adj) validate_balance_type(entry["account"], adv_adj) validate_expense_against_budget(entry) - - if entry.get("against_voucher") and entry.get("against_voucher_type") != "POS" \ - and update_outstanding == 'Yes': - update_outstanding_amt(entry["account"], entry.get("against_voucher_type"), - entry.get("against_voucher"), on_cancel=True) \ No newline at end of file + + if entry.get("against_voucher") and update_outstanding == 'Yes': + update_outstanding_amt(entry["account"], entry.get("against_voucher_type"), + entry.get("against_voucher"), on_cancel=True) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 408f434ee0..b876a2b502 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -36,10 +36,11 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters): or employee_name like "%(txt)s") %(mcond)s order by - case when name like "%(txt)s" then 0 else 1 end, - case when employee_name like "%(txt)s" then 0 else 1 end, - name + if(locate("%(_txt)s", name), locate("%(_txt)s", name), 99999), + if(locate("%(_txt)s", employee_name), locate("%(_txt)s", item_name), 99999), + name, employee_name limit %(start)s, %(page_len)s""" % {'key': searchfield, 'txt': "%%%s%%" % txt, + '_txt': txt.replace("%", ""), 'mcond':get_match_cond(doctype), 'start': start, 'page_len': page_len}) # searches for leads which are not converted @@ -52,11 +53,12 @@ def lead_query(doctype, txt, searchfield, start, page_len, filters): or company_name like "%(txt)s") %(mcond)s order by - case when name like "%(txt)s" then 0 else 1 end, - case when lead_name like "%(txt)s" then 0 else 1 end, - case when company_name like "%(txt)s" then 0 else 1 end, - lead_name asc + if(locate("%(_txt)s", name), locate("%(_txt)s", name), 99999), + if(locate("%(_txt)s", lead_name), locate("%(_txt)s", name), 99999), + if(locate("%(_txt)s", company_name), locate("%(_txt)s", name), 99999), + name, lead_name limit %(start)s, %(page_len)s""" % {'key': searchfield, 'txt': "%%%s%%" % txt, + '_txt': txt.replace("%", ""), 'mcond':get_match_cond(doctype), 'start': start, 'page_len': page_len}) # searches for customer @@ -76,11 +78,12 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters): or customer_name like "%(txt)s") %(mcond)s order by - case when name like "%(txt)s" then 0 else 1 end, - case when customer_name like "%(txt)s" then 0 else 1 end, + if(locate("%(_txt)s", name), locate("%(_txt)s", name), 99999), + if(locate("%(_txt)s", customer_name), locate("%(_txt)s", name), 99999), name, customer_name limit %(start)s, %(page_len)s""" % {'field': fields,'key': searchfield, - 'txt': "%%%s%%" % txt, 'mcond':get_match_cond(doctype), + 'txt': "%%%s%%" % txt, '_txt': txt.replace("%", ""), + 'mcond':get_match_cond(doctype), 'start': start, 'page_len': page_len}) # searches for supplier @@ -98,11 +101,12 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters): or supplier_name like "%(txt)s") %(mcond)s order by - case when name like "%(txt)s" then 0 else 1 end, - case when supplier_name like "%(txt)s" then 0 else 1 end, + if(locate("%(_txt)s", name), locate("%(_txt)s", name), 99999), + if(locate("%(_txt)s", supplier_name), locate("%(_txt)s", name), 99999), name, supplier_name limit %(start)s, %(page_len)s """ % {'field': fields,'key': searchfield, - 'txt': "%%%s%%" % txt, 'mcond':get_match_cond(doctype), 'start': start, + 'txt': "%%%s%%" % txt, '_txt': txt.replace("%", ""), + 'mcond':get_match_cond(doctype), 'start': start, 'page_len': page_len}) def tax_account_query(doctype, txt, searchfield, start, page_len, filters): @@ -141,12 +145,17 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): and (tabItem.`{key}` LIKE %(txt)s or tabItem.item_name LIKE %(txt)s) {fcond} {mcond} + order by + if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999), + if(locate(%(_txt)s, item_name), locate(%(_txt)s, item_name), 99999), + name, item_name limit %(start)s, %(page_len)s """.format(key=searchfield, fcond=get_filters_cond(doctype, filters, conditions), mcond=get_match_cond(doctype)), { "today": nowdate(), "txt": "%%%s%%" % txt, + "_txt": txt.replace("%", ""), "start": start, "page_len": page_len }) diff --git a/erpnext/manufacturing/doctype/production_order/production_order.py b/erpnext/manufacturing/doctype/production_order/production_order.py index edcb25d37a..5c617fd69d 100644 --- a/erpnext/manufacturing/doctype/production_order/production_order.py +++ b/erpnext/manufacturing/doctype/production_order/production_order.py @@ -6,10 +6,10 @@ import frappe from frappe.utils import cstr, flt, nowdate from frappe import _ +from frappe.model.document import Document class OverProductionError(frappe.ValidationError): pass - -from frappe.model.document import Document +class StockOverProductionError(frappe.ValidationError): pass class ProductionOrder(Document): @@ -89,21 +89,41 @@ class ProductionOrder(Document): frappe.msgprint(_("Production Order status is {0}").format(status)) - def update_status(self, status): - if status == 'Stopped': - frappe.db.set(self, 'status', cstr(status)) - else: - if flt(self.qty) == flt(self.produced_qty): - frappe.db.set(self, 'status', 'Completed') - if flt(self.qty) > flt(self.produced_qty): - frappe.db.set(self, 'status', 'In Process') - if flt(self.produced_qty) == 0: - frappe.db.set(self, 'status', 'Submitted') + def update_status(self, status=None): + if not status: + status = self.status + if status != 'Stopped': + stock_entries = frappe._dict(frappe.db.sql("""select purpose, sum(fg_completed_qty) + from `tabStock Entry` where production_order=%s and docstatus=1 + group by purpose""", self.name)) + + status = "Submitted" + if stock_entries: + status = "In Process" + produced_qty = stock_entries.get("Manufacture/Repack") + if flt(produced_qty) == flt(self.qty): + status = "Completed" + + if status != self.status: + self.db_set("status", status) + + def update_produced_qty(self): + produced_qty = frappe.db.sql("""select sum(fg_completed_qty) + from `tabStock Entry` where production_order=%s and docstatus=1 + and purpose='Manufacture/Repack'""", self.name) + produced_qty = flt(produced_qty[0][0]) if produced_qty else 0 + + if produced_qty > self.qty: + frappe.throw(_("Cannot manufacture more than the planned Quantity to Manufacture ({0}) in Production Order: {1}").format(self.qty, self.name), StockOverProductionError) + + self.db_set("produced_qty", produced_qty) def on_submit(self): if not self.wip_warehouse: - frappe.throw(_("WIP Warehouse required before Submit")) + frappe.throw(_("Work-in-Progress Warehouse is required before Submit")) + if not self.fg_warehouse: + frappe.throw(_("For Warehouse is required before Submit")) frappe.db.set(self,'status', 'Submitted') self.update_planned_qty(self.qty) diff --git a/erpnext/manufacturing/doctype/production_order/test_production_order.py b/erpnext/manufacturing/doctype/production_order/test_production_order.py index 9bc001d9b4..125a0164c1 100644 --- a/erpnext/manufacturing/doctype/production_order/test_production_order.py +++ b/erpnext/manufacturing/doctype/production_order/test_production_order.py @@ -49,7 +49,7 @@ class TestProductionOrder(unittest.TestCase): return pro_doc.name def test_over_production(self): - from erpnext.stock.doctype.stock_entry.stock_entry import StockOverProductionError + from erpnext.manufacturing.doctype.production_order.production_order import StockOverProductionError pro_order = self.test_planned_qty() stock_entry = make_stock_entry(pro_order, "Manufacture/Repack") diff --git a/erpnext/public/js/queries.js b/erpnext/public/js/queries.js index ec38d634c4..117e192788 100644 --- a/erpnext/public/js/queries.js +++ b/erpnext/public/js/queries.js @@ -24,8 +24,10 @@ $.extend(erpnext.queries, { return { query: "erpnext.controllers.queries.account_query" }; }, - item: function() { - return { query: "erpnext.controllers.queries.item_query" }; + item: function(filters) { + var args = { query: "erpnext.controllers.queries.item_query" }; + if(filters) args["filters"] = filters; + return args; }, bom: function() { @@ -38,25 +40,25 @@ $.extend(erpnext.queries, { customer_filter: function(doc) { if(!doc.customer) { - frappe.throw(__("Please specify a") + " " + + frappe.throw(__("Please specify a") + " " + __(frappe.meta.get_label(doc.doctype, "customer", doc.name))); } - + return { filters: { customer: doc.customer } }; }, supplier_filter: function(doc) { if(!doc.supplier) { - frappe.throw(__("Please specify a") + " " + + frappe.throw(__("Please specify a") + " " + __(frappe.meta.get_label(doc.doctype, "supplier", doc.name))); } - + return { filters: { supplier: doc.supplier } }; }, lead_filter: function(doc) { if(!doc.lead) { - frappe.throw(__("Please specify a") + " " + + frappe.throw(__("Please specify a") + " " + __(frappe.meta.get_label(doc.doctype, "lead", doc.name))); } @@ -66,4 +68,4 @@ $.extend(erpnext.queries, { not_a_group_filter: function() { return { filters: { is_group: "No" } }; }, -}); \ No newline at end of file +}); diff --git a/erpnext/public/js/transaction.js b/erpnext/public/js/transaction.js index 5d43ad415e..4aaa13886a 100644 --- a/erpnext/public/js/transaction.js +++ b/erpnext/public/js/transaction.js @@ -189,7 +189,7 @@ erpnext.TransactionController = erpnext.stock.StockController.extend({ }, validate: function() { - this.calculate_taxes_and_totals(); + this.calculate_taxes_and_totals(false); }, company: function() { @@ -299,7 +299,8 @@ erpnext.TransactionController = erpnext.stock.StockController.extend({ this.calculate_taxes_and_totals(); }, - tax_rate: function(doc, cdt, cdn) { + // tax rate + rate: function(doc, cdt, cdn) { this.calculate_taxes_and_totals(); }, @@ -372,7 +373,6 @@ erpnext.TransactionController = erpnext.stock.StockController.extend({ var on_previous_row_error = function(row_range) { var msg = __("For row {0} in {1}. To include {2} in Item rate, rows {3} must also be included", [tax.idx, __(tax.doctype), tax.charge_type, row_range]) - frappe.throw(msg); }; @@ -680,14 +680,14 @@ erpnext.TransactionController = erpnext.stock.StockController.extend({ }); }, - calculate_total_advance: function(parenttype, advance_parentfield) { + calculate_total_advance: function(parenttype, advance_parentfield, update_paid_amount) { if(this.frm.doc.doctype == parenttype && this.frm.doc.docstatus < 2) { var advance_doclist = this.frm.doc[advance_parentfield] || []; this.frm.doc.total_advance = flt(frappe.utils.sum( $.map(advance_doclist, function(adv) { return adv.allocated_amount }) ), precision("total_advance")); - this.calculate_outstanding_amount(); + this.calculate_outstanding_amount(update_paid_amount); } }, diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index a89aaef266..1e9643aeb8 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -235,9 +235,9 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ } }, - calculate_taxes_and_totals: function() { + calculate_taxes_and_totals: function(update_paid_amount) { this._super(); - this.calculate_total_advance("Sales Invoice", "advance_adjustment_details"); + this.calculate_total_advance("Sales Invoice", "advance_adjustment_details", update_paid_amount); this.calculate_commission(); this.calculate_contribution(); @@ -398,7 +398,7 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ return grand_total_for_discount_amount; }, - calculate_outstanding_amount: function() { + calculate_outstanding_amount: function(update_paid_amount) { // NOTE: // paid_amount and write_off_amount is only for POS Invoice // total_advance is only for non POS Invoice @@ -408,7 +408,9 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ var total_amount_to_pay = this.frm.doc.grand_total - this.frm.doc.write_off_amount - this.frm.doc.total_advance; if(this.frm.doc.is_pos) { - if(!this.frm.doc.paid_amount) this.frm.doc.paid_amount = flt(total_amount_to_pay); + if(!this.frm.doc.paid_amount || update_paid_amount===undefined || update_paid_amount) { + this.frm.doc.paid_amount = flt(total_amount_to_pay); + } } else { this.frm.doc.paid_amount = 0 } diff --git a/erpnext/stock/doctype/landed_cost_wizard/landed_cost_wizard.py b/erpnext/stock/doctype/landed_cost_wizard/landed_cost_wizard.py index b1a67e25f3..f71c827ff1 100644 --- a/erpnext/stock/doctype/landed_cost_wizard/landed_cost_wizard.py +++ b/erpnext/stock/doctype/landed_cost_wizard/landed_cost_wizard.py @@ -58,13 +58,14 @@ class LandedCostWizard(Document): ch.rate = amt ch.tax_amount = amt ch.docstatus = 1 - ch.save(1) + ch.db_insert() else: # overwrite if exists matched_row[0].rate = amt matched_row[0].tax_amount = amt matched_row[0].cost_center = lc.cost_center pr_doc.run_method("validate") + pr_doc._validate_mandatory() for d in pr_doc.get_all_children(): d.db_update() diff --git a/erpnext/stock/doctype/serial_no/serial_no.js b/erpnext/stock/doctype/serial_no/serial_no.js index 67eb779f2d..10b20f9647 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.js +++ b/erpnext/stock/doctype/serial_no/serial_no.js @@ -8,3 +8,9 @@ cur_frm.add_fetch("item_code", "item_name", "item_name") cur_frm.add_fetch("item_code", "description", "description") cur_frm.add_fetch("item_code", "item_group", "item_group") cur_frm.add_fetch("item_code", "brand", "brand") + +cur_frm.cscript.onload = function() { + cur_frm.set_query("item_code", function() { + return erpnext.queries.item({"is_stock_item": "Yes", "has_serial_no": "Yes"}) + }); +} diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 66716a7848..74c5a3fbc5 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -18,7 +18,6 @@ class NotUpdateStockError(frappe.ValidationError): pass class StockOverReturnError(frappe.ValidationError): pass class IncorrectValuationRateError(frappe.ValidationError): pass class DuplicateEntryForProductionOrderError(frappe.ValidationError): pass -class StockOverProductionError(frappe.ValidationError): pass from erpnext.controllers.stock_controller import StockController @@ -314,24 +313,11 @@ class StockEntry(StockController): if self.production_order: pro_doc = frappe.get_doc("Production Order", self.production_order) _validate_production_order(pro_doc) - self.update_produced_qty(pro_doc) + pro_doc.run_method("update_status") if self.purpose == "Manufacture/Repack": + pro_doc.run_method("update_produced_qty") self.update_planned_qty(pro_doc) - def update_produced_qty(self, pro_doc): - if self.purpose == "Manufacture/Repack": - produced_qty = flt(pro_doc.produced_qty) + \ - (self.docstatus==1 and 1 or -1 ) * flt(self.fg_completed_qty) - - if produced_qty > flt(pro_doc.qty): - frappe.throw(_("Production Order") + ": " + self.production_order + "\n" + - _("Total Manufactured Qty can not be greater than Planned qty to manufacture") - + "(%s/%s)" % (produced_qty, flt(pro_doc.qty)), StockOverProductionError) - - status = 'Completed' if flt(produced_qty) >= flt(pro_doc.qty) else 'In Process' - frappe.db.sql("""update `tabProduction Order` set status=%s, produced_qty=%s - where name=%s""", (status, produced_qty, self.production_order)) - def update_planned_qty(self, pro_doc): from erpnext.stock.utils import update_bin update_bin({ diff --git a/erpnext/support/doctype/customer_issue/customer_issue.js b/erpnext/support/doctype/customer_issue/customer_issue.js index 422ec98036..74ff7d7aee 100644 --- a/erpnext/support/doctype/customer_issue/customer_issue.js +++ b/erpnext/support/doctype/customer_issue/customer_issue.js @@ -3,20 +3,20 @@ frappe.provide("erpnext.support"); -frappe.ui.form.on_change("Customer Issue", "customer", function(frm) { +frappe.ui.form.on_change("Customer Issue", "customer", function(frm) { erpnext.utils.get_party_details(frm) }); -frappe.ui.form.on_change("Customer Issue", "customer_address", +frappe.ui.form.on_change("Customer Issue", "customer_address", erpnext.utils.get_address_display); -frappe.ui.form.on_change("Customer Issue", "contact_person", - erpnext.utils.get_contact_details); +frappe.ui.form.on_change("Customer Issue", "contact_person", + erpnext.utils.get_contact_details); erpnext.support.CustomerIssue = frappe.ui.form.Controller.extend({ refresh: function() { if((cur_frm.doc.status=='Open' || cur_frm.doc.status == 'Work In Progress')) { cur_frm.add_custom_button(__('Make Maintenance Visit'), this.make_maintenance_visit) } - }, - + }, + make_maintenance_visit: function() { frappe.model.open_mapped_doc({ method: "erpnext.support.doctype.customer_issue.customer_issue.make_maintenance_visit", @@ -28,8 +28,8 @@ erpnext.support.CustomerIssue = frappe.ui.form.Controller.extend({ $.extend(cur_frm.cscript, new erpnext.support.CustomerIssue({frm: cur_frm})); cur_frm.cscript.onload = function(doc,cdt,cdn){ - if(!doc.status) - set_multiple(dt,dn,{status:'Open'}); + if(!doc.status) + set_multiple(dt,dn,{status:'Open'}); } cur_frm.fields_dict['customer_address'].get_query = function(doc, cdt, cdn) { @@ -66,7 +66,6 @@ cur_frm.add_fetch('serial_no', 'warranty_expiry_date', 'warranty_expiry_date'); cur_frm.add_fetch('serial_no', 'amc_expiry_date', 'amc_expiry_date'); cur_frm.add_fetch('serial_no', 'customer', 'customer'); cur_frm.add_fetch('serial_no', 'customer_name', 'customer_name'); -cur_frm.add_fetch('serial_no', 'delivery_address', 'customer_address'); cur_frm.add_fetch('item_code', 'item_name', 'item_name'); cur_frm.add_fetch('item_code', 'description', 'description'); @@ -74,14 +73,14 @@ cur_frm.fields_dict['item_code'].get_query = function(doc, cdt, cdn) { if(doc.serial_no) { return{ filters:{ 'serial_no': doc.serial_no} - } + } } else{ return{ filters:[ ['Item', 'docstatus', '!=', 2] ] - } + } } }