From d29465029d9d756b4d6ed021d4821a9e6e0d646a Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Tue, 8 Apr 2014 20:10:03 +0530 Subject: [PATCH] Fixed Test Cases frappe/frappe#478 --- .../doctype/sales_invoice/sales_invoice.py | 290 ++++++------ .../sales_invoice/test_sales_invoice.py | 415 +++++++++--------- .../doctype/purchase_order/purchase_order.py | 114 ++--- .../purchase_order/test_purchase_order.py | 91 ++-- .../supplier_quotation/supplier_quotation.py | 2 +- .../test_supplier_quotation.py | 22 +- erpnext/controllers/accounts_controller.py | 197 ++++----- erpnext/controllers/selling_controller.py | 166 +++---- erpnext/manufacturing/doctype/bom/bom.py | 156 ++++--- erpnext/manufacturing/doctype/bom/test_bom.py | 18 +- .../production_order/test_production_order.py | 32 +- .../doctype/time_log/test_time_log.py | 5 +- .../time_log_batch/test_time_log_batch.py | 8 +- .../doctype/time_log_batch/time_log_batch.py | 8 +- erpnext/selling/doctype/customer/customer.py | 67 ++- erpnext/selling/doctype/lead/lead.py | 2 +- erpnext/selling/doctype/lead/test_lead.py | 10 +- .../doctype/opportunity/opportunity.py | 2 +- .../selling/doctype/quotation/quotation.py | 2 +- .../doctype/quotation/test_quotation.py | 30 +- .../doctype/sales_order/sales_order.py | 216 ++++----- .../doctype/sales_order/test_sales_order.py | 219 ++++----- .../doctype/delivery_note/delivery_note.py | 126 +++--- .../material_request/material_request.py | 6 +- .../purchase_receipt/purchase_receipt.py | 2 +- .../stock/doctype/stock_entry/stock_entry.py | 1 + .../doctype/customer_issue/customer_issue.py | 2 +- .../maintenance_schedule.py | 2 +- 28 files changed, 1095 insertions(+), 1116 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 41be5537104..04a9959cfb2 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -6,14 +6,12 @@ import frappe import frappe.defaults from frappe.utils import add_days, cint, cstr, date_diff, flt, getdate, nowdate, \ - get_first_day, get_last_day - -from frappe.utils import comma_and + get_first_day, get_last_day, comma_and from frappe.model.naming import make_autoname - from frappe import _, msgprint from erpnext.accounts.party import get_party_account, get_due_date +from erpnext.controllers.stock_controller import update_gl_entries_after month_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, 'Yearly': 12} @@ -68,24 +66,24 @@ class SalesInvoice(SellingController): self.validate_c_form() self.validate_time_logs_are_submitted() self.validate_recurring_invoice() - self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", + self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "delivery_note_details") def on_submit(self): - if cint(self.update_stock) == 1: + if cint(self.update_stock) == 1: self.update_stock_ledger() else: # Check for Approving Authority if not self.recurring_id: - frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, + frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.company, self.grand_total, self) - + self.check_prev_docstatus() - + self.update_status_updater_args() self.update_prevdoc_status() self.update_billing_status_for_zero_amount_refdoc("Sales Order") - + # this sequence because outstanding may get -ve self.make_gl_entries() self.check_credit_limit(self.debit_to) @@ -103,18 +101,18 @@ class SalesInvoice(SellingController): def on_cancel(self): if cint(self.update_stock) == 1: self.update_stock_ledger() - + self.check_stop_sales_order("sales_order") - + from erpnext.accounts.utils import remove_against_link_from_jv remove_against_link_from_jv(self.doctype, self.name, "against_invoice") self.update_status_updater_args() self.update_prevdoc_status() self.update_billing_status_for_zero_amount_refdoc("Sales Order") - + self.make_cancel_gl_entries() - + def update_status_updater_args(self): if cint(self.update_stock): self.status_updater.append({ @@ -133,31 +131,31 @@ class SalesInvoice(SellingController): 'second_source_field': 'qty', 'second_join_field': 'prevdoc_detail_docname' }) - + def on_update_after_submit(self): self.validate_recurring_invoice() self.convert_to_recurring() - + def get_portal_page(self): return "invoice" if self.docstatus==1 else None - + def set_missing_values(self, for_validate=False): self.set_pos_fields(for_validate) - + if not self.debit_to: self.debit_to = get_party_account(self.company, self.customer, "Customer") if not self.due_date: self.due_date = get_due_date(self.posting_date, self.customer, "Customer", self.debit_to, self.company) - + super(SalesInvoice, self).set_missing_values(for_validate) - + def update_time_log_batch(self, sales_invoice): for d in self.get(self.fname): if d.time_log_batch: tlb = frappe.get_doc("Time Log Batch", d.time_log_batch) tlb.sales_invoice = sales_invoice - tlb.update_after_submit() + tlb.save() def validate_time_logs_are_submitted(self): for d in self.get(self.fname): @@ -171,10 +169,10 @@ class SalesInvoice(SellingController): """Set retail related fields from pos settings""" if cint(self.is_pos) != 1: return - - from erpnext.stock.get_item_details import get_pos_settings_item_details, get_pos_settings + + from erpnext.stock.get_item_details import get_pos_settings_item_details, get_pos_settings pos = get_pos_settings(self.company) - + if pos: if not for_validate and not self.customer: self.customer = pos.customer @@ -184,31 +182,31 @@ class SalesInvoice(SellingController): 'selling_price_list', 'company', 'select_print_heading', 'cash_bank_account'): if (not for_validate) or (for_validate and not self.get(fieldname)): self.set(fieldname, pos.get(fieldname)) - + if not for_validate: self.update_stock = cint(pos.get("update_stock")) # set pos values in items for item in self.get("entries"): if item.get('item_code'): - for fname, val in get_pos_settings_item_details(pos, + for fname, val in get_pos_settings_item_details(pos, frappe._dict(item.as_dict()), pos).items(): - + if (not for_validate) or (for_validate and not item.get(fname)): item.set(fname, val) - # fetch terms + # fetch terms if self.tc_name and not self.terms: self.terms = frappe.db.get_value("Terms and Conditions", self.tc_name, "terms") - + # fetch charges if self.taxes_and_charges and not len(self.get("other_charges")): self.set_taxes("other_charges", "taxes_and_charges") - + def get_advances(self): - super(SalesInvoice, self).get_advances(self.debit_to, + super(SalesInvoice, self).get_advances(self.debit_to, "Sales Invoice Advance", "advance_adjustment_details", "credit") - + def get_company_abbr(self): return frappe.db.sql("select abbr from tabCompany where name=%s", self.company)[0][0] @@ -219,32 +217,32 @@ class SalesInvoice(SellingController): 2. split into multiple rows if partially adjusted, assign against voucher 3. submit advance voucher """ - + lst = [] for d in self.get('advance_adjustment_details'): if flt(d.allocated_amount) > 0: args = { - 'voucher_no' : d.journal_voucher, - 'voucher_detail_no' : d.jv_detail_no, - 'against_voucher_type' : 'Sales Invoice', + 'voucher_no' : d.journal_voucher, + 'voucher_detail_no' : d.jv_detail_no, + 'against_voucher_type' : 'Sales Invoice', 'against_voucher' : self.name, - 'account' : self.debit_to, - 'is_advance' : 'Yes', - 'dr_or_cr' : 'credit', + 'account' : self.debit_to, + 'is_advance' : 'Yes', + 'dr_or_cr' : 'credit', 'unadjusted_amt' : flt(d.advance_amount), 'allocated_amt' : flt(d.allocated_amount) } lst.append(args) - + if lst: from erpnext.accounts.utils import reconcile_against_document reconcile_against_document(lst) - + def validate_customer_account(self): """Validates Debit To Account and Customer Matches""" if self.customer and self.debit_to and not cint(self.is_pos): acc_head = frappe.db.sql("select master_name from `tabAccount` where name = %s and docstatus != 2", self.debit_to) - + if (acc_head and cstr(acc_head[0][0]) != cstr(self.customer)) or \ (not acc_head and (self.debit_to != cstr(self.customer) + " - " + self.get_company_abbr())): msgprint("Debit To: %s do not match with Customer: %s for Company: %s.\n If both correctly entered, please select Master Type \ @@ -254,20 +252,20 @@ class SalesInvoice(SellingController): def validate_debit_acc(self): if frappe.db.get_value("Account", self.debit_to, "report_type") != "Balance Sheet": frappe.throw(_("Account must be a balance sheet account")) - + def validate_fixed_asset_account(self): """Validate Fixed Asset and whether Income Account Entered Exists""" for d in self.get('entries'): - item = frappe.db.sql("""select name,is_asset_item,is_sales_item from `tabItem` - where name = %s and (ifnull(end_of_life,'')='' or end_of_life = '0000-00-00' + item = frappe.db.sql("""select name,is_asset_item,is_sales_item from `tabItem` + where name = %s and (ifnull(end_of_life,'')='' or end_of_life = '0000-00-00' or end_of_life > now())""", d.item_code) - acc = frappe.db.sql("""select account_type from `tabAccount` + acc = frappe.db.sql("""select account_type from `tabAccount` where name = %s and docstatus != 2""", d.income_account) if not acc: msgprint("Account: "+d.income_account+" does not exist in the system", raise_exception=True) elif item and item[0][1] == 'Yes' and not acc[0][0] == 'Fixed Asset': - msgprint("Please select income head with account type 'Fixed Asset' as Item %s is an asset item" % d.item_code, raise_exception=True) - + msgprint("Please select income head with account type 'Fixed Asset' as Item %s is an asset item" % d.item_code, raise_exception=True) + def validate_with_previous_doc(self): super(SalesInvoice, self).validate_with_previous_doc(self.tname, { "Sales Order": { @@ -281,7 +279,7 @@ class SalesInvoice(SellingController): ["currency", "="]], }, }) - + if cint(frappe.defaults.get_global_default('maintain_same_sales_rate')): super(SalesInvoice, self).validate_with_previous_doc(self.tname, { "Sales Order Item": { @@ -296,7 +294,7 @@ class SalesInvoice(SellingController): "is_child_table": True } }) - + def set_aging_date(self): if self.is_opening != 'Yes': @@ -304,7 +302,7 @@ class SalesInvoice(SellingController): elif not self.aging_date: msgprint("Aging Date is mandatory for opening entry") raise Exception - + def set_against_income_account(self): """Set against account for debit to account""" @@ -333,8 +331,8 @@ class SalesInvoice(SellingController): def validate_proj_cust(self): """check for does customer belong to same project as entered..""" if self.project_name and self.customer: - res = frappe.db.sql("""select name from `tabProject` - where name = %s and (customer = %s or + res = frappe.db.sql("""select name from `tabProject` + where name = %s and (customer = %s or ifnull(customer,'')='')""", (self.project_name, self.customer)) if not res: msgprint("Customer - %s does not belong to project - %s. \n\nIf you want to use project for multiple customers then please make customer details blank in that project."%(self.customer,self.project_name)) @@ -355,7 +353,7 @@ class SalesInvoice(SellingController): if not d.item_code: msgprint("Please enter Item Code at line no : %s to update stock or remove check from Update Stock in Basic Info Tab." % (d.idx), raise_exception=True) - + def validate_delivery_note(self): for d in self.get("entries"): if d.delivery_note: @@ -374,7 +372,7 @@ class SalesInvoice(SellingController): and parent = %s""", (self.amended_from, self.c_form_no)) frappe.db.set(self, 'c_form_no', '') - + def update_current_stock(self): for d in self.get('entries'): if d.item_code and d.warehouse: @@ -385,15 +383,15 @@ class SalesInvoice(SellingController): bin = frappe.db.sql("select actual_qty, projected_qty from `tabBin` where item_code = %s and warehouse = %s", (d.item_code, d.warehouse), as_dict = 1) d.actual_qty = bin and flt(bin[0]['actual_qty']) or 0 d.projected_qty = bin and flt(bin[0]['projected_qty']) or 0 - - + + def get_warehouse(self): - w = frappe.db.sql("""select warehouse from `tabPOS Setting` - where ifnull(user,'') = %s and company = %s""", + w = frappe.db.sql("""select warehouse from `tabPOS Setting` + where ifnull(user,'') = %s and company = %s""", (frappe.session['user'], self.company)) w = w and w[0][0] or '' if not w: - ps = frappe.db.sql("""select name, warehouse from `tabPOS Setting` + ps = frappe.db.sql("""select name, warehouse from `tabPOS Setting` where ifnull(user,'') = '' and company = %s""", self.company) if not ps: msgprint("To make POS entry, please create POS Setting from Accounts --> POS Setting page and refresh the system.", raise_exception=True) @@ -417,11 +415,11 @@ class SalesInvoice(SellingController): make_packing_list(self, 'entries') else: self.set('packing_details', []) - + if cint(self.is_pos) == 1: if flt(self.paid_amount) == 0: - if self.cash_bank_account: - frappe.db.set(self, 'paid_amount', + if self.cash_bank_account: + frappe.db.set(self, 'paid_amount', (flt(self.grand_total) - flt(self.write_off_amount))) else: # show message that the amount is not paid @@ -429,18 +427,18 @@ class SalesInvoice(SellingController): frappe.msgprint("Note: Payment Entry will not be created since 'Cash/Bank Account' was not specified.") else: frappe.db.set(self,'paid_amount',0) - + def check_prev_docstatus(self): for d in self.get('entries'): if d.sales_order: - submitted = frappe.db.sql("""select name from `tabSales Order` + submitted = frappe.db.sql("""select name from `tabSales Order` where docstatus = 1 and name = %s""", d.sales_order) if not submitted: msgprint("Sales Order : "+ cstr(d.sales_order) +" is not submitted") raise Exception , "Validation Error." if d.delivery_note: - submitted = frappe.db.sql("""select name from `tabDelivery Note` + submitted = frappe.db.sql("""select name from `tabDelivery Note` where docstatus = 1 and name = %s""", d.delivery_note) if not submitted: msgprint("Delivery Note : "+ cstr(d.delivery_note) +" is not submitted") @@ -455,45 +453,44 @@ class SalesInvoice(SellingController): "actual_qty": -1*flt(d.qty), "stock_uom": frappe.db.get_value("Item", d.item_code, "stock_uom") })) - + self.make_sl_entries(sl_entries) - + def make_gl_entries(self, repost_future_gle=True): gl_entries = self.get_gl_entries() - + if gl_entries: from erpnext.accounts.general_ledger import make_gl_entries - + update_outstanding = cint(self.is_pos) and self.write_off_account \ and 'No' or 'Yes' - make_gl_entries(gl_entries, cancel=(self.docstatus == 2), + make_gl_entries(gl_entries, cancel=(self.docstatus == 2), update_outstanding=update_outstanding, merge_entries=False) 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() - from controllers.stock_controller import update_gl_entries_after - update_gl_entries_after(self.posting_date, self.posting_time, + update_gl_entries_after(self.posting_date, self.posting_time, warehouse_account, items) - + def get_gl_entries(self, warehouse_account=None): from erpnext.accounts.general_ledger import merge_similar_entries - + gl_entries = [] - + self.make_customer_gl_entry(gl_entries) - + self.make_tax_gl_entries(gl_entries) - + self.make_item_gl_entries(gl_entries) - + # merge gl entries before adding pos entries gl_entries = merge_similar_entries(gl_entries) - + self.make_pos_gl_entries(gl_entries) - + return gl_entries - + def make_customer_gl_entry(self, gl_entries): if self.grand_total: gl_entries.append( @@ -506,7 +503,7 @@ class SalesInvoice(SellingController): "against_voucher_type": self.doctype, }) ) - + def make_tax_gl_entries(self, gl_entries): for tax in self.get("other_charges"): if flt(tax.tax_amount_after_discount_amount): @@ -519,9 +516,9 @@ class SalesInvoice(SellingController): "cost_center": tax.cost_center }) ) - - def make_item_gl_entries(self, gl_entries): - # income account gl entries + + def make_item_gl_entries(self, gl_entries): + # income account gl entries for item in self.get("entries"): if flt(item.base_amount): gl_entries.append( @@ -533,12 +530,12 @@ class SalesInvoice(SellingController): "cost_center": item.cost_center }) ) - + # expense account gl entries if cint(frappe.defaults.get_global_default("auto_accounting_for_stock")) \ and cint(self.update_stock): gl_entries += super(SalesInvoice, self).get_gl_entries() - + def make_pos_gl_entries(self, gl_entries): if cint(self.is_pos) and self.cash_bank_account and self.paid_amount: # POS, make payment entries @@ -581,81 +578,81 @@ class SalesInvoice(SellingController): "cost_center": self.write_off_cost_center }) ) - + def update_c_form(self): """Update amended id in C-form""" if self.c_form_no and self.amended_from: frappe.db.sql("""update `tabC-Form Invoice Detail` set invoice_no = %s, invoice_date = %s, territory = %s, net_total = %s, - grand_total = %s where invoice_no = %s and parent = %s""", + grand_total = %s where invoice_no = %s and parent = %s""", (self.name, self.amended_from, self.c_form_no)) - + def validate_recurring_invoice(self): if self.convert_into_recurring_invoice: self.validate_notification_email_id() - + if not self.recurring_type: msgprint(_("Please select: ") + self.meta.get_label("recurring_type"), raise_exception=1) - + elif not (self.invoice_period_from_date and \ self.invoice_period_to_date): msgprint(comma_and([self.meta.get_label("invoice_period_from_date"), self.meta.get_label("invoice_period_to_date")]) + _(": Mandatory for a Recurring Invoice."), raise_exception=True) - + def convert_to_recurring(self): if self.convert_into_recurring_invoice: if not self.recurring_id: frappe.db.set(self, "recurring_id", make_autoname("RECINV/.#####")) - + self.set_next_date() elif self.recurring_id: frappe.db.sql("""update `tabSales Invoice` set convert_into_recurring_invoice = 0 where recurring_id = %s""", (self.recurring_id,)) - + def validate_notification_email_id(self): if self.notification_email_address: email_list = filter(None, [cstr(email).strip() for email in self.notification_email_address.replace("\n", "").split(",")]) - + from frappe.utils import validate_email_add for email in email_list: if not validate_email_add(email): msgprint(self.meta.get_label("notification_email_address") \ + " - " + _("Invalid Email Address") + ": \"%s\"" % email, raise_exception=1) - + else: msgprint("Notification Email Addresses not specified for recurring invoice", raise_exception=1) - + def set_next_date(self): """ Set next date on which auto invoice will be created""" if not self.repeat_on_day_of_month: - msgprint("""Please enter 'Repeat on Day of Month' field value. - The day of the month on which auto invoice + msgprint("""Please enter 'Repeat on Day of Month' field value. + The day of the month on which auto invoice will be generated e.g. 05, 28 etc.""", raise_exception=1) - + next_date = get_next_date(self.posting_date, month_map[self.recurring_type], cint(self.repeat_on_day_of_month)) - + frappe.db.set(self, 'next_date', next_date) - + def get_next_date(dt, mcount, day=None): dt = getdate(dt) - + from dateutil.relativedelta import relativedelta dt += relativedelta(months=mcount, day=day) - + return dt - + def manage_recurring_invoices(next_date=None, commit=True): - """ + """ Create recurring invoices on specific date by copying the original one and notify the concerned people """ @@ -664,7 +661,7 @@ def manage_recurring_invoices(next_date=None, commit=True): from `tabSales Invoice` where ifnull(convert_into_recurring_invoice, 0)=1 and docstatus=1 and next_date=%s and next_date <= ifnull(end_date, '2199-12-31')""", next_date) - + exception_list = [] for ref_invoice, recurring_id in recurring_invoices: if not frappe.db.sql("""select name from `tabSales Invoice` @@ -690,21 +687,20 @@ def manage_recurring_invoices(next_date=None, commit=True): finally: if commit: frappe.db.begin() - + if exception_list: exception_message = "\n\n".join([cstr(d) for d in exception_list]) raise Exception, exception_message def make_new_invoice(ref_wrapper, posting_date): - from frappe.model.doc import clone from erpnext.accounts.utils import get_fiscal_year - new_invoice = clone(ref_wrapper) - + new_invoice = frappe.copy_doc(ref_wrapper) + mcount = month_map[ref_wrapper.recurring_type] - + invoice_period_from_date = get_next_date(ref_wrapper.invoice_period_from_date, mcount) - - # get last day of the month to maintain period if the from date is first day of its own month + + # get last day of the month to maintain period if the from date is first day of its own month # and to date is the last day of its own month if (cstr(get_first_day(ref_wrapper.invoice_period_from_date)) == \ cstr(ref_wrapper.invoice_period_from_date)) and \ @@ -714,7 +710,7 @@ def make_new_invoice(ref_wrapper, posting_date): mcount)) else: invoice_period_to_date = get_next_date(ref_wrapper.invoice_period_to_date, mcount) - + new_invoice.update({ "posting_date": posting_date, "aging_date": posting_date, @@ -725,30 +721,30 @@ def make_new_invoice(ref_wrapper, posting_date): "fiscal_year": get_fiscal_year(posting_date)[0], "owner": ref_wrapper.owner, }) - + new_invoice.submit() - + return new_invoice - + def send_notification(new_rv): """Notify concerned persons about recurring invoice generation""" - + from frappe.core.doctype.print_format.print_format import get_html - frappe.sendmail(new_rv.notification_email_address, - subject="New Invoice : " + new_rv.name, + frappe.sendmail(new_rv.notification_email_address, + subject="New Invoice : " + new_rv.name, message = get_html(new_rv, new_rv, "SalesInvoice")) - + def notify_errors(inv, customer, owner): from frappe.utils.user import get_system_managers recipients=get_system_managers() - + frappe.sendmail(recipients + [frappe.db.get_value("User", owner, "email")], subject="[Urgent] Error while creating recurring invoice for %s" % inv, message = frappe.get_template("template/emails/recurring_invoice_failed.html").render({ "name": inv, "customer": customer })) - + assign_task_to_owner(inv, "Recurring Invoice Failed", recipients) def assign_task_to_owner(inv, msg, users): @@ -776,64 +772,64 @@ def get_bank_cash_account(mode_of_payment): def get_income_account(doctype, txt, searchfield, start, page_len, filters): from erpnext.controllers.queries import get_match_cond - # income account can be any Credit account, - # but can also be a Asset account with account_type='Income Account' in special circumstances. + # income account can be any Credit account, + # but can also be a Asset account with account_type='Income Account' in special circumstances. # Hence the first condition is an "OR" - return frappe.db.sql("""select tabAccount.name from `tabAccount` + return frappe.db.sql("""select tabAccount.name from `tabAccount` where (tabAccount.report_type = "Profit and Loss" - or tabAccount.account_type = "Income Account") - and tabAccount.group_or_ledger="Ledger" + or tabAccount.account_type = "Income Account") + and tabAccount.group_or_ledger="Ledger" and tabAccount.docstatus!=2 and ifnull(tabAccount.master_type, "")="" and ifnull(tabAccount.master_name, "")="" - and tabAccount.company = '%(company)s' + and tabAccount.company = '%(company)s' and tabAccount.%(key)s LIKE '%(txt)s' - %(mcond)s""" % {'company': filters['company'], 'key': searchfield, + %(mcond)s""" % {'company': filters['company'], 'key': searchfield, 'txt': "%%%s%%" % txt, 'mcond':get_match_cond(doctype)}) @frappe.whitelist() def make_delivery_note(source_name, target_doc=None): from frappe.model.mapper import get_mapped_doc - + def set_missing_values(source, target): doc = frappe.get_doc(target) doc.run_method("onload_post_render") - + def update_item(source_doc, target_doc, source_parent): target_doc.base_amount = (flt(source_doc.qty) - flt(source_doc.delivered_qty)) * \ flt(source_doc.base_rate) target_doc.amount = (flt(source_doc.qty) - flt(source_doc.delivered_qty)) * \ flt(source_doc.rate) target_doc.qty = flt(source_doc.qty) - flt(source_doc.delivered_qty) - + doclist = get_mapped_doc("Sales Invoice", source_name, { "Sales Invoice": { - "doctype": "Delivery Note", + "doctype": "Delivery Note", "validation": { "docstatus": ["=", 1] } - }, + }, "Sales Invoice Item": { - "doctype": "Delivery Note Item", + "doctype": "Delivery Note Item", "field_map": { - "name": "prevdoc_detail_docname", - "parent": "against_sales_invoice", + "name": "prevdoc_detail_docname", + "parent": "against_sales_invoice", "serial_no": "serial_no" }, "postprocess": update_item - }, + }, "Sales Taxes and Charges": { - "doctype": "Sales Taxes and Charges", + "doctype": "Sales Taxes and Charges", "add_if_empty": True - }, + }, "Sales Team": { - "doctype": "Sales Team", + "doctype": "Sales Team", "field_map": { "incentives": "incentives" }, "add_if_empty": True } }, target_doc, set_missing_values) - - return doclist.as_dict() \ No newline at end of file + + return doclist diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 8baeb76d992..2a0bc248a0f 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2,7 +2,7 @@ # License: GNU General Public License v3. See license.txt import frappe -import unittest, json +import unittest, json, copy from frappe.utils import flt from erpnext.accounts.utils import get_stock_and_account_difference from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory @@ -14,57 +14,46 @@ class TestSalesInvoice(unittest.TestCase): w.insert() w.submit() return w - - def test_double_submission(self): - w = frappe.copy_doc(test_records[0]) - w.docstatus = '0' - w.insert() - - w2 = frappe.copy_doc(test_records[0]) - w.submit() - - w = frappe.get_doc(w2) - self.assertRaises(frappe.DocstatusTransitionError, w.submit) - + def test_timestamp_change(self): w = frappe.copy_doc(test_records[0]) - w.docstatus = '0' + w.docstatus = 0 w.insert() - w2 = frappe.copy_doc(w) - + w2 = frappe.get_doc(w.doctype, w.name) + import time time.sleep(1) w.save() - + import time time.sleep(1) self.assertRaises(frappe.TimestampMismatchError, w2.save) - + def test_sales_invoice_calculation_base_currency(self): si = frappe.copy_doc(test_records[2]) si.insert() - + expected_values = { - "keys": ["price_list_rate", "discount_percentage", "rate", "amount", + "keys": ["price_list_rate", "discount_percentage", "rate", "amount", "base_price_list_rate", "base_rate", "base_amount"], "_Test Item Home Desktop 100": [50, 0, 50, 500, 50, 50, 500], "_Test Item Home Desktop 200": [150, 0, 150, 750, 150, 150, 750], } - + # check if children are saved self.assertEquals(len(si.get("entries")), len(expected_values)-1) - + # check if item values are calculated for d in si.get("entries"): for i, k in enumerate(expected_values["keys"]): self.assertEquals(d.get(k), expected_values[d.item_code][i]) - + # check net total self.assertEquals(si.net_total, 1250) self.assertEquals(si.net_total_export, 1250) - + # check tax calculation expected_values = { "keys": ["tax_amount", "total"], @@ -77,14 +66,14 @@ class TestSalesInvoice(unittest.TestCase): "_Test Account VAT - _TC": [156.25, 1807.83], "_Test Account Discount - _TC": [-180.78, 1627.05] } - + for d in si.get("other_charges"): for i, k in enumerate(expected_values["keys"]): self.assertEquals(d.get(k), expected_values[d.account_head][i]) - + self.assertEquals(si.grand_total, 1627.05) self.assertEquals(si.grand_total_export, 1627.05) - + def test_sales_invoice_calculation_export_currency(self): si = frappe.copy_doc(test_records[2]) si.currency = "USD" @@ -94,27 +83,27 @@ class TestSalesInvoice(unittest.TestCase): si.get("entries")[1].rate = 3 si.get("entries")[1].price_list_rate = 3 si.insert() - + expected_values = { - "keys": ["price_list_rate", "discount_percentage", "rate", "amount", + "keys": ["price_list_rate", "discount_percentage", "rate", "amount", "base_price_list_rate", "base_rate", "base_amount"], "_Test Item Home Desktop 100": [1, 0, 1, 10, 50, 50, 500], "_Test Item Home Desktop 200": [3, 0, 3, 15, 150, 150, 750], } - + # check if children are saved self.assertEquals(len(si.get("entries")), len(expected_values)-1) - + # check if item values are calculated for d in si.get("entries"): for i, k in enumerate(expected_values["keys"]): self.assertEquals(d.get(k), expected_values[d.item_code][i]) - + # check net total self.assertEquals(si.net_total, 1250) self.assertEquals(si.net_total_export, 25) - + # check tax calculation expected_values = { "keys": ["tax_amount", "total"], @@ -127,11 +116,11 @@ class TestSalesInvoice(unittest.TestCase): "_Test Account VAT - _TC": [156.25, 1807.83], "_Test Account Discount - _TC": [-180.78, 1627.05] } - + for d in si.get("other_charges"): for i, k in enumerate(expected_values["keys"]): self.assertEquals(d.get(k), expected_values[d.account_head][i]) - + self.assertEquals(si.grand_total, 1627.05) self.assertEquals(si.grand_total_export, 32.54) @@ -148,27 +137,27 @@ class TestSalesInvoice(unittest.TestCase): "row_id": 8, }) si.insert() - + expected_values = { - "keys": ["price_list_rate", "discount_percentage", "rate", "amount", + "keys": ["price_list_rate", "discount_percentage", "rate", "amount", "base_price_list_rate", "base_rate", "base_amount"], "_Test Item Home Desktop 100": [62.5, 0, 62.5, 625.0, 50, 50, 465.37], "_Test Item Home Desktop 200": [190.66, 0, 190.66, 953.3, 150, 150, 698.08], } - + # check if children are saved self.assertEquals(len(si.get("entries")), len(expected_values)-1) - + # check if item values are calculated for d in si.get("entries"): for i, k in enumerate(expected_values["keys"]): self.assertEquals(d.get(k), expected_values[d.item_code][i]) - + # check net total self.assertEquals(si.net_total, 1163.45) self.assertEquals(si.net_total_export, 1578.3) - + # check tax calculation expected_values = { "keys": ["tax_amount", "tax_amount_after_discount_amount", "total"], @@ -182,11 +171,11 @@ class TestSalesInvoice(unittest.TestCase): "_Test Account Discount - _TC": [-180.33, -168.54, 1516.88], "_Test Account Service Tax - _TC": [-18.03, -16.88, 1500] } - + for d in si.get("other_charges"): for i, k in enumerate(expected_values["keys"]): self.assertEquals(d.get(k), expected_values[d.account_head][i]) - + self.assertEquals(si.grand_total, 1500) self.assertEquals(si.grand_total_export, 1500) @@ -213,15 +202,15 @@ class TestSalesInvoice(unittest.TestCase): expected_values = sorted([ [si.debit_to, 1500, 0.0], - [test_records[3][1]["income_account"], 0.0, 1163.45], - [test_records[3][3]["account_head"], 0.0, 130.31], - [test_records[3][4]["account_head"], 0.0, 2.61], - [test_records[3][5]["account_head"], 0.0, 1.31], - [test_records[3][6]["account_head"], 0.0, 25.96], - [test_records[3][7]["account_head"], 0.0, 145.43], - [test_records[3][8]["account_head"], 0.0, 116.35], - [test_records[3][9]["account_head"], 0.0, 100], - [test_records[3][10]["account_head"], 168.54, 0.0], + [test_records[3]["entries"][0]["income_account"], 0.0, 1163.45], + [test_records[3]["other_charges"][0]["account_head"], 0.0, 130.31], + [test_records[3]["other_charges"][1]["account_head"], 0.0, 2.61], + [test_records[3]["other_charges"][2]["account_head"], 0.0, 1.31], + [test_records[3]["other_charges"][3]["account_head"], 0.0, 25.96], + [test_records[3]["other_charges"][4]["account_head"], 0.0, 145.43], + [test_records[3]["other_charges"][5]["account_head"], 0.0, 116.35], + [test_records[3]["other_charges"][6]["account_head"], 0.0, 100], + [test_records[3]["other_charges"][7]["account_head"], 168.54, 0.0], ["_Test Account Service Tax - _TC", 16.88, 0.0], ]) @@ -233,7 +222,7 @@ class TestSalesInvoice(unittest.TestCase): # cancel si.cancel() - gle = frappe.db.sql("""select * from `tabGL Entry` + gle = frappe.db.sql("""select * from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s""", si.name) self.assertFalse(gle) @@ -242,44 +231,44 @@ class TestSalesInvoice(unittest.TestCase): si = frappe.copy_doc(test_records[2]) for i, tax in enumerate(si.get("other_charges")): tax.idx = i+1 - + si.get("entries")[0].price_list_rate = 62.5 si.get("entries")[0].price_list_rate = 191 - for i in [2, 4, 5, 6, 7, 8]: + for i in xrange(6): si.get("other_charges")[i].included_in_print_rate = 1 - + # tax type "Actual" cannot be inclusive self.assertRaises(frappe.ValidationError, si.insert) - + # taxes above included type 'On Previous Row Total' should also be included si.get("other_charges")[0].included_in_print_rate = 0 self.assertRaises(frappe.ValidationError, si.insert) - + def test_sales_invoice_calculation_base_currency_with_tax_inclusive_price(self): # prepare si = frappe.copy_doc(test_records[3]) si.insert() - + expected_values = { - "keys": ["price_list_rate", "discount_percentage", "rate", "amount", + "keys": ["price_list_rate", "discount_percentage", "rate", "amount", "base_price_list_rate", "base_rate", "base_amount"], "_Test Item Home Desktop 100": [62.5, 0, 62.5, 625.0, 50, 50, 499.98], "_Test Item Home Desktop 200": [190.66, 0, 190.66, 953.3, 150, 150, 750], } - + # check if children are saved self.assertEquals(len(si.get("entries")), len(expected_values)-1) - + # check if item values are calculated for d in si.get("entries"): for i, k in enumerate(expected_values["keys"]): self.assertEquals(d.get(k), expected_values[d.item_code][i]) - + # check net total self.assertEquals(si.net_total, 1249.98) self.assertEquals(si.net_total_export, 1578.3) - + # check tax calculation expected_values = { "keys": ["tax_amount", "total"], @@ -292,14 +281,14 @@ class TestSalesInvoice(unittest.TestCase): "_Test Account Shipping Charges - _TC": [100, 1803.31], "_Test Account Discount - _TC": [-180.33, 1622.98] } - + for d in si.get("other_charges"): for i, k in enumerate(expected_values["keys"]): self.assertEquals(d.get(k), expected_values[d.account_head][i]) - + self.assertEquals(si.grand_total, 1622.98) self.assertEquals(si.grand_total_export, 1622.98) - + def test_sales_invoice_calculation_export_currency_with_tax_inclusive_price(self): # prepare si = frappe.copy_doc(test_records[3]) @@ -309,30 +298,29 @@ class TestSalesInvoice(unittest.TestCase): si.get("entries")[0].discount_percentage = 10 si.get("entries")[1].price_list_rate = 187.5 si.get("entries")[1].discount_percentage = 20 - si.get("other_charges")[5].rate = 5000 - + si.get("other_charges")[6].rate = 5000 + si.insert() - + expected_values = { - "keys": ["price_list_rate", "discount_percentage", "rate", "amount", + "keys": ["price_list_rate", "discount_percentage", "rate", "amount", "base_price_list_rate", "base_rate", "base_amount"], "_Test Item Home Desktop 100": [55.56, 10, 50, 500, 2222.11, 1999.9, 19999.04], "_Test Item Home Desktop 200": [187.5, 20, 150, 750, 7375.66, 5900.53, 29502.66], } - + # check if children are saved - self.assertEquals(len(si.get("entries")), - len(expected_values)-1) - + self.assertEquals(len(si.get("entries")), len(expected_values)-1) + # check if item values are calculated for d in si.get("entries"): for i, k in enumerate(expected_values["keys"]): self.assertEquals(d.get(k), expected_values[d.item_code][i]) - + # check net total self.assertEquals(si.net_total, 49501.7) self.assertEquals(si.net_total_export, 1250) - + # check tax calculation expected_values = { "keys": ["tax_amount", "total"], @@ -345,134 +333,134 @@ class TestSalesInvoice(unittest.TestCase): "_Test Account Shipping Charges - _TC": [5000, 72450.17], "_Test Account Discount - _TC": [-7245.01, 65205.16] } - + for d in si.get("other_charges"): for i, k in enumerate(expected_values["keys"]): self.assertEquals(d.get(k), expected_values[d.account_head][i]) - + self.assertEquals(si.grand_total, 65205.16) self.assertEquals(si.grand_total_export, 1304.1) def test_outstanding(self): w = self.make() self.assertEquals(w.outstanding_amount, w.grand_total) - + def test_payment(self): frappe.db.sql("""delete from `tabGL Entry`""") w = self.make() - + from erpnext.accounts.doctype.journal_voucher.test_journal_voucher \ import test_records as jv_test_records - + jv = frappe.get_doc(frappe.copy_doc(jv_test_records[0])) jv.get("entries")[0].against_invoice = w.name jv.insert() jv.submit() - + self.assertEquals(frappe.db.get_value("Sales Invoice", w.name, "outstanding_amount"), 161.8) - + jv.cancel() self.assertEquals(frappe.db.get_value("Sales Invoice", w.name, "outstanding_amount"), 561.8) - + def test_time_log_batch(self): tlb = frappe.get_doc("Time Log Batch", "_T-Time Log Batch-00001") tlb.submit() - + si = frappe.get_doc(frappe.copy_doc(test_records[0])) si.get("entries")[0].time_log_batch = "_T-Time Log Batch-00001" si.insert() si.submit() - + self.assertEquals(frappe.db.get_value("Time Log Batch", "_T-Time Log Batch-00001", "status"), "Billed") - self.assertEquals(frappe.db.get_value("Time Log", "_T-Time Log-00001", "status"), + self.assertEquals(frappe.db.get_value("Time Log", "_T-Time Log-00001", "status"), "Billed") si.cancel() - self.assertEquals(frappe.db.get_value("Time Log Batch", "_T-Time Log Batch-00001", + self.assertEquals(frappe.db.get_value("Time Log Batch", "_T-Time Log Batch-00001", "status"), "Submitted") - self.assertEquals(frappe.db.get_value("Time Log", "_T-Time Log-00001", "status"), + self.assertEquals(frappe.db.get_value("Time Log", "_T-Time Log-00001", "status"), "Batched for Billing") - + def test_sales_invoice_gl_entry_without_aii(self): self.clear_stock_account_balance() set_perpetual_inventory(0) si = frappe.copy_doc(test_records[1]) si.insert() si.submit() - + gl_entries = frappe.db.sql("""select account, debit, credit from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s order by account asc""", si.name, as_dict=1) - + self.assertTrue(gl_entries) - + expected_values = sorted([ [si.debit_to, 630.0, 0.0], - [test_records[1][1]["income_account"], 0.0, 500.0], - [test_records[1][2]["account_head"], 0.0, 80.0], - [test_records[1][3]["account_head"], 0.0, 50.0], + [test_records[1]["entries"][0]["income_account"], 0.0, 500.0], + [test_records[1]["other_charges"][0]["account_head"], 0.0, 80.0], + [test_records[1]["other_charges"][1]["account_head"], 0.0, 50.0], ]) - + for i, gle in enumerate(gl_entries): self.assertEquals(expected_values[i][0], gle.account) self.assertEquals(expected_values[i][1], gle.debit) self.assertEquals(expected_values[i][2], gle.credit) - + # cancel si.cancel() - - gle = frappe.db.sql("""select * from `tabGL Entry` + + gle = frappe.db.sql("""select * from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s""", si.name) - + self.assertFalse(gle) - + def test_pos_gl_entry_with_aii(self): self.clear_stock_account_balance() set_perpetual_inventory() - + self._insert_purchase_receipt() self._insert_pos_settings() - - pos = frappe.copy_doc(test_records[1]) - pos[0]["is_pos"] = 1 - pos[0]["update_stock"] = 1 - pos[0]["posting_time"] = "12:05" - pos[0]["cash_bank_account"] = "_Test Account Bank Account - _TC" - pos[0]["paid_amount"] = 600.0 + + pos = copy.deepcopy(test_records[1]) + pos["is_pos"] = 1 + pos["update_stock"] = 1 + pos["posting_time"] = "12:05" + pos["cash_bank_account"] = "_Test Account Bank Account - _TC" + pos["paid_amount"] = 600.0 si = frappe.copy_doc(pos) si.insert() si.submit() - + # check stock ledger entries - sle = frappe.db.sql("""select * from `tabStock Ledger Entry` - where voucher_type = 'Sales Invoice' and voucher_no = %s""", + sle = frappe.db.sql("""select * from `tabStock Ledger Entry` + where voucher_type = 'Sales Invoice' and voucher_no = %s""", si.name, as_dict=1)[0] self.assertTrue(sle) - self.assertEquals([sle.item_code, sle.warehouse, sle.actual_qty], + self.assertEquals([sle.item_code, sle.warehouse, sle.actual_qty], ["_Test Item", "_Test Warehouse - _TC", -1.0]) - + # check gl entries gl_entries = frappe.db.sql("""select account, debit, credit from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s order by account asc, debit asc""", si.name, as_dict=1) self.assertTrue(gl_entries) - + stock_in_hand = frappe.db.get_value("Account", {"master_name": "_Test Warehouse - _TC"}) - + expected_gl_entries = sorted([ [si.debit_to, 630.0, 0.0], - [pos[1]["income_account"], 0.0, 500.0], - [pos[2]["account_head"], 0.0, 80.0], - [pos[3]["account_head"], 0.0, 50.0], + [pos["entries"][0]["income_account"], 0.0, 500.0], + [pos["other_charges"][0]["account_head"], 0.0, 80.0], + [pos["other_charges"][1]["account_head"], 0.0, 50.0], [stock_in_hand, 0.0, 75.0], - [pos[1]["expense_account"], 75.0, 0.0], + [pos["entries"][0]["expense_account"], 75.0, 0.0], [si.debit_to, 0.0, 600.0], ["_Test Account Bank Account - _TC", 600.0, 0.0] ]) @@ -480,57 +468,57 @@ class TestSalesInvoice(unittest.TestCase): self.assertEquals(expected_gl_entries[i][0], gle.account) self.assertEquals(expected_gl_entries[i][1], gle.debit) self.assertEquals(expected_gl_entries[i][2], gle.credit) - + si.cancel() - gle = frappe.db.sql("""select * from `tabGL Entry` + gle = frappe.db.sql("""select * from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s""", si.name) - + self.assertFalse(gle) - + self.assertFalse(get_stock_and_account_difference([stock_in_hand])) - + set_perpetual_inventory(0) - + def test_si_gl_entry_with_aii_and_update_stock_with_warehouse_but_no_account(self): self.clear_stock_account_balance() set_perpetual_inventory() frappe.delete_doc("Account", "_Test Warehouse No Account - _TC") - + # insert purchase receipt from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import test_records \ as pr_test_records pr = frappe.copy_doc(pr_test_records[0]) pr.naming_series = "_T-Purchase Receipt-" - pr.get("entries")[0].warehouse = "_Test Warehouse No Account - _TC" + pr.get("purchase_receipt_details")[0].warehouse = "_Test Warehouse No Account - _TC" pr.insert() pr.submit() - - si_doc = frappe.copy_doc(test_records[1]) + + si_doc = copy.deepcopy(test_records[1]) si_doc["update_stock"] = 1 si_doc["posting_time"] = "12:05" - si_doc.get("entries")["warehouse"] = "_Test Warehouse No Account - _TC" + si_doc.get("entries")[0]["warehouse"] = "_Test Warehouse No Account - _TC" si = frappe.copy_doc(si_doc) si.insert() si.submit() - + # check stock ledger entries - sle = frappe.db.sql("""select * from `tabStock Ledger Entry` - where voucher_type = 'Sales Invoice' and voucher_no = %s""", + sle = frappe.db.sql("""select * from `tabStock Ledger Entry` + where voucher_type = 'Sales Invoice' and voucher_no = %s""", si.name, as_dict=1)[0] self.assertTrue(sle) - self.assertEquals([sle.item_code, sle.warehouse, sle.actual_qty], + self.assertEquals([sle.item_code, sle.warehouse, sle.actual_qty], ["_Test Item", "_Test Warehouse No Account - _TC", -1.0]) - + # check gl entries gl_entries = frappe.db.sql("""select account, debit, credit from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s order by account asc, debit asc""", si.name, as_dict=1) self.assertTrue(gl_entries) - + expected_gl_entries = sorted([ [si.debit_to, 630.0, 0.0], - [si_doc.get("entries")["income_account"], 0.0, 500.0], + [si_doc.get("entries")[0]["income_account"], 0.0, 500.0], [si_doc.get("other_charges")[0]["account_head"], 0.0, 80.0], [si_doc.get("other_charges")[1]["account_head"], 0.0, 50.0], ]) @@ -538,69 +526,67 @@ class TestSalesInvoice(unittest.TestCase): self.assertEquals(expected_gl_entries[i][0], gle.account) self.assertEquals(expected_gl_entries[i][1], gle.debit) self.assertEquals(expected_gl_entries[i][2], gle.credit) - + si.cancel() - gle = frappe.db.sql("""select * from `tabGL Entry` + gle = frappe.db.sql("""select * from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s""", si.name) - + self.assertFalse(gle) set_perpetual_inventory(0) - + def test_sales_invoice_gl_entry_with_aii_no_item_code(self): self.clear_stock_account_balance() set_perpetual_inventory() - - si_copy = frappe.copy_doc(test_records[1]) - si_copy[1]["item_code"] = None - si = frappe.get_doc(si_copy) + + si = frappe.get_doc(test_records[1]) + si.get("entries")[0].item_code = None si.insert() si.submit() - + gl_entries = frappe.db.sql("""select account, debit, credit from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s order by account asc""", si.name, as_dict=1) self.assertTrue(gl_entries) - + expected_values = sorted([ [si.debit_to, 630.0, 0.0], - [test_records[1][1]["income_account"], 0.0, 500.0], - [test_records[1][2]["account_head"], 0.0, 80.0], - [test_records[1][3]["account_head"], 0.0, 50.0], + [test_records[1]["entries"][0]["income_account"], 0.0, 500.0], + [test_records[1]["other_charges"][0]["account_head"], 0.0, 80.0], + [test_records[1]["other_charges"][1]["account_head"], 0.0, 50.0], ]) for i, gle in enumerate(gl_entries): self.assertEquals(expected_values[i][0], gle.account) self.assertEquals(expected_values[i][1], gle.debit) self.assertEquals(expected_values[i][2], gle.credit) - + set_perpetual_inventory(0) - + def test_sales_invoice_gl_entry_with_aii_non_stock_item(self): self.clear_stock_account_balance() set_perpetual_inventory() - si_copy = frappe.copy_doc(test_records[1]) - si_copy[1]["item_code"] = "_Test Non Stock Item" - si = frappe.get_doc(si_copy) + si = frappe.get_doc(test_records[1]) + si.get("entries")[0].item_code = "_Test Non Stock Item" si.insert() si.submit() - + gl_entries = frappe.db.sql("""select account, debit, credit from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s order by account asc""", si.name, as_dict=1) self.assertTrue(gl_entries) - + expected_values = sorted([ [si.debit_to, 630.0, 0.0], - [test_records[1][1]["income_account"], 0.0, 500.0], - [test_records[1][2]["account_head"], 0.0, 80.0], - [test_records[1][3]["account_head"], 0.0, 50.0], + [test_records[1]["entries"][0]["income_account"], 0.0, 500.0], + [test_records[1]["other_charges"][0]["account_head"], 0.0, 80.0], + [test_records[1]["other_charges"][1]["account_head"], 0.0, 50.0], ]) for i, gle in enumerate(gl_entries): self.assertEquals(expected_values[i][0], gle.account) self.assertEquals(expected_values[i][1], gle.debit) self.assertEquals(expected_values[i][2], gle.credit) - + set_perpetual_inventory(0) - + def _insert_purchase_receipt(self): from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import test_records \ as pr_test_records @@ -608,7 +594,7 @@ class TestSalesInvoice(unittest.TestCase): pr.naming_series = "_T-Purchase Receipt-" pr.insert() pr.submit() - + def _insert_delivery_note(self): from erpnext.stock.doctype.delivery_note.test_delivery_note import test_records \ as dn_test_records @@ -617,23 +603,23 @@ class TestSalesInvoice(unittest.TestCase): dn.insert() dn.submit() return dn - + def _insert_pos_settings(self): from erpnext.accounts.doctype.pos_setting.test_pos_setting \ import test_records as pos_setting_test_records frappe.db.sql("""delete from `tabPOS Setting`""") - + ps = frappe.copy_doc(pos_setting_test_records[0]) ps.insert() - + def test_sales_invoice_with_advance(self): from erpnext.accounts.doctype.journal_voucher.test_journal_voucher \ import test_records as jv_test_records - + jv = frappe.copy_doc(jv_test_records[0]) jv.insert() jv.submit() - + si = frappe.copy_doc(test_records[0]) si.append("advance_adjustment_details", { "doctype": "Sales Invoice Advance", @@ -646,20 +632,20 @@ class TestSalesInvoice(unittest.TestCase): si.insert() si.submit() si.load_from_db() - + self.assertTrue(frappe.db.sql("""select name from `tabJournal Voucher Detail` where against_invoice=%s""", si.name)) - + self.assertTrue(frappe.db.sql("""select name from `tabJournal Voucher Detail` where against_invoice=%s and credit=300""", si.name)) - + self.assertEqual(si.outstanding_amount, 261.8) - + si.cancel() - + self.assertTrue(not frappe.db.sql("""select name from `tabJournal Voucher Detail` where against_invoice=%s""", si.name)) - + def test_recurring_invoice(self): from frappe.utils import get_first_day, get_last_day, add_to_date, nowdate, getdate from erpnext.accounts.utils import get_fiscal_year @@ -675,13 +661,13 @@ class TestSalesInvoice(unittest.TestCase): "invoice_period_from_date": get_first_day(today), "invoice_period_to_date": get_last_day(today) }) - + # monthly si1 = frappe.copy_doc(base_si) si1.insert() si1.submit() self._test_recurring_invoice(si1, True) - + # monthly without a first and last day period si2 = frappe.copy_doc(base_si) si2.update({ @@ -691,7 +677,7 @@ class TestSalesInvoice(unittest.TestCase): si2.insert() si2.submit() self._test_recurring_invoice(si2, False) - + # quarterly si3 = frappe.copy_doc(base_si) si3.update({ @@ -702,7 +688,7 @@ class TestSalesInvoice(unittest.TestCase): si3.insert() si3.submit() self._test_recurring_invoice(si3, True) - + # quarterly without a first and last day period si4 = frappe.copy_doc(base_si) si4.update({ @@ -713,7 +699,7 @@ class TestSalesInvoice(unittest.TestCase): si4.insert() si4.submit() self._test_recurring_invoice(si4, False) - + # yearly si5 = frappe.copy_doc(base_si) si5.update({ @@ -724,7 +710,7 @@ class TestSalesInvoice(unittest.TestCase): si5.insert() si5.submit() self._test_recurring_invoice(si5, True) - + # yearly without a first and last day period si6 = frappe.copy_doc(base_si) si6.update({ @@ -735,7 +721,7 @@ class TestSalesInvoice(unittest.TestCase): si6.insert() si6.submit() self._test_recurring_invoice(si6, False) - + # change posting date but keep recuring day to be today si7 = frappe.copy_doc(base_si) si7.update({ @@ -743,7 +729,7 @@ class TestSalesInvoice(unittest.TestCase): }) si7.insert() si7.submit() - + # setting so that _test function works si7.posting_date = today self._test_recurring_invoice(si7, True) @@ -752,52 +738,52 @@ class TestSalesInvoice(unittest.TestCase): from frappe.utils import add_months, get_last_day from erpnext.accounts.doctype.sales_invoice.sales_invoice \ import manage_recurring_invoices, get_next_date - + no_of_months = ({"Monthly": 1, "Quarterly": 3, "Yearly": 12})[base_si.recurring_type] - + def _test(i): self.assertEquals(i+1, frappe.db.sql("""select count(*) from `tabSales Invoice` where recurring_id=%s and docstatus=1""", base_si.recurring_id)[0][0]) - - next_date = get_next_date(base_si.posting_date, no_of_months, + + next_date = get_next_date(base_si.posting_date, no_of_months, base_si.repeat_on_day_of_month) manage_recurring_invoices(next_date=next_date, commit=False) - + recurred_invoices = frappe.db.sql("""select name from `tabSales Invoice` where recurring_id=%s and docstatus=1 order by name desc""", base_si.recurring_id) - + self.assertEquals(i+2, len(recurred_invoices)) - + new_si = frappe.get_doc("Sales Invoice", recurred_invoices[0][0]) - + for fieldname in ["convert_into_recurring_invoice", "recurring_type", "repeat_on_day_of_month", "notification_email_address"]: self.assertEquals(base_si.get(fieldname), new_si.get(fieldname)) self.assertEquals(new_si.posting_date, unicode(next_date)) - + self.assertEquals(new_si.invoice_period_from_date, unicode(add_months(base_si.invoice_period_from_date, no_of_months))) - + if first_and_last_day: - self.assertEquals(new_si.invoice_period_to_date, + self.assertEquals(new_si.invoice_period_to_date, unicode(get_last_day(add_months(base_si.invoice_period_to_date, no_of_months)))) else: - self.assertEquals(new_si.invoice_period_to_date, + self.assertEquals(new_si.invoice_period_to_date, unicode(add_months(base_si.invoice_period_to_date, no_of_months))) - - + + return new_si - + # if yearly, test 1 repetition, else test 5 repetitions count = 1 if (no_of_months == 12) else 5 for i in xrange(count): base_si = _test(i) - + def clear_stock_account_balance(self): frappe.db.sql("delete from `tabStock Ledger Entry`") frappe.db.sql("delete from tabBin") @@ -806,10 +792,10 @@ class TestSalesInvoice(unittest.TestCase): 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 - + se = make_serialized_item() - serial_nos = get_serial_nos(se.get("entries")[0].serial_no) - + serial_nos = get_serial_nos(se.get("mtn_details")[0].serial_no) + si = frappe.copy_doc(test_records[0]) si.update_stock = 1 si.get("entries")[0].item_code = "_Test Serialized Item With Series" @@ -817,14 +803,14 @@ class TestSalesInvoice(unittest.TestCase): si.get("entries")[0].serial_no = serial_nos[0] si.insert() si.submit() - + self.assertEquals(frappe.db.get_value("Serial No", serial_nos[0], "status"), "Delivered") self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0], "warehouse")) - self.assertEquals(frappe.db.get_value("Serial No", serial_nos[0], + self.assertEquals(frappe.db.get_value("Serial No", serial_nos[0], "delivery_document_no"), si.name) - + return si - + def test_serialized_cancel(self): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos si = self.test_serialized() @@ -834,20 +820,20 @@ class TestSalesInvoice(unittest.TestCase): self.assertEquals(frappe.db.get_value("Serial No", serial_nos[0], "status"), "Available") self.assertEquals(frappe.db.get_value("Serial No", serial_nos[0], "warehouse"), "_Test Warehouse - _TC") - self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0], + self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0], "delivery_document_no")) def test_serialize_status(self): from erpnext.stock.doctype.serial_no.serial_no import SerialNoStatusError, get_serial_nos from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item - + se = make_serialized_item() - serial_nos = get_serial_nos(se.get("entries")[0].serial_no) - + serial_nos = get_serial_nos(se.get("mtn_details")[0].serial_no) + sr = frappe.get_doc("Serial No", serial_nos[0]) sr.status = "Not Available" sr.save() - + si = frappe.copy_doc(test_records[0]) si.update_stock = 1 si.get("entries")[0].item_code = "_Test Serialized Item With Series" @@ -858,5 +844,4 @@ class TestSalesInvoice(unittest.TestCase): self.assertRaises(SerialNoStatusError, si.submit) test_dependencies = ["Journal Voucher", "POS Setting", "Contact", "Address"] - -test_records = frappe.get_test_records('Sales Invoice') \ No newline at end of file +test_records = frappe.get_test_records('Sales Invoice') diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 25bdfa97a5a..720a1d5c17e 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -8,7 +8,7 @@ from frappe.utils import cstr, flt from frappe import msgprint - + from erpnext.controllers.buying_controller import BuyingController class PurchaseOrder(BuyingController): tname = 'Purchase Order Item' @@ -24,15 +24,15 @@ class PurchaseOrder(BuyingController): 'source_field': 'qty', 'percent_join_field': 'prevdoc_docname', }] - + def validate(self): super(PurchaseOrder, self).validate() - + if not self.status: self.status = "Draft" from erpnext.utilities import validate_status - validate_status(self.status, ["Draft", "Submitted", "Stopped", + validate_status(self.status, ["Draft", "Submitted", "Stopped", "Cancelled"]) pc_obj = frappe.get_doc('Purchase Common') @@ -45,7 +45,7 @@ class PurchaseOrder(BuyingController): self.validate_with_previous_doc() self.validate_for_subcontracting() self.update_raw_materials_supplied("po_raw_material_details") - + def validate_with_previous_doc(self): super(PurchaseOrder, self).validate_with_previous_doc(self.tname, { "Supplier Quotation": { @@ -54,7 +54,7 @@ class PurchaseOrder(BuyingController): }, "Supplier Quotation Item": { "ref_dn_field": "supplier_quotation_item", - "compare_fields": [["rate", "="], ["project_name", "="], ["item_code", "="], + "compare_fields": [["rate", "="], ["project_name", "="], ["item_code", "="], ["uom", "="]], "is_child_table": True } @@ -65,11 +65,11 @@ class PurchaseOrder(BuyingController): if d.prevdoc_detail_docname and not d.schedule_date: d.schedule_date = frappe.db.get_value("Material Request Item", d.prevdoc_detail_docname, "schedule_date") - + def get_last_purchase_rate(self): frappe.get_doc('Purchase Common').get_last_purchase_rate(self) - # Check for Stopped status + # Check for Stopped status def check_for_stopped_status(self, pc_obj): check_list =[] for d in self.get('po_details'): @@ -77,7 +77,7 @@ class PurchaseOrder(BuyingController): check_list.append(d.prevdoc_docname) pc_obj.check_for_stopped_status( d.prevdoc_doctype, d.prevdoc_docname) - + def update_bin(self, is_submit, is_stopped = 0): from erpnext.stock.utils import update_bin pc_obj = frappe.get_doc('Purchase Common') @@ -87,29 +87,29 @@ class PurchaseOrder(BuyingController): # this happens when item is changed from non-stock to stock item if not d.warehouse: continue - + ind_qty, po_qty = 0, flt(d.qty) * flt(d.conversion_factor) if is_stopped: po_qty = flt(d.qty) > flt(d.received_qty) and \ - flt( flt(flt(d.qty) - flt(d.received_qty))*flt(d.conversion_factor)) or 0 - + flt( flt(flt(d.qty) - flt(d.received_qty))*flt(d.conversion_factor)) or 0 + # No updates in Material Request on Stop / Unstop if cstr(d.prevdoc_doctype) == 'Material Request' and not is_stopped: - # get qty and pending_qty of prevdoc + # get qty and pending_qty of prevdoc curr_ref_qty = pc_obj.get_qty(d.doctype, 'prevdoc_detail_docname', - d.prevdoc_detail_docname, 'Material Request Item', + d.prevdoc_detail_docname, 'Material Request Item', 'Material Request - Purchase Order', self.name) max_qty, qty, curr_qty = flt(curr_ref_qty.split('~~~')[1]), \ flt(curr_ref_qty.split('~~~')[0]), 0 - + if flt(qty) + flt(po_qty) > flt(max_qty): curr_qty = flt(max_qty) - flt(qty) - # special case as there is no restriction - # for Material Request - Purchase Order + # special case as there is no restriction + # for Material Request - Purchase Order curr_qty = curr_qty > 0 and curr_qty or 0 else: curr_qty = flt(po_qty) - + ind_qty = -flt(curr_qty) # Update ordered_qty and indented_qty in bin @@ -121,12 +121,12 @@ class PurchaseOrder(BuyingController): "posting_date": self.transaction_date } update_bin(args) - + def check_modified_date(self): - mod_db = frappe.db.sql("select modified from `tabPurchase Order` where name = %s", + mod_db = frappe.db.sql("select modified from `tabPurchase Order` where name = %s", self.name) date_diff = frappe.db.sql("select TIMEDIFF('%s', '%s')" % ( mod_db[0][0],cstr(self.modified))) - + if date_diff and date_diff[0][0]: msgprint(cstr(self.doctype) +" => "+ cstr(self.name) +" has been modified. Please Refresh. ") raise Exception @@ -144,28 +144,28 @@ class PurchaseOrder(BuyingController): def on_submit(self): purchase_controller = frappe.get_doc("Purchase Common") - + self.update_prevdoc_status() self.update_bin(is_submit = 1, is_stopped = 0) - - frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, + + frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.company, self.grand_total) - + purchase_controller.update_last_purchase_rate(self, is_submit = 1) - + frappe.db.set(self,'status','Submitted') - + def on_cancel(self): - pc_obj = frappe.get_doc(dt = 'Purchase Common') + pc_obj = frappe.get_doc(dt = 'Purchase Common') self.check_for_stopped_status(pc_obj) - + # Check if Purchase Receipt has been submitted against current Purchase Order pc_obj.check_docstatus(check = 'Next', doctype = 'Purchase Receipt', docname = self.name, detail_doctype = 'Purchase Receipt Item') # Check if Purchase Invoice has been submitted against current Purchase Order - submitted = frappe.db.sql("""select t1.name - from `tabPurchase Invoice` t1,`tabPurchase Invoice Item` t2 - where t1.name = t2.parent and t2.purchase_order = %s and t1.docstatus = 1""", + submitted = frappe.db.sql("""select t1.name + from `tabPurchase Invoice` t1,`tabPurchase Invoice Item` t2 + where t1.name = t2.parent and t2.purchase_order = %s and t1.docstatus = 1""", self.name) if submitted: msgprint("Purchase Invoice : " + cstr(submitted[0][0]) + " has already been submitted !") @@ -175,14 +175,14 @@ class PurchaseOrder(BuyingController): self.update_prevdoc_status() self.update_bin( is_submit = 0, is_stopped = 0) pc_obj.update_last_purchase_rate(self, is_submit = 0) - + def on_update(self): pass - + @frappe.whitelist() def make_purchase_receipt(source_name, target_doc=None): from frappe.model.mapper import get_mapped_doc - + def set_missing_values(source, target): doc = frappe.get_doc(target) doc.run_method("set_missing_values") @@ -193,35 +193,35 @@ def make_purchase_receipt(source_name, target_doc=None): target.amount = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.rate) target.base_amount = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.base_rate) - doclist = get_mapped_doc("Purchase Order", source_name, { + doc = get_mapped_doc("Purchase Order", source_name, { "Purchase Order": { - "doctype": "Purchase Receipt", + "doctype": "Purchase Receipt", "validation": { "docstatus": ["=", 1], } - }, + }, "Purchase Order Item": { - "doctype": "Purchase Receipt Item", + "doctype": "Purchase Receipt Item", "field_map": { - "name": "prevdoc_detail_docname", - "parent": "prevdoc_docname", - "parenttype": "prevdoc_doctype", + "name": "prevdoc_detail_docname", + "parent": "prevdoc_docname", + "parenttype": "prevdoc_doctype", }, "postprocess": update_item, "condition": lambda doc: doc.received_qty < doc.qty - }, + }, "Purchase Taxes and Charges": { - "doctype": "Purchase Taxes and Charges", + "doctype": "Purchase Taxes and Charges", "add_if_empty": True } }, target_doc, set_missing_values) - return doclist.as_dict() - + return doc + @frappe.whitelist() def make_purchase_invoice(source_name, target_doc=None): from frappe.model.mapper import get_mapped_doc - + def set_missing_values(source, target): doc = frappe.get_doc(target) doc.run_method("set_missing_values") @@ -232,26 +232,26 @@ def make_purchase_invoice(source_name, target_doc=None): if flt(obj.base_rate): target.qty = target.base_amount / flt(obj.base_rate) - doclist = get_mapped_doc("Purchase Order", source_name, { + doc = get_mapped_doc("Purchase Order", source_name, { "Purchase Order": { - "doctype": "Purchase Invoice", + "doctype": "Purchase Invoice", "validation": { "docstatus": ["=", 1], } - }, + }, "Purchase Order Item": { - "doctype": "Purchase Invoice Item", + "doctype": "Purchase Invoice Item", "field_map": { - "name": "po_detail", - "parent": "purchase_order", + "name": "po_detail", + "parent": "purchase_order", }, "postprocess": update_item, - "condition": lambda doc: doc.base_amount==0 or doc.billed_amt < doc.amount - }, + "condition": lambda doc: doc.base_amount==0 or doc.billed_amt < doc.amount + }, "Purchase Taxes and Charges": { - "doctype": "Purchase Taxes and Charges", + "doctype": "Purchase Taxes and Charges", "add_if_empty": True } }, target_doc, set_missing_values) - return doclist.as_dict() \ No newline at end of file + return doc diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index f8b03f040b3..d1d183a7a14 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -8,90 +8,87 @@ import frappe.defaults from frappe.utils import flt class TestPurchaseOrder(unittest.TestCase): - def test_make_purchase_receipt(self): + def test_make_purchase_receipt(self): from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt po = frappe.copy_doc(test_records[0]).insert() - self.assertRaises(frappe.ValidationError, make_purchase_receipt, + self.assertRaises(frappe.ValidationError, make_purchase_receipt, po.name) po = frappe.get_doc("Purchase Order", po.name) po.submit() - + pr = make_purchase_receipt(po.name) - pr[0]["supplier_warehouse"] = "_Test Warehouse 1 - _TC" - pr[0]["posting_date"] = "2013-05-12" - self.assertEquals(pr[0]["doctype"], "Purchase Receipt") - self.assertEquals(len(pr), len(test_records[0])) - - pr[0]["naming_series"] = "_T-Purchase Receipt-" - pr_doc = frappe.get_doc(pr) - pr_doc.insert() - + pr.supplier_warehouse = "_Test Warehouse 1 - _TC" + pr.posting_date = "2013-05-12" + self.assertEquals(pr.doctype, "Purchase Receipt") + self.assertEquals(len(pr.get("purchase_receipt_details")), len(test_records[0]["po_details"])) + + pr.naming_series = "_T-Purchase Receipt-" + frappe.get_doc(pr).insert() + def test_ordered_qty(self): frappe.db.sql("delete from tabBin") - + from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt po = frappe.copy_doc(test_records[0]).insert() - self.assertRaises(frappe.ValidationError, make_purchase_receipt, + self.assertRaises(frappe.ValidationError, make_purchase_receipt, po.name) po = frappe.get_doc("Purchase Order", po.name) po.is_subcontracted = "No" po.get("po_details")[0].item_code = "_Test Item" po.submit() - - self.assertEquals(frappe.db.get_value("Bin", {"item_code": "_Test Item", + + self.assertEquals(frappe.db.get_value("Bin", {"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"}, "ordered_qty"), 10) - + pr = make_purchase_receipt(po.name) - - self.assertEquals(pr[0]["doctype"], "Purchase Receipt") - self.assertEquals(len(pr), len(test_records[0])) - pr[0]["posting_date"] = "2013-05-12" - pr[0]["naming_series"] = "_T-Purchase Receipt-" - pr[1]["qty"] = 4.0 - pr_doc = frappe.get_doc(pr) - pr_doc.insert() - pr_doc.submit() - - self.assertEquals(flt(frappe.db.get_value("Bin", {"item_code": "_Test Item", + + self.assertEquals(pr.doctype, "Purchase Receipt") + self.assertEquals(len(pr.get("purchase_receipt_details", [])), len(test_records[0]["po_details"])) + pr.posting_date = "2013-05-12" + pr.naming_series = "_T-Purchase Receipt-" + pr.purchase_receipt_details[0].qty = 4.0 + pr.insert() + pr.submit() + + self.assertEquals(flt(frappe.db.get_value("Bin", {"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"}, "ordered_qty")), 6.0) - + frappe.db.set_value('Item', '_Test Item', 'tolerance', 50) - + pr1 = make_purchase_receipt(po.name) - pr1[0]["naming_series"] = "_T-Purchase Receipt-" - pr1[0]["posting_date"] = "2013-05-12" - pr1[1]["qty"] = 8 - pr1_doc = frappe.get_doc(pr1) - pr1_doc.insert() - pr1_doc.submit() - - self.assertEquals(flt(frappe.db.get_value("Bin", {"item_code": "_Test Item", + pr1.naming_series = "_T-Purchase Receipt-" + pr1.posting_date = "2013-05-12" + pr1.get("purchase_receipt_details")[0].qty = 8 + pr1.insert() + pr1.submit() + + self.assertEquals(flt(frappe.db.get_value("Bin", {"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"}, "ordered_qty")), 0.0) - + def test_make_purchase_invoice(self): from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice po = frappe.copy_doc(test_records[0]).insert() - self.assertRaises(frappe.ValidationError, make_purchase_invoice, + self.assertRaises(frappe.ValidationError, make_purchase_invoice, po.name) po = frappe.get_doc("Purchase Order", po.name) po.submit() pi = make_purchase_invoice(po.name) - - self.assertEquals(pi[0]["doctype"], "Purchase Invoice") - self.assertEquals(len(pi), len(test_records[0])) - pi[0]["posting_date"] = "2013-05-12" - pi[0]["bill_no"] = "NA" + + self.assertEquals(pi.doctype, "Purchase Invoice") + self.assertEquals(len(pi.get("entries", [])), len(test_records[0]["po_details"])) + pi.posting_date = "2013-05-12" + pi.bill_no = "NA" frappe.get_doc(pi).insert() - + def test_subcontracting(self): po = frappe.copy_doc(test_records[0]) po.insert() @@ -113,4 +110,4 @@ class TestPurchaseOrder(unittest.TestCase): test_dependencies = ["BOM"] -test_records = frappe.get_test_records('Purchase Order') \ No newline at end of file +test_records = frappe.get_test_records('Purchase Order') diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py index e937735111b..fa484887661 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py @@ -88,4 +88,4 @@ def make_purchase_order(source_name, target_doc=None): }, }, target_doc, set_missing_values) - return doclist.as_dict() \ No newline at end of file + return doclist \ No newline at end of file diff --git a/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py index 708b5e7f7a0..3f22fd50a48 100644 --- a/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py +++ b/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py @@ -13,22 +13,22 @@ class TestPurchaseOrder(unittest.TestCase): sq = frappe.copy_doc(test_records[0]).insert() - self.assertRaises(frappe.ValidationError, make_purchase_order, + self.assertRaises(frappe.ValidationError, make_purchase_order, sq.name) sq = frappe.get_doc("Supplier Quotation", sq.name) sq.submit() po = make_purchase_order(sq.name) - - self.assertEquals(po[0]["doctype"], "Purchase Order") - self.assertEquals(len(po), len(sq)) - - po[0]["naming_series"] = "_T-Purchase Order-" - for doc in po: + self.assertEquals(po.doctype, "Purchase Order") + self.assertEquals(len(po.get("po_details")), len(sq.get("quotation_items"))) + + po.naming_series = "_T-Purchase Order-" + + for doc in po.get("po_details"): if doc.get("item_code"): - doc["schedule_date"] = "2013-04-12" + doc.set("schedule_date", "2013-04-12") - frappe.get_doc(po).insert() - -test_records = frappe.get_test_records('Supplier Quotation') \ No newline at end of file + po.insert() + +test_records = frappe.get_test_records('Supplier Quotation') diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index ffe6a6a43b1..ec6fe1fa413 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -18,16 +18,16 @@ class AccountsController(TransactionBase): self.calculate_taxes_and_totals() self.validate_value("grand_total", ">=", 0) self.set_total_in_words() - + self.validate_for_freezed_account() - + def set_missing_values(self, for_validate=False): for fieldname in ["posting_date", "transaction_date"]: if not self.get(fieldname) and self.meta.get_field(fieldname): self.set(fieldname, today()) if not self.fiscal_year: self.fiscal_year = get_fiscal_year(self.get(fieldname))[0] - + def validate_date_with_fiscal_year(self): if self.meta.get_field("fiscal_year") : date_field = "" @@ -35,40 +35,40 @@ class AccountsController(TransactionBase): date_field = "posting_date" elif self.meta.get_field("transaction_date"): date_field = "transaction_date" - + if date_field and self.get(date_field): - validate_fiscal_year(self.get(date_field), self.fiscal_year, + validate_fiscal_year(self.get(date_field), self.fiscal_year, label=self.meta.get_label(date_field)) - + def validate_for_freezed_account(self): for fieldname in ["customer", "supplier"]: if self.meta.get_field(fieldname) and self.get(fieldname): - accounts = frappe.db.get_values("Account", - {"master_type": fieldname.title(), "master_name": self.get(fieldname), + accounts = frappe.db.get_values("Account", + {"master_type": fieldname.title(), "master_name": self.get(fieldname), "company": self.company}, "name") if accounts: from erpnext.accounts.doctype.gl_entry.gl_entry import validate_frozen_account - for account in accounts: + for account in accounts: validate_frozen_account(account[0]) - + def set_price_list_currency(self, buying_or_selling): if self.meta.get_field("currency"): company_currency = get_company_currency(self.company) - + # price list part fieldname = "selling_price_list" if buying_or_selling.lower() == "selling" \ else "buying_price_list" if self.meta.get_field(fieldname) and self.get(fieldname): self.price_list_currency = frappe.db.get_value("Price List", self.get(fieldname), "currency") - + if self.price_list_currency == company_currency: self.plc_conversion_rate = 1.0 elif not self.plc_conversion_rate: self.plc_conversion_rate = self.get_exchange_rate( self.price_list_currency, company_currency) - + # currency if not self.currency: self.currency = self.price_list_currency @@ -96,44 +96,44 @@ class AccountsController(TransactionBase): if item.meta.get_field(fieldname) and \ item.get(fieldname) is None and value is not None: item.set(fieldname, value) - + def set_taxes(self, tax_parentfield, tax_master_field): if not self.meta.get_field(tax_parentfield): return - + tax_master_doctype = self.meta.get_field(tax_master_field).options - + if not self.get(tax_parentfield): if not self.get(tax_master_field): # get the default tax master self.set(tax_master_field, frappe.db.get_value(tax_master_doctype, {"is_default": 1})) - + self.append_taxes_from_master(tax_parentfield, tax_master_field, tax_master_doctype) - + def append_taxes_from_master(self, tax_parentfield, tax_master_field, tax_master_doctype=None): if self.get(tax_master_field): if not tax_master_doctype: tax_master_doctype = self.meta.get_field(tax_master_field).options - + tax_doctype = self.meta.get_field(tax_parentfield).options - + from frappe.model import default_fields tax_master = frappe.get_doc(tax_master_doctype, self.get(tax_master_field)) - + for i, tax in enumerate(tax_master.get(tax_parentfield)): for fieldname in default_fields: tax.set(fieldname, None) - + self.append(tax_parentfield, tax) def get_other_charges(self): self.set("other_charges", []) self.set_taxes("other_charges", "taxes_and_charges") - + def calculate_taxes_and_totals(self): self.discount_amount_applied = False self._calculate_taxes_and_totals() - + if self.meta.get_field("discount_amount"): self.apply_discount_amount() @@ -151,23 +151,23 @@ class AccountsController(TransactionBase): self.conversion_rate = flt(self.conversion_rate) self.item_doclist = self.get(self.fname) self.tax_doclist = self.get(self.other_fname) - + self.calculate_item_values() self.initialize_taxes() - + if hasattr(self, "determine_exclusive_rate"): self.determine_exclusive_rate() - + self.calculate_net_total() self.calculate_taxes() self.calculate_totals() self._cleanup() - + def initialize_taxes(self): for tax in self.tax_doclist: tax.item_wise_tax_detail = {} - tax_fields = ["total", "tax_amount_after_discount_amount", - "tax_amount_for_current_item", "grand_total_for_current_item", + tax_fields = ["total", "tax_amount_after_discount_amount", + "tax_amount_for_current_item", "grand_total_for_current_item", "tax_fraction_for_current_item", "grand_total_fraction_for_current_item"] if not self.discount_amount_applied: @@ -179,7 +179,7 @@ class AccountsController(TransactionBase): self.validate_on_previous_row(tax) self.validate_inclusive_tax(tax) self.round_floats_in(tax) - + def validate_on_previous_row(self, tax): """ validate if a valid row id is mentioned in case of @@ -194,7 +194,7 @@ class AccountsController(TransactionBase): "row_id_label": self.meta.get_label("row_id", parentfield=self.other_fname) }) - + def validate_inclusive_tax(self, tax): def _on_previous_row_error(row_range): throw((_("Row") + " # %(idx)s [%(doctype)s]: " + @@ -202,24 +202,21 @@ class AccountsController(TransactionBase): " [" + _("Row") + " # %(row_range)s] " + _("also be included in Item's rate")) % { "idx": tax.idx, "doctype": tax.doctype, - "inclusive_label": self.meta.get_label("included_in_print_rate", - parentfield=self.other_fname), - "charge_type_label": self.meta.get_label("charge_type", - parentfield=self.other_fname), + "inclusive_label": frappe.get_meta(tax.doctype).get_label("included_in_print_rate"), + "charge_type_label": frappe.get_meta(tax.doctype).get_label("charge_type"), "charge_type": tax.charge_type, "row_range": row_range }) - + if cint(getattr(tax, "included_in_print_rate", None)): if tax.charge_type == "Actual": # inclusive tax cannot be of type Actual - throw((_("Row") - + " # %(idx)s [%(doctype)s]: %(charge_type_label)s = \"%(charge_type)s\" " + throw((_("Row") + + " # %(idx)s [%(doctype)s]: %(charge_type_label)s = \"%(charge_type)s\" " + "cannot be included in Item's rate") % { "idx": tax.idx, "doctype": tax.doctype, - "charge_type_label": self.meta.get_label("charge_type", - parentfield=self.other_fname), + "charge_type_label": frappe.get_meta(tax.doctype).get_label("charge_type"), "charge_type": tax.charge_type, }) elif tax.charge_type == "On Previous Row Amount" and \ @@ -230,10 +227,10 @@ class AccountsController(TransactionBase): not all([cint(t.included_in_print_rate) for t in self.tax_doclist[:cint(tax.row_id) - 1]]): # all rows about the reffered tax should be inclusive _on_previous_row_error("1 - %d" % (tax.row_id,)) - + def calculate_taxes(self): # maintain actual tax rate based on idx - actual_tax_dict = dict([[tax.idx, tax.rate] for tax in self.tax_doclist + actual_tax_dict = dict([[tax.idx, tax.rate] for tax in self.tax_doclist if tax.charge_type == "Actual"]) for n, item in enumerate(self.item_doclist): @@ -258,26 +255,26 @@ class AccountsController(TransactionBase): tax.tax_amount += current_tax_amount tax.tax_amount_after_discount_amount += current_tax_amount - + if getattr(tax, "category", None): # if just for valuation, do not add the tax amount in total # hence, setting it as 0 for further steps current_tax_amount = 0.0 if (tax.category == "Valuation") \ else current_tax_amount - + current_tax_amount *= -1.0 if (tax.add_deduct_tax == "Deduct") else 1.0 - + # Calculate tax.total viz. grand total till that step - # note: grand_total_for_current_item contains the contribution of + # note: grand_total_for_current_item contains the contribution of # item's amount, previously applied tax and the current tax on that item if i==0: tax.grand_total_for_current_item = flt(item.base_amount + current_tax_amount, self.precision("total", tax)) else: tax.grand_total_for_current_item = \ - flt(self.tax_doclist[i-1].grand_total_for_current_item + + flt(self.tax_doclist[i-1].grand_total_for_current_item + current_tax_amount, self.precision("total", tax)) - + # in tax.total, accumulate grand total of each item tax.total += tax.grand_total_for_current_item @@ -292,12 +289,12 @@ class AccountsController(TransactionBase): def round_off_totals(self, tax): tax.total = flt(tax.total, self.precision("total", tax)) tax.tax_amount = flt(tax.tax_amount, self.precision("tax_amount", tax)) - tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount, + tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount, self.precision("tax_amount", tax)) def adjust_discount_amount_loss(self, tax): discount_amount_loss = self.grand_total - flt(self.discount_amount) - tax.total - tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount + + tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount + discount_amount_loss, self.precision("tax_amount", tax)) tax.total = flt(tax.total + discount_amount_loss, self.precision("total", tax)) @@ -331,40 +328,40 @@ class AccountsController(TransactionBase): tax.item_wise_tax_detail[key] = [tax_rate, current_tax_amount] return current_tax_amount - + def _load_item_tax_rate(self, item_tax_rate): return json.loads(item_tax_rate) if item_tax_rate else {} - + def _get_tax_rate(self, tax, item_tax_map): if item_tax_map.has_key(tax.account_head): return flt(item_tax_map.get(tax.account_head), self.precision("rate", tax)) else: return tax.rate - + def _cleanup(self): for tax in self.tax_doclist: tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail) - + def _set_in_company_currency(self, item, print_field, base_field): """set values in base currency""" - value_in_company_currency = flt(self.conversion_rate * + value_in_company_currency = flt(self.conversion_rate * flt(item.get(print_field), self.precision(print_field, item)), self.precision(base_field, item)) item.set(base_field, value_in_company_currency) - + def calculate_total_advance(self, parenttype, advance_parentfield): if self.doctype == parenttype and self.docstatus < 2: - sum_of_allocated_amount = sum([flt(adv.allocated_amount, self.precision("allocated_amount", adv)) + sum_of_allocated_amount = sum([flt(adv.allocated_amount, self.precision("allocated_amount", adv)) for adv in self.get(advance_parentfield)]) self.total_advance = flt(sum_of_allocated_amount, self.precision("total_advance")) - + self.calculate_outstanding_amount() def get_gl_dict(self, args): """this method populates the common properties of a gl entry record""" gl_dict = frappe._dict({ - 'company': self.company, + 'company': self.company, 'posting_date': self.posting_date, 'voucher_type': self.doctype, 'voucher_no': self.name, @@ -377,24 +374,24 @@ class AccountsController(TransactionBase): }) gl_dict.update(args) return gl_dict - + def clear_unallocated_advances(self, childtype, parentfield): self.set(parentfield, self.get(parentfield, {"allocated_amount": ["not in", [0, None, ""]]})) - frappe.db.sql("""delete from `tab%s` where parentfield=%s and parent = %s + frappe.db.sql("""delete from `tab%s` where parentfield=%s and parent = %s and ifnull(allocated_amount, 0) = 0""" % (childtype, '%s', '%s'), (parentfield, self.name)) - + def get_advances(self, account_head, child_doctype, parentfield, dr_or_cr): - res = frappe.db.sql("""select t1.name as jv_no, t1.remark, + res = frappe.db.sql("""select t1.name as jv_no, t1.remark, t2.%s as amount, t2.name as jv_detail_no - from `tabJournal Voucher` t1, `tabJournal Voucher Detail` t2 - where t1.name = t2.parent and t2.account = %s and t2.is_advance = 'Yes' + from `tabJournal Voucher` t1, `tabJournal Voucher Detail` t2 + where t1.name = t2.parent and t2.account = %s and t2.is_advance = 'Yes' and (t2.against_voucher is null or t2.against_voucher = '') - and (t2.against_invoice is null or t2.against_invoice = '') - and (t2.against_jv is null or t2.against_jv = '') - and t1.docstatus = 1 order by t1.posting_date""" % + and (t2.against_invoice is null or t2.against_invoice = '') + and (t2.against_jv is null or t2.against_jv = '') + and t1.docstatus = 1 order by t1.posting_date""" % (dr_or_cr, '%s'), account_head, as_dict=1) - + self.set(parentfield, []) for d in res: self.append(parentfield, { @@ -405,74 +402,74 @@ class AccountsController(TransactionBase): "advance_amount": flt(d.amount), "allocate_amount": 0 }) - + def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield): from erpnext.controllers.status_updater import get_tolerance_for item_tolerance = {} global_tolerance = None - + for item in self.get("entries"): if item.get(item_ref_dn): - ref_amt = flt(frappe.db.get_value(ref_dt + " Item", + ref_amt = flt(frappe.db.get_value(ref_dt + " Item", item.get(item_ref_dn), based_on), self.precision(based_on, item)) if not ref_amt: - frappe.msgprint(_("As amount for item") + ": " + item.item_code + _(" in ") + + frappe.msgprint(_("As amount for item") + ": " + item.item_code + _(" in ") + ref_dt + _(" is zero, system will not check for over-billed")) else: - already_billed = frappe.db.sql("""select sum(%s) from `tab%s` - where %s=%s and docstatus=1 and parent != %s""" % - (based_on, self.tname, item_ref_dn, '%s', '%s'), + already_billed = frappe.db.sql("""select sum(%s) from `tab%s` + where %s=%s and docstatus=1 and parent != %s""" % + (based_on, self.tname, item_ref_dn, '%s', '%s'), (item.get(item_ref_dn), self.name))[0][0] - - total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)), + + total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)), self.precision(based_on, item)) - - tolerance, item_tolerance, global_tolerance = get_tolerance_for(item.item_code, + + tolerance, item_tolerance, global_tolerance = get_tolerance_for(item.item_code, item_tolerance, global_tolerance) - + max_allowed_amt = flt(ref_amt * (100 + tolerance) / 100) - + if total_billed_amt - max_allowed_amt > 0.01: reduce_by = total_billed_amt - max_allowed_amt - - frappe.throw(_("Row #") + cstr(item.idx) + ": " + - _(" Max amount allowed for Item ") + cstr(item.item_code) + - _(" against ") + ref_dt + " " + - cstr(item.get(ref_dt.lower().replace(" ", "_"))) + _(" is ") + - cstr(max_allowed_amt) + ". \n" + + + frappe.throw(_("Row #") + cstr(item.idx) + ": " + + _(" Max amount allowed for Item ") + cstr(item.item_code) + + _(" against ") + ref_dt + " " + + cstr(item.get(ref_dt.lower().replace(" ", "_"))) + _(" is ") + + cstr(max_allowed_amt) + ". \n" + _("""If you want to increase your overflow tolerance, please increase \ - tolerance % in Global Defaults or Item master. - Or, you must reduce the amount by """) + cstr(reduce_by) + "\n" + + tolerance % in Global Defaults or Item master. + Or, you must reduce the amount by """) + cstr(reduce_by) + "\n" + _("""Also, please check if the order item has already been billed \ in the Sales Order""")) - + def get_company_default(self, fieldname): from erpnext.accounts.utils import get_company_default return get_company_default(self.company, fieldname) - + def get_stock_items(self): stock_items = [] - item_codes = list(set(item.item_code for item in + item_codes = list(set(item.item_code for item in self.get(self.fname))) if item_codes: stock_items = [r[0] for r in frappe.db.sql("""select name from `tabItem` where name in (%s) and is_stock_item='Yes'""" % \ (", ".join((["%s"]*len(item_codes))),), item_codes)] - + return stock_items - + @property def company_abbr(self): if not hasattr(self, "_abbr"): self._abbr = frappe.db.get_value("Company", self.company, "abbr") - + return self._abbr def check_credit_limit(self, account): total_outstanding = frappe.db.sql(""" - select sum(ifnull(debit, 0)) - sum(ifnull(credit, 0)) + select sum(ifnull(debit, 0)) - sum(ifnull(credit, 0)) from `tabGL Entry` where account = %s""", account) - + total_outstanding = total_outstanding[0][0] if total_outstanding else 0 if total_outstanding: frappe.get_doc('Account', account).check_credit_limit(total_outstanding) @@ -480,4 +477,4 @@ class AccountsController(TransactionBase): @frappe.whitelist() def get_tax_rate(account_head): - return frappe.db.get_value("Account", account_head, "tax_rate") \ No newline at end of file + return frappe.db.get_value("Account", account_head, "tax_rate") diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 027bfd2ad89..3f78fe69cbf 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -13,52 +13,52 @@ class SellingController(StockController): def onload_post_render(self): # contact, address, item details and pos details (if applicable) self.set_missing_values() - + def validate(self): super(SellingController, self).validate() self.validate_max_discount() check_active_sales_items(self) - + def get_sender(self, comm): return frappe.db.get_value('Sales Email Settings', None, 'email_id') - + def set_missing_values(self, for_validate=False): super(SellingController, self).set_missing_values(for_validate) - + # set contact and address details for customer, if they are not mentioned self.set_missing_lead_customer_details() self.set_price_list_and_item_details() if self.get("__islocal"): self.set_taxes("other_charges", "taxes_and_charges") - + def set_missing_lead_customer_details(self): if getattr(self, "customer", None): from erpnext.accounts.party import _get_party_details self.update_if_missing(_get_party_details(self.customer, ignore_permissions=getattr(self, "ignore_permissions", None))) - + elif getattr(self, "lead", None): from erpnext.selling.doctype.lead.lead import get_lead_details self.update_if_missing(get_lead_details(self.lead)) - + def set_price_list_and_item_details(self): self.set_price_list_currency("Selling") self.set_missing_item_details() - + def apply_shipping_rule(self): if self.shipping_rule: shipping_rule = frappe.get_doc("Shipping Rule", self.shipping_rule) value = self.net_total - + # TODO # shipping rule calculation based on item's net weight - + shipping_amount = 0.0 for condition in shipping_rule.get("shipping_rule_conditions"): if not condition.to_value or (flt(condition.from_value) <= value <= flt(condition.to_value)): shipping_amount = condition.shipping_amount break - + self.append("other_charges", { "doctype": "Sales Taxes and Charges", "charge_type": "Actual", @@ -67,86 +67,86 @@ class SellingController(StockController): "description": shipping_rule.label, "rate": shipping_amount }) - + def set_total_in_words(self): from frappe.utils import money_in_words company_currency = get_company_currency(self.company) - - disable_rounded_total = cint(frappe.db.get_value("Global Defaults", None, + + disable_rounded_total = cint(frappe.db.get_value("Global Defaults", None, "disable_rounded_total")) - + if self.meta.get_field("in_words"): - self.in_words = money_in_words(disable_rounded_total and + self.in_words = money_in_words(disable_rounded_total and self.grand_total or self.rounded_total, company_currency) if self.meta.get_field("in_words_export"): - self.in_words_export = money_in_words(disable_rounded_total and + self.in_words_export = money_in_words(disable_rounded_total and self.grand_total_export or self.rounded_total_export, self.currency) - + def calculate_taxes_and_totals(self): self.other_fname = "other_charges" - + super(SellingController, self).calculate_taxes_and_totals() - + self.calculate_total_advance("Sales Invoice", "advance_adjustment_details") self.calculate_commission() self.calculate_contribution() - + def determine_exclusive_rate(self): if not any((cint(tax.included_in_print_rate) for tax in self.tax_doclist)): # no inclusive tax return - + for item in self.item_doclist: item_tax_map = self._load_item_tax_rate(item.item_tax_rate) cumulated_tax_fraction = 0 for i, tax in enumerate(self.tax_doclist): tax.tax_fraction_for_current_item = self.get_current_tax_fraction(tax, item_tax_map) - + if i==0: tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item else: tax.grand_total_fraction_for_current_item = \ self.tax_doclist[i-1].grand_total_fraction_for_current_item \ + tax.tax_fraction_for_current_item - + cumulated_tax_fraction += tax.tax_fraction_for_current_item - + if cumulated_tax_fraction and not self.discount_amount_applied: item.base_amount = flt((item.amount * self.conversion_rate) / (1 + cumulated_tax_fraction), self.precision("base_amount", item)) - + item.base_rate = flt(item.base_amount / item.qty, self.precision("base_rate", item)) - + if item.discount_percentage == 100: item.base_price_list_rate = item.base_rate item.base_rate = 0.0 else: item.base_price_list_rate = flt(item.base_rate / (1 - (item.discount_percentage / 100.0)), self.precision("base_price_list_rate", item)) - + def get_current_tax_fraction(self, tax, item_tax_map): """ Get tax fraction for calculating tax exclusive amount from tax inclusive amount """ current_tax_fraction = 0 - + if cint(tax.included_in_print_rate): tax_rate = self._get_tax_rate(tax, item_tax_map) - + if tax.charge_type == "On Net Total": current_tax_fraction = tax_rate / 100.0 - + elif tax.charge_type == "On Previous Row Amount": current_tax_fraction = (tax_rate / 100.0) * \ self.tax_doclist[cint(tax.row_id) - 1].tax_fraction_for_current_item - + elif tax.charge_type == "On Previous Row Total": current_tax_fraction = (tax_rate / 100.0) * \ self.tax_doclist[cint(tax.row_id) - 1].grand_total_fraction_for_current_item - + return current_tax_fraction - + def calculate_item_values(self): if not self.discount_amount_applied: for item in self.item_doclist: @@ -171,22 +171,22 @@ class SellingController(StockController): for item in self.item_doclist: self.net_total += item.base_amount self.net_total_export += item.amount - + self.round_floats_in(self, ["net_total", "net_total_export"]) - + def calculate_totals(self): self.grand_total = flt(self.tax_doclist and \ self.tax_doclist[-1].total or self.net_total, self.precision("grand_total")) - self.grand_total_export = flt(self.grand_total / self.conversion_rate, + self.grand_total_export = flt(self.grand_total / self.conversion_rate, self.precision("grand_total_export")) - + self.other_charges_total = flt(self.grand_total - self.net_total, self.precision("other_charges_total")) - self.other_charges_total_export = flt(self.grand_total_export - - self.net_total_export + flt(self.discount_amount), + self.other_charges_total_export = flt(self.grand_total_export - + self.net_total_export + flt(self.discount_amount), self.precision("other_charges_total_export")) - + self.rounded_total = _round(self.grand_total) self.rounded_total_export = _round(self.grand_total_export) @@ -214,12 +214,12 @@ class SellingController(StockController): flt(tax.rate) / 100 actual_taxes_dict.setdefault(tax.idx, actual_tax_amount) - grand_total_for_discount_amount = flt(self.grand_total - sum(actual_taxes_dict.values()), + grand_total_for_discount_amount = flt(self.grand_total - sum(actual_taxes_dict.values()), self.precision("grand_total")) return grand_total_for_discount_amount def calculate_outstanding_amount(self): - # NOTE: + # NOTE: # write_off_amount is only for POS Invoice # total_advance is only for non POS Invoice if self.doctype == "Sales Invoice" and self.docstatus == 0: @@ -228,14 +228,14 @@ class SellingController(StockController): total_amount_to_pay = self.grand_total - self.write_off_amount self.outstanding_amount = flt(total_amount_to_pay - self.total_advance \ - self.paid_amount, self.precision("outstanding_amount")) - + def calculate_commission(self): if self.meta.get_field("commission_rate"): self.round_floats_in(self, ["net_total", "commission_rate"]) if self.commission_rate > 100.0: - msgprint(_(self.meta.get_label("commission_rate")) + " " + + msgprint(_(self.meta.get_label("commission_rate")) + " " + _("cannot be greater than 100"), raise_exception=True) - + self.total_commission = flt(self.net_total * self.commission_rate / 100.0, self.precision("total_commission")) @@ -248,66 +248,66 @@ class SellingController(StockController): sales_person.allocated_amount = flt( self.net_total * sales_person.allocated_percentage / 100.0, self.precision("allocated_amount", sales_person)) - + total += sales_person.allocated_percentage - + if sales_team and total != 100.0: - msgprint(_("Total") + " " + - _(self.meta.get_label("allocated_percentage", parentfield="sales_team")) + + msgprint(_("Total") + " " + + _(self.meta.get_label("allocated_percentage", parentfield="sales_team")) + " " + _("should be 100%"), raise_exception=True) - + def validate_order_type(self): valid_types = ["Sales", "Maintenance", "Shopping Cart"] if not self.order_type: self.order_type = "Sales" elif self.order_type not in valid_types: - msgprint(_(self.meta.get_label("order_type")) + " " + + msgprint(_(self.meta.get_label("order_type")) + " " + _("must be one of") + ": " + comma_or(valid_types), raise_exception=True) - + def check_credit(self, grand_total): - customer_account = frappe.db.get_value("Account", {"company": self.company, + customer_account = frappe.db.get_value("Account", {"company": self.company, "master_name": self.customer}, "name") if customer_account: - total_outstanding = frappe.db.sql("""select - sum(ifnull(debit, 0)) - sum(ifnull(credit, 0)) + total_outstanding = frappe.db.sql("""select + sum(ifnull(debit, 0)) - sum(ifnull(credit, 0)) from `tabGL Entry` where account = %s""", customer_account) total_outstanding = total_outstanding[0][0] if total_outstanding else 0 - + outstanding_including_current = flt(total_outstanding) + flt(grand_total) - frappe.get_doc('Account', customer_account).run_method("check_credit_limit", + frappe.get_doc('Account', customer_account).run_method("check_credit_limit", outstanding_including_current) - + def validate_max_discount(self): for d in self.get(self.fname): discount = flt(frappe.db.get_value("Item", d.item_code, "max_discount")) - + if discount and flt(d.discount_percentage) > discount: - frappe.throw(_("You cannot give more than ") + cstr(discount) + "% " + + frappe.throw(_("You cannot give more than ") + cstr(discount) + "% " + _("discount on Item Code") + ": " + cstr(d.item_code)) - + def get_item_list(self): il = [] for d in self.get(self.fname): reserved_warehouse = "" reserved_qty_for_main_item = 0 - + if self.doctype == "Sales Order": - if (frappe.db.get_value("Item", d.item_code, "is_stock_item") == 'Yes' or + if (frappe.db.get_value("Item", d.item_code, "is_stock_item") == 'Yes' or self.has_sales_bom(d.item_code)) and not d.warehouse: - frappe.throw(_("Please enter Reserved Warehouse for item ") + + frappe.throw(_("Please enter Reserved Warehouse for item ") + d.item_code + _(" as it is stock Item or packing item")) reserved_warehouse = d.warehouse if flt(d.qty) > flt(d.delivered_qty): reserved_qty_for_main_item = flt(d.qty) - flt(d.delivered_qty) - - if self.doctype == "Delivery Note" and d.against_sales_order: + + elif self.doctype == "Delivery Note" and d.against_sales_order: # if SO qty is 10 and there is tolerance of 20%, then it will allow DN of 12. # But in this case reserved qty should only be reduced by 10 and not 12 - - already_delivered_qty = self.get_already_delivered_qty(self.name, + + already_delivered_qty = self.get_already_delivered_qty(self.name, d.against_sales_order, d.prevdoc_detail_docname) so_qty, reserved_warehouse = self.get_so_qty_and_warehouse(d.prevdoc_detail_docname) - + if already_delivered_qty + d.qty > so_qty: reserved_qty_for_main_item = -(so_qty - already_delivered_qty) else: @@ -341,15 +341,15 @@ class SellingController(StockController): 'name': d.name })) return il - + def has_sales_bom(self, item_code): - return frappe.db.sql("""select name from `tabSales BOM` + return frappe.db.sql("""select name from `tabSales BOM` where new_item_code=%s and docstatus != 2""", item_code) - + def get_already_delivered_qty(self, dn, so, so_detail): - qty = frappe.db.sql("""select sum(qty) from `tabDelivery Note Item` - where prevdoc_detail_docname = %s and docstatus = 1 - and against_sales_order = %s + qty = frappe.db.sql("""select sum(qty) from `tabDelivery Note Item` + where prevdoc_detail_docname = %s and docstatus = 1 + and against_sales_order = %s and parent != %s""", (so_detail, so, dn)) return qty and flt(qty[0][0]) or 0.0 @@ -359,24 +359,24 @@ class SellingController(StockController): so_qty = so_item and flt(so_item[0]["qty"]) or 0.0 so_warehouse = so_item and so_item[0]["warehouse"] or "" return so_qty, so_warehouse - + def check_stop_sales_order(self, ref_fieldname): for d in self.get(self.fname): if d.get(ref_fieldname): status = frappe.db.get_value("Sales Order", d.get(ref_fieldname), "status") if status == "Stopped": - frappe.throw(self.doctype + - _(" can not be created/modified against stopped Sales Order ") + + frappe.throw(self.doctype + + _(" can not be created/modified against stopped Sales Order ") + d.get(ref_fieldname)) - + def check_active_sales_items(obj): for d in obj.get(obj.fname): if d.item_code: - item = frappe.db.sql("""select docstatus, is_sales_item, - is_service_item, income_account from tabItem where name = %s""", + item = frappe.db.sql("""select docstatus, is_sales_item, + is_service_item, income_account from tabItem where name = %s""", d.item_code, as_dict=True)[0] if item.is_sales_item == 'No' and item.is_service_item == 'No': frappe.throw(_("Item is neither Sales nor Service Item") + ": " + d.item_code) if getattr(d, "income_account", None) and not item.income_account: - frappe.db.set_value("Item", d.item_code, "income_account", + frappe.db.set_value("Item", d.item_code, "income_account", d.income_account) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index aa96b7c3997..206c963046e 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -14,15 +14,15 @@ from frappe.model.document import Document class BOM(Document): def autoname(self): - last_name = frappe.db.sql("""select max(name) from `tabBOM` + last_name = frappe.db.sql("""select max(name) from `tabBOM` where name like "BOM/%s/%%" """ % cstr(self.item).replace('"', '\\"')) if last_name: idx = cint(cstr(last_name[0][0]).split('/')[-1].split('-')[0]) + 1 - + else: idx = 1 self.name = 'BOM/' + self.item + ('/%.3i' % idx) - + def validate(self): self.clear_operations() self.validate_main_item() @@ -34,12 +34,11 @@ class BOM(Document): self.validate_materials() self.set_bom_material_details() self.calculate_cost() - + def on_update(self): self.check_recursion() self.update_exploded_items() - self.db_update() - + def on_submit(self): self.manage_default_bom() @@ -50,48 +49,48 @@ class BOM(Document): # check if used in any other bom self.validate_bom_links() self.manage_default_bom() - + def on_update_after_submit(self): self.validate_bom_links() self.manage_default_bom() def get_item_det(self, item_code): - item = frappe.db.sql("""select name, is_asset_item, is_purchase_item, - docstatus, description, is_sub_contracted_item, stock_uom, default_bom, - last_purchase_rate, standard_rate, is_manufactured_item + item = frappe.db.sql("""select name, is_asset_item, is_purchase_item, + docstatus, description, is_sub_contracted_item, stock_uom, default_bom, + last_purchase_rate, standard_rate, is_manufactured_item from `tabItem` where name=%s""", item_code, as_dict = 1) return item - + def validate_rm_item(self, item): if item[0]['name'] == self.item: - msgprint("Item_code: %s in materials tab cannot be same as FG Item", + msgprint("Item_code: %s in materials tab cannot be same as FG Item", item[0]['name'], raise_exception=1) - + if not item or item[0]['docstatus'] == 2: msgprint("Item %s does not exist in system" % item[0]['item_code'], raise_exception = 1) - + def set_bom_material_details(self): for item in self.get("bom_materials"): - ret = self.get_bom_material_detail({"item_code": item.item_code, "bom_no": item.bom_no, + ret = self.get_bom_material_detail({"item_code": item.item_code, "bom_no": item.bom_no, "qty": item.qty}) for r in ret: if not item.get(r): item.set(r, ret[r]) - + def get_bom_material_detail(self, args=None): """ Get raw material details like uom, desc and rate""" if not args: args = frappe.form_dict.get('args') - + if isinstance(args, basestring): import json args = json.loads(args) - + item = self.get_item_det(args['item_code']) self.validate_rm_item(item) - + args['bom_no'] = args['bom_no'] or item and cstr(item[0]['default_bom']) or '' args.update(item[0]) @@ -117,27 +116,23 @@ class BOM(Document): elif self.rm_cost_as_per == "Price List": if not self.buying_price_list: frappe.throw(_("Please select Price List")) - rate = frappe.db.get_value("Item Price", {"price_list": self.buying_price_list, + rate = frappe.db.get_value("Item Price", {"price_list": self.buying_price_list, "item_code": arg["item_code"]}, "price_list_rate") or 0 elif self.rm_cost_as_per == 'Standard Rate': rate = arg['standard_rate'] return rate - + def update_cost(self): for d in self.get("bom_materials"): d.rate = self.get_bom_material_detail({ - 'item_code': d.item_code, + 'item_code': d.item_code, 'bom_no': d.bom_no, 'qty': d.qty })["rate"] - - if self.docstatus == 0: + + if self.docstatus in (0, 1): self.save() - elif self.docstatus == 1: - self.calculate_cost() - self.update_exploded_items() - self.update_after_submit() def get_bom_unitcost(self, bom_no): bom = frappe.db.sql("""select name, total_cost/quantity as unit_cost from `tabBOM` @@ -145,9 +140,9 @@ class BOM(Document): return bom and bom[0]['unit_cost'] or 0 def get_valuation_rate(self, args): - """ Get average valuation rate of relevant warehouses - as per valuation method (MAR/FIFO) - as on costing date + """ Get average valuation rate of relevant warehouses + as per valuation method (MAR/FIFO) + as on costing date """ from erpnext.stock.utils import get_incoming_rate dt = self.costing_date or nowdate() @@ -168,19 +163,19 @@ class BOM(Document): return rate and flt(sum(rate))/len(rate) or 0 def manage_default_bom(self): - """ Uncheck others if current one is selected as default, + """ Uncheck others if current one is selected as default, update default bom in item master """ if self.is_default and self.is_active: from frappe.model.utils import set_default set_default(self, "item") frappe.db.set_value("Item", self.item, "default_bom", self.name) - + else: if not self.is_active: frappe.db.set(self, "is_default", 0) - - frappe.db.sql("update `tabItem` set default_bom = null where name = %s and default_bom = %s", + + frappe.db.sql("update `tabItem` set default_bom = null where name = %s and default_bom = %s", (self.item, self.name)) def clear_operations(self): @@ -193,7 +188,7 @@ class BOM(Document): """ Validate main FG item""" item = self.get_item_det(self.item) if not item: - msgprint("Item %s does not exists in the system or expired." % + msgprint("Item %s does not exists in the system or expired." % self.item, raise_exception = 1) elif item[0]['is_manufactured_item'] != 'Yes' \ and item[0]['is_sub_contracted_item'] != 'Yes': @@ -209,7 +204,7 @@ class BOM(Document): self.op = [] for d in self.get('bom_operations'): if cstr(d.operation_no) in self.op: - msgprint("Operation no: %s is repeated in Operations Table" % + msgprint("Operation no: %s is repeated in Operations Table" % d.operation_no, raise_exception=1) else: # add operation in op list @@ -222,36 +217,36 @@ class BOM(Document): # check if operation no not in op table if self.with_operations and cstr(m.operation_no) not in self.op: msgprint("""Operation no: %s against item: %s at row no: %s \ - is not present at Operations table""" % + is not present at Operations table""" % (m.operation_no, m.item_code, m.idx), raise_exception = 1) - + item = self.get_item_det(m.item_code) if item[0]['is_manufactured_item'] == 'Yes': if not m.bom_no: - msgprint("Please enter BOM No aginst item: %s at row no: %s" % + msgprint("Please enter BOM No aginst item: %s at row no: %s" % (m.item_code, m.idx), raise_exception=1) else: self.validate_bom_no(m.item_code, m.bom_no, m.idx) elif m.bom_no: msgprint("""As Item %s is not a manufactured / sub-contracted item, \ - you can not enter BOM against it (Row No: %s).""" % + you can not enter BOM against it (Row No: %s).""" % (m.item_code, m.idx), raise_exception = 1) if flt(m.qty) <= 0: - msgprint("Please enter qty against raw material: %s at row no: %s" % + msgprint("Please enter qty against raw material: %s at row no: %s" % (m.item_code, m.idx), raise_exception = 1) self.check_if_item_repeated(m.item_code, m.operation_no, check_list) def validate_bom_no(self, item, bom_no, idx): """Validate BOM No of sub-contracted items""" - bom = frappe.db.sql("""select name from `tabBOM` where name = %s and item = %s - and is_active=1 and docstatus=1""", + bom = frappe.db.sql("""select name from `tabBOM` where name = %s and item = %s + and is_active=1 and docstatus=1""", (bom_no, item), as_dict =1) if not bom: msgprint("""Incorrect BOM No: %s against item: %s at row no: %s. - It may be inactive or not submitted or does not belong to this item.""" % + It may be inactive or not submitted or does not belong to this item.""" % (bom_no, item, idx), raise_exception = 1) def check_if_item_repeated(self, item, op, check_list): @@ -268,7 +263,7 @@ class BOM(Document): for d in check_list: bom_list, count = [self.name], 0 while (len(bom_list) > count ): - boms = frappe.db.sql(" select %s from `tabBOM Item` where %s = %s " % + boms = frappe.db.sql(" select %s from `tabBOM Item` where %s = %s " % (d[0], d[1], '%s'), cstr(bom_list[count])) count = count + 1 for b in boms: @@ -277,24 +272,24 @@ class BOM(Document): """ % (cstr(b[0]), cstr(d[2]), self.name), raise_exception = 1) if b[0]: bom_list.append(b[0]) - + def update_cost_and_exploded_items(self, bom_list=[]): bom_list = self.traverse_tree(bom_list) for bom in bom_list: bom_obj = frappe.get_doc("BOM", bom) bom_obj.on_update() - + return bom_list - + def traverse_tree(self, bom_list=[]): def _get_children(bom_no): - return [cstr(d[0]) for d in frappe.db.sql("""select bom_no from `tabBOM Item` + return [cstr(d[0]) for d in frappe.db.sql("""select bom_no from `tabBOM Item` where parent = %s and ifnull(bom_no, '') != ''""", bom_no)] - + count = 0 if self.name not in bom_list: bom_list.append(self.name) - + while(count < len(bom_list)): for child_bom in _get_children(bom_list[count]): if child_bom not in bom_list: @@ -302,7 +297,7 @@ class BOM(Document): count += 1 bom_list.reverse() return bom_list - + def calculate_cost(self): """Calculate bom totals""" self.calculate_op_cost() @@ -319,7 +314,7 @@ class BOM(Document): d.operating_cost = flt(d.hour_rate) * flt(d.time_in_mins) / 60.0 total_op_cost += flt(d.operating_cost) self.operating_cost = total_op_cost - + def calculate_rm_cost(self): """Fetch RM rate as per today's valuation rate and calculate totals""" total_rm_cost = 0 @@ -329,7 +324,7 @@ class BOM(Document): d.amount = flt(d.rate) * flt(d.qty) d.qty_consumed_per_unit = flt(d.qty) / flt(self.quantity) total_rm_cost += d.amount - + self.raw_material_cost = total_rm_cost def update_exploded_items(self): @@ -345,38 +340,38 @@ class BOM(Document): self.get_child_exploded_items(d.bom_no, d.qty) else: self.add_to_cur_exploded_items(frappe._dict({ - 'item_code' : d.item_code, - 'description' : d.description, - 'stock_uom' : d.stock_uom, + 'item_code' : d.item_code, + 'description' : d.description, + 'stock_uom' : d.stock_uom, 'qty' : flt(d.qty), 'rate' : flt(d.rate), })) - + def add_to_cur_exploded_items(self, args): if self.cur_exploded_items.get(args.item_code): self.cur_exploded_items[args.item_code]["qty"] += args.qty else: self.cur_exploded_items[args.item_code] = args - + def get_child_exploded_items(self, bom_no, qty): """ Add all items from Flat BOM of child BOM""" - - child_fb_items = frappe.db.sql("""select item_code, description, stock_uom, qty, rate, - qty_consumed_per_unit from `tabBOM Explosion Item` + + child_fb_items = frappe.db.sql("""select item_code, description, stock_uom, qty, rate, + qty_consumed_per_unit from `tabBOM Explosion Item` where parent = %s and docstatus = 1""", bom_no, as_dict = 1) - + for d in child_fb_items: self.add_to_cur_exploded_items(frappe._dict({ - 'item_code' : d['item_code'], - 'description' : d['description'], - 'stock_uom' : d['stock_uom'], + 'item_code' : d['item_code'], + 'description' : d['description'], + 'stock_uom' : d['stock_uom'], 'qty' : flt(d['qty_consumed_per_unit'])*qty, 'rate' : flt(d['rate']), })) def add_exploded_items(self): "Add items to Flat BOM table" - self.set('flat_bom_details', []) + frappe.db.sql("""delete from `tabBOM Explosion Item` where parent=%s""", self.name) for d in self.cur_exploded_items: ch = self.append('flat_bom_details', {}) for i in self.cur_exploded_items[d].keys(): @@ -384,7 +379,7 @@ class BOM(Document): ch.amount = flt(ch.qty) * flt(ch.rate) ch.qty_consumed_per_unit = flt(ch.qty) / flt(self.quantity) ch.docstatus = self.docstatus - ch.db_update() + ch.db_insert() def validate_bom_links(self): if not self.is_active: @@ -399,26 +394,27 @@ class BOM(Document): raise_exception=1) def get_bom_items_as_dict(bom, qty=1, fetch_exploded=1): + import json item_dict = {} - - query = """select + + query = """select bom_item.item_code, item.item_name, - ifnull(sum(bom_item.qty_consumed_per_unit),0) * %(qty)s as qty, - item.description, + ifnull(sum(bom_item.qty_consumed_per_unit),0) * %(qty)s as qty, + item.description, item.stock_uom, item.default_warehouse, item.expense_account as expense_account, item.buying_cost_center as cost_center - from - `tab%(table)s` bom_item, `tabItem` item - where - bom_item.docstatus < 2 + from + `tab%(table)s` bom_item, `tabItem` item + where + bom_item.docstatus < 2 and bom_item.parent = "%(bom)s" - and item.name = bom_item.item_code + and item.name = bom_item.item_code %(conditions)s group by item_code, stock_uom""" - + if fetch_exploded: items = frappe.db.sql(query % { "qty": qty, @@ -441,7 +437,7 @@ def get_bom_items_as_dict(bom, qty=1, fetch_exploded=1): item_dict[item.item_code]["qty"] += flt(item.qty) else: item_dict[item.item_code] = item - + return item_dict @frappe.whitelist() diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 074daa4eb95..28ee49acbc2 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -12,19 +12,19 @@ class TestBOM(unittest.TestCase): def test_get_items(self): from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict items_dict = get_bom_items_as_dict(bom="BOM/_Test FG Item 2/001", qty=1, fetch_exploded=0) - self.assertTrue(test_records[2][1]["item_code"] in items_dict) - self.assertTrue(test_records[2][2]["item_code"] in items_dict) + self.assertTrue(test_records[2]["bom_materials"][0]["item_code"] in items_dict) + self.assertTrue(test_records[2]["bom_materials"][1]["item_code"] in items_dict) self.assertEquals(len(items_dict.values()), 2) - + def test_get_items_exploded(self): from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict items_dict = get_bom_items_as_dict(bom="BOM/_Test FG Item 2/001", qty=1, fetch_exploded=1) - self.assertTrue(test_records[2][1]["item_code"] in items_dict) - self.assertFalse(test_records[2][2]["item_code"] in items_dict) - self.assertTrue(test_records[0][1]["item_code"] in items_dict) - self.assertTrue(test_records[0][2]["item_code"] in items_dict) + self.assertTrue(test_records[2]["bom_materials"][0]["item_code"] in items_dict) + self.assertFalse(test_records[2]["bom_materials"][1]["item_code"] in items_dict) + self.assertTrue(test_records[0]["bom_materials"][0]["item_code"] in items_dict) + self.assertTrue(test_records[0]["bom_materials"][1]["item_code"] in items_dict) self.assertEquals(len(items_dict.values()), 3) - + def test_get_items_list(self): from erpnext.manufacturing.doctype.bom.bom import get_bom_items - self.assertEquals(len(get_bom_items(bom="BOM/_Test FG Item 2/001", qty=1, fetch_exploded=1)), 3) \ No newline at end of file + self.assertEquals(len(get_bom_items(bom="BOM/_Test FG Item 2/001", qty=1, fetch_exploded=1)), 3) diff --git a/erpnext/manufacturing/doctype/production_order/test_production_order.py b/erpnext/manufacturing/doctype/production_order/test_production_order.py index 81fc61661fa..9bc001d9b48 100644 --- a/erpnext/manufacturing/doctype/production_order/test_production_order.py +++ b/erpnext/manufacturing/doctype/production_order/test_production_order.py @@ -16,51 +16,53 @@ class TestProductionOrder(unittest.TestCase): frappe.db.sql("delete from `tabStock Ledger Entry`") frappe.db.sql("""delete from `tabBin`""") frappe.db.sql("""delete from `tabGL Entry`""") - + pro_doc = frappe.copy_doc(test_records[0]) pro_doc.insert() pro_doc.submit() - + from erpnext.stock.doctype.stock_entry.test_stock_entry import test_records as se_test_records mr1 = frappe.copy_doc(se_test_records[0]) mr1.insert() mr1.submit() - + mr2 = frappe.copy_doc(se_test_records[0]) mr2.get("mtn_details")[0].item_code = "_Test Item Home Desktop 100" mr2.insert() mr2.submit() - + stock_entry = make_stock_entry(pro_doc.name, "Manufacture/Repack") stock_entry = frappe.get_doc(stock_entry) stock_entry.fiscal_year = "_Test Fiscal Year 2013" stock_entry.fg_completed_qty = 4 stock_entry.posting_date = "2013-05-12" stock_entry.fiscal_year = "_Test Fiscal Year 2013" + stock_entry.set("mtn_details", []) stock_entry.run_method("get_items") stock_entry.submit() - - self.assertEqual(frappe.db.get_value("Production Order", pro_doc.name, + + self.assertEqual(frappe.db.get_value("Production Order", pro_doc.name, "produced_qty"), 4) - self.assertEqual(frappe.db.get_value("Bin", {"item_code": "_Test FG Item", + self.assertEqual(frappe.db.get_value("Bin", {"item_code": "_Test FG Item", "warehouse": "_Test Warehouse 1 - _TC"}, "planned_qty"), 6) - + return pro_doc.name - + def test_over_production(self): from erpnext.stock.doctype.stock_entry.stock_entry import StockOverProductionError pro_order = self.test_planned_qty() - + stock_entry = make_stock_entry(pro_order, "Manufacture/Repack") stock_entry = frappe.get_doc(stock_entry) stock_entry.posting_date = "2013-05-12" stock_entry.fiscal_year = "_Test Fiscal Year 2013" stock_entry.fg_completed_qty = 15 + stock_entry.set("mtn_details", []) stock_entry.run_method("get_items") stock_entry.insert() - - self.assertRaises(StockOverProductionError, stock_entry.submit) - - -test_records = frappe.get_test_records('Production Order') \ No newline at end of file + self.assertRaises(StockOverProductionError, stock_entry.submit) + + + +test_records = frappe.get_test_records('Production Order') diff --git a/erpnext/projects/doctype/time_log/test_time_log.py b/erpnext/projects/doctype/time_log/test_time_log.py index eedad0b387a..7aadf5c2861 100644 --- a/erpnext/projects/doctype/time_log/test_time_log.py +++ b/erpnext/projects/doctype/time_log/test_time_log.py @@ -7,8 +7,9 @@ import unittest from erpnext.projects.doctype.time_log.time_log import OverlapError class TestTimeLog(unittest.TestCase): - def test_duplication(self): + def test_duplication(self): ts = frappe.get_doc(frappe.copy_doc(test_records[0])) self.assertRaises(OverlapError, ts.insert) -test_records = frappe.get_test_records('Time Log') \ No newline at end of file +test_records = frappe.get_test_records('Time Log') +test_ignore = ["Time Log Batch", "Sales Invoice"] diff --git a/erpnext/projects/doctype/time_log_batch/test_time_log_batch.py b/erpnext/projects/doctype/time_log_batch/test_time_log_batch.py index 004c3643acf..fdbc2102c2b 100644 --- a/erpnext/projects/doctype/time_log_batch/test_time_log_batch.py +++ b/erpnext/projects/doctype/time_log_batch/test_time_log_batch.py @@ -14,10 +14,10 @@ class TimeLogBatchTest(unittest.TestCase): }) time_log.insert() time_log.submit() - + self.assertEquals(frappe.db.get_value("Time Log", time_log.name, "status"), "Submitted") tlb = frappe.copy_doc(test_records[0]) - tlb["time_log_batch_details"][0].time_log = time_log.name + tlb.get("time_log_batch_details")[0].time_log = time_log.name tlb.insert() tlb.submit() @@ -25,4 +25,6 @@ class TimeLogBatchTest(unittest.TestCase): tlb.cancel() self.assertEquals(frappe.db.get_value("Time Log", time_log.name, "status"), "Submitted") -test_records = frappe.get_test_records('Time Log Batch') \ No newline at end of file +test_records = frappe.get_test_records('Time Log Batch') +test_dependencies = ["Time Log"] +test_ignore = ["Sales Invoice"] diff --git a/erpnext/projects/doctype/time_log_batch/time_log_batch.py b/erpnext/projects/doctype/time_log_batch/time_log_batch.py index 4ad8130d8d6..b8204e48559 100644 --- a/erpnext/projects/doctype/time_log_batch/time_log_batch.py +++ b/erpnext/projects/doctype/time_log_batch/time_log_batch.py @@ -31,17 +31,17 @@ class TimeLogBatch(Document): if tl.status != "Submitted" and self.docstatus == 0: frappe.msgprint(_("Time Log must have status 'Submitted'") + \ " :" + tl.name + " (" + _(tl.status) + ")", raise_exception=True) - + def set_status(self): self.status = { "0": "Draft", "1": "Submitted", "2": "Cancelled" }[str(self.docstatus or 0)] - + if self.sales_invoice: self.status = "Billed" - + def on_submit(self): self.update_status(self.name) @@ -57,4 +57,4 @@ class TimeLogBatch(Document): tl = frappe.get_doc("Time Log", d.time_log) tl.time_log_batch = time_log_batch tl.sales_invoice = self.sales_invoice - tl.update_after_submit() \ No newline at end of file + tl.save() diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index e5364d08715..361f4fa4ffd 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -12,7 +12,7 @@ from erpnext.utilities.transaction_base import TransactionBase from erpnext.accounts.party import create_party_account class Customer(TransactionBase): - + def autoname(self): cust_master_name = frappe.defaults.get_global_default('cust_master_name') if cust_master_name == 'Customer Name': @@ -24,7 +24,7 @@ class Customer(TransactionBase): def get_company_abbr(self): return frappe.db.get_value('Company', self.company, 'abbr') - + def validate_values(self): if frappe.defaults.get_global_default('cust_master_name') == 'Naming Series' and not self.naming_series: frappe.throw("Series is Mandatory.", frappe.MandatoryError) @@ -37,28 +37,27 @@ class Customer(TransactionBase): frappe.db.sql("update `tabLead` set status='Converted' where name = %s", self.lead_name) def update_address(self): - frappe.db.sql("""update `tabAddress` set customer_name=%s, modified=NOW() + frappe.db.sql("""update `tabAddress` set customer_name=%s, modified=NOW() where customer=%s""", (self.customer_name, self.name)) def update_contact(self): - frappe.db.sql("""update `tabContact` set customer_name=%s, modified=NOW() + frappe.db.sql("""update `tabContact` set customer_name=%s, modified=NOW() where customer=%s""", (self.customer_name, self.name)) def update_credit_days_limit(self): - frappe.db.sql("""update tabAccount set credit_days = %s, credit_limit = %s - where master_type='Customer' and master_name = %s""", + frappe.db.sql("""update tabAccount set credit_days = %s, credit_limit = %s + where master_type='Customer' and master_name = %s""", (self.credit_days or 0, self.credit_limit or 0, self.name)) def create_lead_address_contact(self): if self.lead_name: - if not frappe.db.get_value("Address", {"lead": self.lead_name, "customer": self.customer}): - frappe.db.sql("""update `tabAddress` set customer=%s, customer_name=%s where lead=%s""", + if not frappe.db.get_value("Address", {"lead": self.lead_name, "customer": self.name}): + frappe.db.sql("""update `tabAddress` set customer=%s, customer_name=%s where lead=%s""", (self.name, self.customer_name, self.lead_name)) lead = frappe.db.get_value("Lead", self.lead_name, ["lead_name", "email_id", "phone", "mobile_no"], as_dict=True) - c = frappe.get_doc('Contact') - c.set("__islocal", 1) - c.first_name = lead.lead_name + c = frappe.new_doc('Contact') + c.first_name = lead.lead_name c.email_id = lead.email_id c.phone = lead.phone c.mobile_no = lead.mobile_no @@ -72,7 +71,7 @@ class Customer(TransactionBase): def on_update(self): self.validate_name_with_customer_group() - + self.update_lead_status() self.update_address() self.update_contact() @@ -84,29 +83,29 @@ class Customer(TransactionBase): self.update_credit_days_limit() #create address and contact from lead self.create_lead_address_contact() - + def validate_name_with_customer_group(self): if frappe.db.exists("Customer Group", self.name): frappe.msgprint("An Customer Group exists with same name (%s), \ - please change the Customer name or rename the Customer Group" % + please change the Customer name or rename the Customer Group" % self.name, raise_exception=1) def delete_customer_address(self): addresses = frappe.db.sql("""select name, lead from `tabAddress` where customer=%s""", (self.name,)) - + for name, lead in addresses: if lead: frappe.db.sql("""update `tabAddress` set customer=null, customer_name=null where name=%s""", name) else: frappe.db.sql("""delete from `tabAddress` where name=%s""", name) - + def delete_customer_contact(self): - for contact in frappe.db.sql_list("""select name from `tabContact` + for contact in frappe.db.sql_list("""select name from `tabContact` where customer=%s""", self.name): frappe.delete_doc("Contact", contact) - + def delete_customer_account(self): """delete customer's ledger if exist and check balance before deletion""" acc = frappe.db.sql("select name from `tabAccount` where master_type = 'Customer' \ @@ -120,7 +119,7 @@ class Customer(TransactionBase): self.delete_customer_account() if self.lead_name: frappe.db.sql("update `tabLead` set status='Interested' where name=%s",self.lead_name) - + def before_rename(self, olddn, newdn, merge=False): from erpnext.accounts.utils import rename_account_for rename_account_for("Customer", olddn, newdn, merge, self.company) @@ -134,7 +133,7 @@ class Customer(TransactionBase): self.update_customer_address(newdn, set_field) def update_customer_address(self, newdn, set_field): - frappe.db.sql("""update `tabAddress` set address_title=%(newdn)s + frappe.db.sql("""update `tabAddress` set address_title=%(newdn)s {set_field} where customer=%(newdn)s"""\ .format(set_field=set_field), ({"newdn": newdn})) @@ -142,21 +141,21 @@ class Customer(TransactionBase): def get_dashboard_info(customer): if not frappe.has_permission("Customer", "read", customer): frappe.msgprint("No Permission", raise_exception=True) - + out = {} for doctype in ["Opportunity", "Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]: - out[doctype] = frappe.db.get_value(doctype, + out[doctype] = frappe.db.get_value(doctype, {"customer": customer, "docstatus": ["!=", 2] }, "count(*)") - - billing = frappe.db.sql("""select sum(grand_total), sum(outstanding_amount) - from `tabSales Invoice` - where customer=%s + + billing = frappe.db.sql("""select sum(grand_total), sum(outstanding_amount) + from `tabSales Invoice` + where customer=%s and docstatus = 1 and fiscal_year = %s""", (customer, frappe.db.get_default("fiscal_year"))) - + out["total_billing"] = billing[0][0] out["total_unpaid"] = billing[0][1] - + return out @@ -165,11 +164,11 @@ def get_customer_list(doctype, txt, searchfield, start, page_len, filters): fields = ["name", "customer_group", "territory"] else: fields = ["name", "customer_name", "customer_group", "territory"] - - return frappe.db.sql("""select %s from `tabCustomer` where docstatus < 2 - and (%s like %s or customer_name like %s) order by + + return frappe.db.sql("""select %s from `tabCustomer` where docstatus < 2 + and (%s like %s or customer_name like %s) order by case when name like %s then 0 else 1 end, case when customer_name like %s then 0 else 1 end, - name, customer_name limit %s, %s""" % - (", ".join(fields), searchfield, "%s", "%s", "%s", "%s", "%s", "%s"), - ("%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, start, page_len)) \ No newline at end of file + name, customer_name limit %s, %s""" % + (", ".join(fields), searchfield, "%s", "%s", "%s", "%s", "%s", "%s"), + ("%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, start, page_len)) diff --git a/erpnext/selling/doctype/lead/lead.py b/erpnext/selling/doctype/lead/lead.py index 57e73d09b4f..8163be3111f 100644 --- a/erpnext/selling/doctype/lead/lead.py +++ b/erpnext/selling/doctype/lead/lead.py @@ -96,7 +96,7 @@ def _make_customer(source_name, target_doc=None, ignore_permissions=False): } }}, target_doc, set_missing_values, ignore_permissions=ignore_permissions) - return doclist.as_dict() + return doclist @frappe.whitelist() def make_opportunity(source_name, target_doc=None): diff --git a/erpnext/selling/doctype/lead/test_lead.py b/erpnext/selling/doctype/lead/test_lead.py index 504f8398b7f..606e328a627 100644 --- a/erpnext/selling/doctype/lead/test_lead.py +++ b/erpnext/selling/doctype/lead/test_lead.py @@ -13,9 +13,9 @@ class TestLead(unittest.TestCase): from erpnext.selling.doctype.lead.lead import make_customer customer = make_customer("_T-Lead-00001") - self.assertEquals(customer[0]["doctype"], "Customer") - self.assertEquals(customer[0]["lead_name"], "_T-Lead-00001") + self.assertEquals(customer.doctype, "Customer") + self.assertEquals(customer.lead_name, "_T-Lead-00001") - customer[0]["company"] = "_Test Company" - customer[0]["customer_group"] = "_Test Customer Group" - frappe.get_doc(customer).insert() + customer.company = "_Test Company" + customer.customer_group = "_Test Customer Group" + customer.insert() diff --git a/erpnext/selling/doctype/opportunity/opportunity.py b/erpnext/selling/doctype/opportunity/opportunity.py index a2358a3250d..c87c9836fe6 100644 --- a/erpnext/selling/doctype/opportunity/opportunity.py +++ b/erpnext/selling/doctype/opportunity/opportunity.py @@ -161,4 +161,4 @@ def make_quotation(source_name, target_doc=None): } }, target_doc, set_missing_values) - return doclist.as_dict() \ No newline at end of file + return doclist \ No newline at end of file diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index f5c2050e64f..d6ade9e0cbc 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -137,7 +137,7 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False): # postprocess: fetch shipping address, set missing values - return doclist.as_dict() + return doclist def _make_customer(source_name, ignore_permissions=False): quotation = frappe.db.get_value("Quotation", source_name, ["lead", "order_type"]) diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py index 54aa1c4eb00..d393a3d9e2a 100644 --- a/erpnext/selling/doctype/quotation/test_quotation.py +++ b/erpnext/selling/doctype/quotation/test_quotation.py @@ -10,26 +10,26 @@ test_dependencies = ["Sales BOM"] class TestQuotation(unittest.TestCase): def test_make_sales_order(self): from erpnext.selling.doctype.quotation.quotation import make_sales_order - + quotation = frappe.copy_doc(test_records[0]) quotation.insert() - + self.assertRaises(frappe.ValidationError, make_sales_order, quotation.name) - + quotation.submit() sales_order = make_sales_order(quotation.name) - - self.assertEquals(sales_order[0]["doctype"], "Sales Order") - self.assertEquals(len(sales_order), 2) - self.assertEquals(sales_order[1]["doctype"], "Sales Order Item") - self.assertEquals(sales_order[1]["prevdoc_docname"], quotation.name) - self.assertEquals(sales_order[0]["customer"], "_Test Customer") - - sales_order[0]["delivery_date"] = "2014-01-01" - sales_order[0]["naming_series"] = "_T-Quotation-" - sales_order[0]["transaction_date"] = "2013-05-12" - frappe.get_doc(sales_order).insert() + + self.assertEquals(sales_order.doctype, "Sales Order") + self.assertEquals(len(sales_order.get("sales_order_details")), 2) + self.assertEquals(sales_order.get("sales_order_details")[0]["doctype"], "Sales Order Item") + self.assertEquals(sales_order.get("sales_order_details")[0]["prevdoc_docname"], quotation.name) + self.assertEquals(sales_order.customer, "_Test Customer") + + sales_order.delivery_date = "2014-01-01" + sales_order.naming_series = "_T-Quotation-" + sales_order.transaction_date = "2013-05-12" + sales_order.insert() -test_records = frappe.get_test_records('Quotation') \ No newline at end of file +test_records = frappe.get_test_records('Quotation') diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index d420e967b2e..f4b6833d41c 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -18,28 +18,28 @@ class SalesOrder(SellingController): person_tname = 'Target Detail' partner_tname = 'Partner Target Detail' territory_tname = 'Territory Target Detail' - + def validate_mandatory(self): # validate transaction date v/s delivery date if self.delivery_date: if getdate(self.transaction_date) > getdate(self.delivery_date): msgprint("Expected Delivery Date cannot be before Sales Order Date") raise Exception - + def validate_po(self): # validate p.o date v/s delivery date if self.po_date and self.delivery_date and getdate(self.po_date) > getdate(self.delivery_date): msgprint("Expected Delivery Date cannot be before Purchase Order Date") - raise Exception - + raise Exception + if self.po_no and self.customer: so = frappe.db.sql("select name from `tabSales Order` \ where ifnull(po_no, '') = %s and name != %s and docstatus < 2\ and customer = %s", (self.po_no, self.name, self.customer)) if so and so[0][0]: - msgprint("""Another Sales Order (%s) exists against same PO No and Customer. + msgprint("""Another Sales Order (%s) exists against same PO No and Customer. Please be sure, you are not making duplicate entry.""" % so[0][0]) - + def validate_for_items(self): check_list, flag = [], 0 chk_dupl_itm = [] @@ -49,9 +49,9 @@ class SalesOrder(SellingController): if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 'Yes': if not d.warehouse: - msgprint("""Please enter Reserved Warehouse for item %s + msgprint("""Please enter Reserved Warehouse for item %s as it is stock Item""" % d.item_code, raise_exception=1) - + if e in check_list: msgprint("Item %s has been entered twice." % d.item_code) else: @@ -64,7 +64,7 @@ class SalesOrder(SellingController): # used for production plan d.transaction_date = self.transaction_date - + tot_avail_qty = frappe.db.sql("select projected_qty from `tabBin` \ where item_code = %s and warehouse = %s", (d.item_code,d.warehouse)) d.projected_qty = tot_avail_qty and flt(tot_avail_qty[0][0]) or 0 @@ -79,26 +79,26 @@ class SalesOrder(SellingController): def validate_order_type(self): super(SalesOrder, self).validate_order_type() - + def validate_delivery_date(self): if self.order_type == 'Sales' and not self.delivery_date: msgprint("Please enter 'Expected Delivery Date'") raise Exception - + self.validate_sales_mntc_quotation() def validate_proj_cust(self): if self.project_name and self.customer_name: - res = frappe.db.sql("""select name from `tabProject` where name = %s - and (customer = %s or ifnull(customer,'')='')""", + res = frappe.db.sql("""select name from `tabProject` where name = %s + and (customer = %s or ifnull(customer,'')='')""", (self.project_name, self.customer)) if not res: msgprint("Customer - %s does not belong to project - %s. \n\nIf you want to use project for multiple customers then please make customer details blank in project - %s."%(self.customer,self.project_name,self.project_name)) raise Exception - + def validate(self): super(SalesOrder, self).validate() - + self.validate_order_type() self.validate_delivery_date() self.validate_mandatory() @@ -111,28 +111,28 @@ class SalesOrder(SellingController): from erpnext.stock.doctype.packed_item.packed_item import make_packing_list make_packing_list(self,'sales_order_details') - + self.validate_with_previous_doc() - + if not self.status: self.status = "Draft" from erpnext.utilities import validate_status - validate_status(self.status, ["Draft", "Submitted", "Stopped", + validate_status(self.status, ["Draft", "Submitted", "Stopped", "Cancelled"]) if not self.billing_status: self.billing_status = 'Not Billed' - if not self.delivery_status: self.delivery_status = 'Not Delivered' - + if not self.delivery_status: self.delivery_status = 'Not Delivered' + def validate_warehouse(self): from erpnext.stock.utils import validate_warehouse_company - - warehouses = list(set([d.warehouse for d in + + warehouses = list(set([d.warehouse for d in self.get(self.fname) if d.warehouse])) - + for w in warehouses: validate_warehouse_company(w, self.company) - + def validate_with_previous_doc(self): super(SalesOrder, self).validate_with_previous_doc(self.tname, { "Quotation": { @@ -141,31 +141,31 @@ class SalesOrder(SellingController): } }) - + def update_enquiry_status(self, prevdoc, flag): enq = frappe.db.sql("select t2.prevdoc_docname from `tabQuotation` t1, `tabQuotation Item` t2 where t2.parent = t1.name and t1.name=%s", prevdoc) if enq: frappe.db.sql("update `tabOpportunity` set status = %s where name=%s",(flag,enq[0][0])) - def update_prevdoc_status(self, flag): + def update_prevdoc_status(self, flag): for quotation in list(set([d.prevdoc_docname for d in self.get(self.fname)])): if quotation: doc = frappe.get_doc("Quotation", quotation) if doc.docstatus==2: frappe.throw(quotation + ": " + frappe._("Quotation is cancelled.")) - + doc.set_status(update=True) def on_submit(self): self.update_stock_ledger(update_stock = 1) self.check_credit(self.grand_total) - + frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.grand_total, self) - + self.update_prevdoc_status('submit') frappe.db.set(self, 'status', 'Submitted') - + def on_cancel(self): # Cannot cancel stopped SO if self.status == 'Stopped': @@ -173,45 +173,45 @@ class SalesOrder(SellingController): raise Exception self.check_nextdoc_docstatus() self.update_stock_ledger(update_stock = -1) - + self.update_prevdoc_status('cancel') - + frappe.db.set(self, 'status', 'Cancelled') - + def check_nextdoc_docstatus(self): # Checks Delivery Note submit_dn = frappe.db.sql("select t1.name from `tabDelivery Note` t1,`tabDelivery Note Item` t2 where t1.name = t2.parent and t2.against_sales_order = %s and t1.docstatus = 1", self.name) if submit_dn: msgprint("Delivery Note : " + cstr(submit_dn[0][0]) + " has been submitted against " + cstr(self.doctype) + ". Please cancel Delivery Note : " + cstr(submit_dn[0][0]) + " first and then cancel "+ cstr(self.doctype), raise_exception = 1) - + # Checks Sales Invoice - submit_rv = frappe.db.sql("""select t1.name - from `tabSales Invoice` t1,`tabSales Invoice Item` t2 - where t1.name = t2.parent and t2.sales_order = %s and t1.docstatus = 1""", + submit_rv = frappe.db.sql("""select t1.name + from `tabSales Invoice` t1,`tabSales Invoice Item` t2 + where t1.name = t2.parent and t2.sales_order = %s and t1.docstatus = 1""", self.name) if submit_rv: msgprint("Sales Invoice : " + cstr(submit_rv[0][0]) + " has already been submitted against " +cstr(self.doctype)+ ". Please cancel Sales Invoice : "+ cstr(submit_rv[0][0]) + " first and then cancel "+ cstr(self.doctype), raise_exception = 1) - + #check maintenance schedule submit_ms = frappe.db.sql("select t1.name from `tabMaintenance Schedule` t1, `tabMaintenance Schedule Item` t2 where t2.parent=t1.name and t2.prevdoc_docname = %s and t1.docstatus = 1",self.name) if submit_ms: msgprint("Maintenance Schedule : " + cstr(submit_ms[0][0]) + " has already been submitted against " +cstr(self.doctype)+ ". Please cancel Maintenance Schedule : "+ cstr(submit_ms[0][0]) + " first and then cancel "+ cstr(self.doctype), raise_exception = 1) - + # check maintenance visit submit_mv = frappe.db.sql("select t1.name from `tabMaintenance Visit` t1, `tabMaintenance Visit Purpose` t2 where t2.parent=t1.name and t2.prevdoc_docname = %s and t1.docstatus = 1",self.name) if submit_mv: msgprint("Maintenance Visit : " + cstr(submit_mv[0][0]) + " has already been submitted against " +cstr(self.doctype)+ ". Please cancel Maintenance Visit : " + cstr(submit_mv[0][0]) + " first and then cancel "+ cstr(self.doctype), raise_exception = 1) - + # check production order pro_order = frappe.db.sql("""select name from `tabProduction Order` where sales_order = %s and docstatus = 1""", self.name) if pro_order: - msgprint("""Production Order: %s exists against this sales order. - Please cancel production order first and then cancel this sales order""" % + msgprint("""Production Order: %s exists against this sales order. + Please cancel production order first and then cancel this sales order""" % pro_order[0][0], raise_exception=1) def check_modified_date(self): mod_db = frappe.db.get_value("Sales Order", self.name, "modified") - date_diff = frappe.db.sql("select TIMEDIFF('%s', '%s')" % + date_diff = frappe.db.sql("select TIMEDIFF('%s', '%s')" % ( mod_db, cstr(self.modified))) if date_diff and date_diff[0][0]: msgprint("%s: %s has been modified after you have opened. Please Refresh" @@ -221,7 +221,7 @@ class SalesOrder(SellingController): self.check_modified_date() self.update_stock_ledger(-1) frappe.db.set(self, 'status', 'Stopped') - msgprint("""%s: %s has been Stopped. To make transactions against this Sales Order + msgprint("""%s: %s has been Stopped. To make transactions against this Sales Order you need to Unstop it.""" % (self.doctype, self.name)) def unstop_sales_order(self): @@ -237,7 +237,7 @@ class SalesOrder(SellingController): if frappe.db.get_value("Item", d['item_code'], "is_stock_item") == "Yes": args = { "item_code": d['item_code'], - "warehouse": d['reserved_warehouse'], + "warehouse": d['reserved_warehouse'], "reserved_qty": flt(update_stock) * flt(d['reserved_qty']), "posting_date": self.transaction_date, "voucher_type": self.doctype, @@ -248,76 +248,76 @@ class SalesOrder(SellingController): def on_update(self): pass - + def get_portal_page(self): return "order" if self.docstatus==1 else None - + def set_missing_values(source, target): doc = frappe.get_doc(target) doc.run_method("onload_post_render") - + @frappe.whitelist() -def make_material_request(source_name, target_doc=None): - def postprocess(source, doclist): - doclist[0].material_request_type = "Purchase" - - doclist = get_mapped_doc("Sales Order", source_name, { +def make_material_request(source_name, target_doc=None): + def postprocess(source, doc): + doc.material_request_type = "Purchase" + + doc = get_mapped_doc("Sales Order", source_name, { "Sales Order": { - "doctype": "Material Request", + "doctype": "Material Request", "validation": { "docstatus": ["=", 1] } - }, + }, "Sales Order Item": { - "doctype": "Material Request Item", + "doctype": "Material Request Item", "field_map": { - "parent": "sales_order_no", + "parent": "sales_order_no", "stock_uom": "uom" } } }, target_doc, postprocess) - - return doclist + + return doc @frappe.whitelist() -def make_delivery_note(source_name, target_doc=None): +def make_delivery_note(source_name, target_doc=None): def update_item(obj, target, source_parent): target.base_amount = (flt(obj.qty) - flt(obj.delivered_qty)) * flt(obj.base_rate) target.amount = (flt(obj.qty) - flt(obj.delivered_qty)) * flt(obj.rate) target.qty = flt(obj.qty) - flt(obj.delivered_qty) - + doclist = get_mapped_doc("Sales Order", source_name, { "Sales Order": { - "doctype": "Delivery Note", + "doctype": "Delivery Note", "field_map": { - "shipping_address": "address_display", - "shipping_address_name": "customer_address", + "shipping_address": "address_display", + "shipping_address_name": "customer_address", }, "validation": { "docstatus": ["=", 1] } - }, + }, "Sales Order Item": { - "doctype": "Delivery Note Item", + "doctype": "Delivery Note Item", "field_map": { - "rate": "rate", - "name": "prevdoc_detail_docname", - "parent": "against_sales_order", + "rate": "rate", + "name": "prevdoc_detail_docname", + "parent": "against_sales_order", }, "postprocess": update_item, "condition": lambda doc: doc.delivered_qty < doc.qty - }, + }, "Sales Taxes and Charges": { - "doctype": "Sales Taxes and Charges", + "doctype": "Sales Taxes and Charges", "add_if_empty": True - }, + }, "Sales Team": { "doctype": "Sales Team", "add_if_empty": True } }, target_doc, set_missing_values) - - return doclist.as_dict() + + return doclist @frappe.whitelist() def make_sales_invoice(source_name, target_doc=None): @@ -325,94 +325,94 @@ def make_sales_invoice(source_name, target_doc=None): doc = frappe.get_doc(target) doc.is_pos = 0 doc.run_method("onload_post_render") - + def update_item(obj, target, source_parent): target.amount = flt(obj.amount) - flt(obj.billed_amt) target.base_amount = target.amount * flt(source_parent.conversion_rate) target.qty = obj.rate and target.amount / flt(obj.rate) or obj.qty - + doclist = get_mapped_doc("Sales Order", source_name, { "Sales Order": { - "doctype": "Sales Invoice", + "doctype": "Sales Invoice", "validation": { "docstatus": ["=", 1] } - }, + }, "Sales Order Item": { - "doctype": "Sales Invoice Item", + "doctype": "Sales Invoice Item", "field_map": { - "name": "so_detail", - "parent": "sales_order", + "name": "so_detail", + "parent": "sales_order", }, "postprocess": update_item, "condition": lambda doc: doc.base_amount==0 or doc.billed_amt < doc.amount - }, + }, "Sales Taxes and Charges": { - "doctype": "Sales Taxes and Charges", + "doctype": "Sales Taxes and Charges", "add_if_empty": True - }, + }, "Sales Team": { - "doctype": "Sales Team", + "doctype": "Sales Team", "add_if_empty": True } }, target_doc, set_missing_values) - - return doclist.as_dict() - + + return doclist + @frappe.whitelist() def make_maintenance_schedule(source_name, target_doc=None): - maint_schedule = frappe.db.sql("""select t1.name - from `tabMaintenance Schedule` t1, `tabMaintenance Schedule Item` t2 + maint_schedule = frappe.db.sql("""select t1.name + from `tabMaintenance Schedule` t1, `tabMaintenance Schedule Item` t2 where t2.parent=t1.name and t2.prevdoc_docname=%s and t1.docstatus=1""", source_name) - + if not maint_schedule: doclist = get_mapped_doc("Sales Order", source_name, { "Sales Order": { - "doctype": "Maintenance Schedule", + "doctype": "Maintenance Schedule", "field_map": { "name": "sales_order_no" - }, + }, "validation": { "docstatus": ["=", 1] } - }, + }, "Sales Order Item": { - "doctype": "Maintenance Schedule Item", + "doctype": "Maintenance Schedule Item", "field_map": { "parent": "prevdoc_docname" }, "add_if_empty": True } }, target_doc) - - return doclist.as_dict() - + + return doclist + @frappe.whitelist() def make_maintenance_visit(source_name, target_doc=None): - visit = frappe.db.sql("""select t1.name - from `tabMaintenance Visit` t1, `tabMaintenance Visit Purpose` t2 - where t2.parent=t1.name and t2.prevdoc_docname=%s + visit = frappe.db.sql("""select t1.name + from `tabMaintenance Visit` t1, `tabMaintenance Visit Purpose` t2 + where t2.parent=t1.name and t2.prevdoc_docname=%s and t1.docstatus=1 and t1.completion_status='Fully Completed'""", source_name) - + if not visit: doclist = get_mapped_doc("Sales Order", source_name, { "Sales Order": { - "doctype": "Maintenance Visit", + "doctype": "Maintenance Visit", "field_map": { "name": "sales_order_no" }, "validation": { "docstatus": ["=", 1] } - }, + }, "Sales Order Item": { - "doctype": "Maintenance Visit Purpose", + "doctype": "Maintenance Visit Purpose", "field_map": { - "parent": "prevdoc_docname", + "parent": "prevdoc_docname", "parenttype": "prevdoc_doctype" }, "add_if_empty": True } }, target_doc) - - return doclist.as_dict() + + return doclist diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index af52028461f..b41027bcfd9 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -4,82 +4,82 @@ import frappe from frappe.utils import flt import unittest +import copy class TestSalesOrder(unittest.TestCase): def tearDown(self): frappe.set_user("Administrator") - + def test_make_material_request(self): from erpnext.selling.doctype.sales_order.sales_order import make_material_request - + so = frappe.copy_doc(test_records[0]).insert() - - self.assertRaises(frappe.ValidationError, make_material_request, + + self.assertRaises(frappe.ValidationError, make_material_request, so.name) sales_order = frappe.get_doc("Sales Order", so.name) sales_order.submit() mr = make_material_request(so.name) - - self.assertEquals(mr[0]["material_request_type"], "Purchase") - self.assertEquals(len(mr), len(sales_order)) + + self.assertEquals(mr.material_request_type, "Purchase") + self.assertEquals(len(mr.get("indent_details")), len(sales_order.get("sales_order_details"))) def test_make_delivery_note(self): from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note so = frappe.copy_doc(test_records[0]).insert() - self.assertRaises(frappe.ValidationError, make_delivery_note, + self.assertRaises(frappe.ValidationError, make_delivery_note, so.name) sales_order = frappe.get_doc("Sales Order", so.name) sales_order.submit() dn = make_delivery_note(so.name) - - self.assertEquals(dn[0]["doctype"], "Delivery Note") - self.assertEquals(len(dn), len(sales_order)) + + self.assertEquals(dn.doctype, "Delivery Note") + self.assertEquals(len(dn.get("delivery_note_details")), len(sales_order.get("sales_order_details"))) def test_make_sales_invoice(self): from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice so = frappe.copy_doc(test_records[0]).insert() - self.assertRaises(frappe.ValidationError, make_sales_invoice, + self.assertRaises(frappe.ValidationError, make_sales_invoice, so.name) sales_order = frappe.get_doc("Sales Order", so.name) sales_order.submit() si = make_sales_invoice(so.name) - - self.assertEquals(si[0]["doctype"], "Sales Invoice") - self.assertEquals(len(si), len(sales_order)) - self.assertEquals(len([d for d in si if d["doctype"]=="Sales Invoice Item"]), 1) - - si = frappe.get_doc(si) + + self.assertEquals(si.doctype, "Sales Invoice") + self.assertEquals(len(si.get("entries")), len(sales_order.get("sales_order_details"))) + self.assertEquals(len(si.get("entries")), 1) + si.posting_date = "2013-10-10" si.insert() si.submit() si1 = make_sales_invoice(so.name) - self.assertEquals(len([d for d in si1 if d["doctype"]=="Sales Invoice Item"]), 0) - + self.assertEquals(len(si1.get("entries")), 0) + def create_so(self, so_doc = None): if not so_doc: so_doc = test_records[0] - + w = frappe.copy_doc(so_doc) w.insert() w.submit() return w - + def create_dn_against_so(self, so, delivered_qty=0): from erpnext.stock.doctype.delivery_note.test_delivery_note import test_records as dn_test_records from erpnext.stock.doctype.delivery_note.test_delivery_note import _insert_purchase_receipt _insert_purchase_receipt(so.get("sales_order_details")[0].item_code) - + dn = frappe.get_doc(frappe.copy_doc(dn_test_records[0])) dn.get("delivery_note_details")[0].item_code = so.get("sales_order_details")[0].item_code dn.get("delivery_note_details")[0].against_sales_order = so.name @@ -89,76 +89,79 @@ class TestSalesOrder(unittest.TestCase): dn.insert() dn.submit() return dn - + def get_bin_reserved_qty(self, item_code, warehouse): - return flt(frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, + return flt(frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "reserved_qty")) - + def delete_bin(self, item_code, warehouse): - bin = frappe.db.exists({"doctype": "Bin", "item_code": item_code, + bin = frappe.db.exists({"doctype": "Bin", "item_code": item_code, "warehouse": warehouse}) if bin: frappe.delete_doc("Bin", bin[0][0]) - + def check_reserved_qty(self, item_code, warehouse, qty): bin_reserved_qty = self.get_bin_reserved_qty(item_code, warehouse) self.assertEqual(bin_reserved_qty, qty) - + def test_reserved_qty_for_so(self): # reset bin - self.delete_bin(test_records[0][1]["item_code"], test_records[0][1]["warehouse"]) - + so_item = test_records[0]["sales_order_details"][0] + self.delete_bin(so_item["item_code"], so_item["warehouse"]) + # submit so = self.create_so() self.check_reserved_qty(so.get("sales_order_details")[0].item_code, so.get("sales_order_details")[0].warehouse, 10.0) - + # cancel so.cancel() self.check_reserved_qty(so.get("sales_order_details")[0].item_code, so.get("sales_order_details")[0].warehouse, 0.0) - - + + def test_reserved_qty_for_partial_delivery(self): # reset bin - self.delete_bin(test_records[0][1]["item_code"], test_records[0][1]["warehouse"]) - + so_item = test_records[0]["sales_order_details"][0] + self.delete_bin(so_item["item_code"], so_item["warehouse"]) + # submit so so = self.create_so() - + # allow negative stock frappe.db.set_default("allow_negative_stock", 1) - + # submit dn dn = self.create_dn_against_so(so) - + self.check_reserved_qty(so.get("sales_order_details")[0].item_code, so.get("sales_order_details")[0].warehouse, 5.0) - + # stop so so.load_from_db() - so.obj.stop_sales_order() + so.stop_sales_order() self.check_reserved_qty(so.get("sales_order_details")[0].item_code, so.get("sales_order_details")[0].warehouse, 0.0) - + # unstop so so.load_from_db() - so.obj.unstop_sales_order() + so.unstop_sales_order() self.check_reserved_qty(so.get("sales_order_details")[0].item_code, so.get("sales_order_details")[0].warehouse, 5.0) - + # cancel dn dn.cancel() self.check_reserved_qty(so.get("sales_order_details")[0].item_code, so.get("sales_order_details")[0].warehouse, 10.0) - + def test_reserved_qty_for_over_delivery(self): # reset bin - self.delete_bin(test_records[0][1]["item_code"], test_records[0][1]["warehouse"]) - + so_item = test_records[0]["sales_order_details"][0] + self.delete_bin(so_item["item_code"], so_item["warehouse"]) + # submit so so = self.create_so() - + # allow negative stock frappe.db.set_default("allow_negative_stock", 1) - + # set over-delivery tolerance frappe.db.set_value('Item', so.get("sales_order_details")[0].item_code, 'tolerance', 50) - + # submit dn dn = self.create_dn_against_so(so, 15) self.check_reserved_qty(so.get("sales_order_details")[0].item_code, so.get("sales_order_details")[0].warehouse, 0.0) @@ -166,127 +169,127 @@ class TestSalesOrder(unittest.TestCase): # cancel dn dn.cancel() self.check_reserved_qty(so.get("sales_order_details")[0].item_code, so.get("sales_order_details")[0].warehouse, 10.0) - + def test_reserved_qty_for_so_with_packing_list(self): from erpnext.selling.doctype.sales_bom.test_sales_bom import test_records as sbom_test_records - + # change item in test so record - test_record = test_records[0][:] - test_record[1]["item_code"] = "_Test Sales BOM Item" - + test_record = copy.deepcopy(test_records[0]) + test_record["sales_order_details"][0]["item_code"] = "_Test Sales BOM Item" + # reset bin - self.delete_bin(sbom_test_records[0][1]["item_code"], test_record[1]["warehouse"]) - self.delete_bin(sbom_test_records[0][2]["item_code"], test_record[1]["warehouse"]) - + self.delete_bin(sbom_test_records[0]["sales_bom_items"][0]["item_code"], test_record.get("sales_order_details")[0]["warehouse"]) + self.delete_bin(sbom_test_records[0]["sales_bom_items"][1]["item_code"], test_record.get("sales_order_details")[0]["warehouse"]) + # submit so = self.create_so(test_record) - - - self.check_reserved_qty(sbom_test_records[0][1]["item_code"], + + + self.check_reserved_qty(sbom_test_records[0]["sales_bom_items"][0]["item_code"], so.get("sales_order_details")[0].warehouse, 50.0) - self.check_reserved_qty(sbom_test_records[0][2]["item_code"], + self.check_reserved_qty(sbom_test_records[0]["sales_bom_items"][1]["item_code"], so.get("sales_order_details")[0].warehouse, 20.0) - + # cancel so.cancel() - self.check_reserved_qty(sbom_test_records[0][1]["item_code"], + self.check_reserved_qty(sbom_test_records[0]["sales_bom_items"][0]["item_code"], so.get("sales_order_details")[0].warehouse, 0.0) - self.check_reserved_qty(sbom_test_records[0][2]["item_code"], + self.check_reserved_qty(sbom_test_records[0]["sales_bom_items"][1]["item_code"], so.get("sales_order_details")[0].warehouse, 0.0) - + def test_reserved_qty_for_partial_delivery_with_packing_list(self): from erpnext.selling.doctype.sales_bom.test_sales_bom import test_records as sbom_test_records - + # change item in test so record - + test_record = frappe.copy_doc(test_records[0]) - test_record[1]["item_code"] = "_Test Sales BOM Item" + test_record.get("sales_order_details")[0].item_code = "_Test Sales BOM Item" # reset bin - self.delete_bin(sbom_test_records[0][1]["item_code"], test_record[1]["warehouse"]) - self.delete_bin(sbom_test_records[0][2]["item_code"], test_record[1]["warehouse"]) - + self.delete_bin(sbom_test_records[0]["sales_bom_items"][0]["item_code"], test_record.get("sales_order_details")[0].warehouse) + self.delete_bin(sbom_test_records[0]["sales_bom_items"][1]["item_code"], test_record.get("sales_order_details")[0].warehouse) + # submit so = self.create_so(test_record) - + # allow negative stock frappe.db.set_default("allow_negative_stock", 1) - + # submit dn dn = self.create_dn_against_so(so) - - self.check_reserved_qty(sbom_test_records[0][1]["item_code"], + + self.check_reserved_qty(sbom_test_records[0]["sales_bom_items"][0]["item_code"], so.get("sales_order_details")[0].warehouse, 25.0) - self.check_reserved_qty(sbom_test_records[0][2]["item_code"], + self.check_reserved_qty(sbom_test_records[0]["sales_bom_items"][1]["item_code"], so.get("sales_order_details")[0].warehouse, 10.0) - + # stop so so.load_from_db() - so.obj.stop_sales_order() - - self.check_reserved_qty(sbom_test_records[0][1]["item_code"], + so.stop_sales_order() + + self.check_reserved_qty(sbom_test_records[0]["sales_bom_items"][0]["item_code"], so.get("sales_order_details")[0].warehouse, 0.0) - self.check_reserved_qty(sbom_test_records[0][2]["item_code"], + self.check_reserved_qty(sbom_test_records[0]["sales_bom_items"][1]["item_code"], so.get("sales_order_details")[0].warehouse, 0.0) - + # unstop so so.load_from_db() - so.obj.unstop_sales_order() - self.check_reserved_qty(sbom_test_records[0][1]["item_code"], + so.unstop_sales_order() + self.check_reserved_qty(sbom_test_records[0]["sales_bom_items"][0]["item_code"], so.get("sales_order_details")[0].warehouse, 25.0) - self.check_reserved_qty(sbom_test_records[0][2]["item_code"], + self.check_reserved_qty(sbom_test_records[0]["sales_bom_items"][1]["item_code"], so.get("sales_order_details")[0].warehouse, 10.0) - + # cancel dn dn.cancel() - self.check_reserved_qty(sbom_test_records[0][1]["item_code"], + self.check_reserved_qty(sbom_test_records[0]["sales_bom_items"][0]["item_code"], so.get("sales_order_details")[0].warehouse, 50.0) - self.check_reserved_qty(sbom_test_records[0][2]["item_code"], + self.check_reserved_qty(sbom_test_records[0]["sales_bom_items"][1]["item_code"], so.get("sales_order_details")[0].warehouse, 20.0) - + def test_reserved_qty_for_over_delivery_with_packing_list(self): from erpnext.selling.doctype.sales_bom.test_sales_bom import test_records as sbom_test_records - + # change item in test so record test_record = frappe.copy_doc(test_records[0]) - test_record[1]["item_code"] = "_Test Sales BOM Item" + test_record.get("sales_order_details")[0].item_code = "_Test Sales BOM Item" # reset bin - self.delete_bin(sbom_test_records[0][1]["item_code"], test_record[1]["warehouse"]) - self.delete_bin(sbom_test_records[0][2]["item_code"], test_record[1]["warehouse"]) - + self.delete_bin(sbom_test_records[0]["sales_bom_items"][0]["item_code"], test_record.get("sales_order_details")[0].warehouse) + self.delete_bin(sbom_test_records[0]["sales_bom_items"][1]["item_code"], test_record.get("sales_order_details")[0].warehouse) + # submit so = self.create_so(test_record) - + # allow negative stock frappe.db.set_default("allow_negative_stock", 1) - + # set over-delivery tolerance frappe.db.set_value('Item', so.get("sales_order_details")[0].item_code, 'tolerance', 50) - + # submit dn dn = self.create_dn_against_so(so, 15) - - self.check_reserved_qty(sbom_test_records[0][1]["item_code"], + + self.check_reserved_qty(sbom_test_records[0]["sales_bom_items"][0]["item_code"], so.get("sales_order_details")[0].warehouse, 0.0) - self.check_reserved_qty(sbom_test_records[0][2]["item_code"], + self.check_reserved_qty(sbom_test_records[0]["sales_bom_items"][1]["item_code"], so.get("sales_order_details")[0].warehouse, 0.0) # cancel dn dn.cancel() - self.check_reserved_qty(sbom_test_records[0][1]["item_code"], + self.check_reserved_qty(sbom_test_records[0]["sales_bom_items"][0]["item_code"], so.get("sales_order_details")[0].warehouse, 50.0) - self.check_reserved_qty(sbom_test_records[0][2]["item_code"], + self.check_reserved_qty(sbom_test_records[0]["sales_bom_items"][1]["item_code"], so.get("sales_order_details")[0].warehouse, 20.0) def test_warehouse_user(self): frappe.defaults.add_default("Warehouse", "_Test Warehouse 1 - _TC1", "test@example.com", "Restriction") frappe.get_doc("User", "test@example.com")\ .add_roles("Sales User", "Sales Manager", "Material User", "Material Manager") - + frappe.get_doc("User", "test2@example.com")\ .add_roles("Sales User", "Sales Manager", "Material User", "Material Manager") - + frappe.set_user("test@example.com") so = frappe.copy_doc(test_records[0]) @@ -298,9 +301,9 @@ class TestSalesOrder(unittest.TestCase): frappe.set_user("test2@example.com") so.insert() - + frappe.defaults.clear_default("Warehouse", "_Test Warehouse 1 - _TC1", "test@example.com", parenttype="Restriction") test_dependencies = ["Sales BOM", "Currency Exchange"] - -test_records = frappe.get_test_records('Sales Order') \ No newline at end of file + +test_records = frappe.get_test_records('Sales Order') diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 347b1f4d81c..ba1509fb5d2 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -28,21 +28,21 @@ class DeliveryNote(SellingController): 'status_field': 'delivery_status', 'keyword': 'Delivered' }] - + def onload(self): billed_qty = frappe.db.sql("""select sum(ifnull(qty, 0)) from `tabSales Invoice Item` where docstatus=1 and delivery_note=%s""", self.name) if billed_qty: total_qty = sum((item.qty for item in self.get("delivery_note_details"))) self.set("__billing_complete", billed_qty[0][0] == total_qty) - + def get_portal_page(self): return "shipment" if self.docstatus==1 else None def set_actual_qty(self): for d in self.get('delivery_note_details'): if d.item_code and d.warehouse: - actual_qty = frappe.db.sql("""select actual_qty from `tabBin` + actual_qty = frappe.db.sql("""select actual_qty from `tabBin` where item_code = %s and warehouse = %s""", (d.item_code, d.warehouse)) d.actual_qty = actual_qty and flt(actual_qty[0][0]) or 0 @@ -57,7 +57,7 @@ class DeliveryNote(SellingController): def validate(self): super(DeliveryNote, self).validate() - + from erpnext.utilities import validate_status validate_status(self.status, ["Draft", "Submitted", "Cancelled"]) @@ -67,18 +67,18 @@ class DeliveryNote(SellingController): self.validate_for_items() self.validate_warehouse() self.validate_uom_is_integer("stock_uom", "qty") - self.update_current_stock() + self.update_current_stock() self.validate_with_previous_doc() - + from erpnext.stock.doctype.packed_item.packed_item import make_packing_list make_packing_list(self, 'delivery_note_details') - + self.status = 'Draft' - if not self.installation_status: self.installation_status = 'Not Installed' - + if not self.installation_status: self.installation_status = 'Not Installed' + def validate_with_previous_doc(self): items = self.get("delivery_note_details") - + for fn in (("Sales Order", "against_sales_order"), ("Sales Invoice", "against_sales_invoice")): if filter(None, [getattr(d, fn[1], None) for d in items]): super(DeliveryNote, self).validate_with_previous_doc(self.tname, { @@ -97,12 +97,12 @@ class DeliveryNote(SellingController): "is_child_table": True } }) - + def validate_proj_cust(self): """check for does customer belong to same project as entered..""" if self.project_name and self.customer: - res = frappe.db.sql("""select name from `tabProject` - where name = %s and (customer = %s or + res = frappe.db.sql("""select name from `tabProject` + where name = %s and (customer = %s or ifnull(customer,'')='')""", (self.project_name, self.customer)) if not res: msgprint("Customer - %s does not belong to project - %s. \n\nIf you want to use project for multiple customers then please make customer details blank in project - %s."%(self.customer,self.project_name,self.project_name)) @@ -116,13 +116,13 @@ class DeliveryNote(SellingController): if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 'Yes': if e in check_list: - msgprint("Please check whether item %s has been entered twice wrongly." + msgprint("Please check whether item %s has been entered twice wrongly." % d.item_code) else: check_list.append(e) else: if f in chk_dupl_itm: - msgprint("Please check whether item %s has been entered twice wrongly." + msgprint("Please check whether item %s has been entered twice wrongly." % d.item_code) else: chk_dupl_itm.append(f) @@ -133,7 +133,7 @@ class DeliveryNote(SellingController): if not d['warehouse']: msgprint("Please enter Warehouse for item %s as it is stock item" % d['item_code'], raise_exception=1) - + def update_current_stock(self): for d in self.get('delivery_note_details'): @@ -150,15 +150,15 @@ class DeliveryNote(SellingController): # Check for Approving Authority frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.company, self.grand_total, self) - - # update delivered qty in sales order + + # update delivered qty in sales order self.update_prevdoc_status() - + # create stock ledger entry self.update_stock_ledger() self.credit_limit() - + self.make_gl_entries() # set DN status @@ -168,14 +168,14 @@ class DeliveryNote(SellingController): def on_cancel(self): self.check_stop_sales_order("against_sales_order") self.check_next_docstatus() - + self.update_prevdoc_status() - + self.update_stock_ledger() frappe.db.set(self, 'status', 'Cancelled') self.cancel_packing_slips() - + self.make_cancel_gl_entries() def validate_packed_qty(self): @@ -198,17 +198,17 @@ class DeliveryNote(SellingController): frappe.msgprint("Packing Error:\n" + err_msg, raise_exception=1) def check_next_docstatus(self): - submit_rv = frappe.db.sql("""select t1.name - from `tabSales Invoice` t1,`tabSales Invoice Item` t2 - where t1.name = t2.parent and t2.delivery_note = %s and t1.docstatus = 1""", + submit_rv = frappe.db.sql("""select t1.name + from `tabSales Invoice` t1,`tabSales Invoice Item` t2 + where t1.name = t2.parent and t2.delivery_note = %s and t1.docstatus = 1""", (self.name)) if submit_rv: msgprint("Sales Invoice : " + cstr(submit_rv[0][0]) + " has already been submitted !") raise Exception , "Validation Error." - submit_in = frappe.db.sql("""select t1.name - from `tabInstallation Note` t1, `tabInstallation Note Item` t2 - where t1.name = t2.parent and t2.prevdoc_docname = %s and t1.docstatus = 1""", + submit_in = frappe.db.sql("""select t1.name + from `tabInstallation Note` t1, `tabInstallation Note Item` t2 + where t1.name = t2.parent and t2.prevdoc_docname = %s and t1.docstatus = 1""", (self.name)) if submit_in: msgprint("Installation Note : "+cstr(submit_in[0][0]) +" has already been submitted !") @@ -218,7 +218,7 @@ class DeliveryNote(SellingController): """ Cancel submitted packing slips related to this delivery note """ - res = frappe.db.sql("""SELECT name FROM `tabPacking Slip` WHERE delivery_note = %s + res = frappe.db.sql("""SELECT name FROM `tabPacking Slip` WHERE delivery_note = %s AND docstatus = 1""", self.name) if res: @@ -234,19 +234,19 @@ class DeliveryNote(SellingController): if frappe.db.get_value("Item", d.item_code, "is_stock_item") == "Yes" \ and d.warehouse: self.update_reserved_qty(d) - + sl_entries.append(self.get_sl_entries(d, { "actual_qty": -1*flt(d['qty']), })) - + self.make_sl_entries(sl_entries) - + def update_reserved_qty(self, d): if d['reserved_qty'] < 0 : # Reduce reserved qty from reserved warehouse mentioned in so if not d["reserved_warehouse"]: frappe.throw(_("Reserved Warehouse is missing in Sales Order")) - + args = { "item_code": d['item_code'], "warehouse": d["reserved_warehouse"], @@ -271,88 +271,88 @@ class DeliveryNote(SellingController): def get_invoiced_qty_map(delivery_note): """returns a map: {dn_detail: invoiced_qty}""" invoiced_qty_map = {} - + for dn_detail, qty in frappe.db.sql("""select dn_detail, qty from `tabSales Invoice Item` where delivery_note=%s and docstatus=1""", delivery_note): if not invoiced_qty_map.get(dn_detail): invoiced_qty_map[dn_detail] = 0 invoiced_qty_map[dn_detail] += qty - + return invoiced_qty_map @frappe.whitelist() def make_sales_invoice(source_name, target_doc=None): invoiced_qty_map = get_invoiced_qty_map(source_name) - + def update_accounts(source, target): si = frappe.get_doc(target) si.is_pos = 0 si.run_method("onload_post_render") - + if len(si.get("entries")) == 0: frappe.msgprint(_("All these items have already been invoiced."), raise_exception=True) - + def update_item(source_doc, target_doc, source_parent): target_doc.qty = source_doc.qty - invoiced_qty_map.get(source_doc.name, 0) - + doc = get_mapped_doc("Delivery Note", source_name, { "Delivery Note": { - "doctype": "Sales Invoice", + "doctype": "Sales Invoice", "validation": { "docstatus": ["=", 1] } - }, + }, "Delivery Note Item": { - "doctype": "Sales Invoice Item", + "doctype": "Sales Invoice Item", "field_map": { - "name": "dn_detail", - "parent": "delivery_note", - "prevdoc_detail_docname": "so_detail", - "against_sales_order": "sales_order", + "name": "dn_detail", + "parent": "delivery_note", + "prevdoc_detail_docname": "so_detail", + "against_sales_order": "sales_order", "serial_no": "serial_no" }, "postprocess": update_item, - "filter": lambda d: d.qty - invoiced_qty_map.get(d.name, 0)<=0 - }, + "filter": lambda d: d.qty - invoiced_qty_map.get(d.name, 0)<=0 + }, "Sales Taxes and Charges": { - "doctype": "Sales Taxes and Charges", + "doctype": "Sales Taxes and Charges", "add_if_empty": True - }, + }, "Sales Team": { - "doctype": "Sales Team", + "doctype": "Sales Team", "field_map": { "incentives": "incentives" }, "add_if_empty": True } }, target_doc, update_accounts) - - return doc.as_dict() - + + return doc + @frappe.whitelist() def make_installation_note(source_name, target_doc=None): def update_item(obj, target, source_parent): target.qty = flt(obj.qty) - flt(obj.installed_qty) target.serial_no = obj.serial_no - + doclist = get_mapped_doc("Delivery Note", source_name, { "Delivery Note": { - "doctype": "Installation Note", + "doctype": "Installation Note", "validation": { "docstatus": ["=", 1] } - }, + }, "Delivery Note Item": { - "doctype": "Installation Note Item", + "doctype": "Installation Note Item", "field_map": { - "name": "prevdoc_detail_docname", - "parent": "prevdoc_docname", - "parenttype": "prevdoc_doctype", + "name": "prevdoc_detail_docname", + "parent": "prevdoc_docname", + "parenttype": "prevdoc_doctype", }, "postprocess": update_item, "condition": lambda doc: doc.installed_qty < doc.qty } }, target_doc) - return doclist.as_dict() \ No newline at end of file + return doclist diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index bd147840767..3f67cd2fcc4 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -245,7 +245,7 @@ def make_purchase_order(source_name, target_doc=None): } }, target_doc, set_missing_values) - return doclist.as_dict() + return doclist @frappe.whitelist() def make_purchase_order_based_on_supplier(source_name, target_doc=None): @@ -325,7 +325,7 @@ def make_supplier_quotation(source_name, target_doc=None): } }, target_doc, set_missing_values) - return doclist.as_dict() + return doclist @frappe.whitelist() def make_stock_entry(source_name, target_doc=None): @@ -361,4 +361,4 @@ def make_stock_entry(source_name, target_doc=None): } }, target_doc, set_missing_values) - return doclist.as_dict() + return doclist diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 9a137a1bfd0..03250a62633 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -323,4 +323,4 @@ def make_purchase_invoice(source_name, target_doc=None): } }, target_doc, set_missing_values) - return doclist.as_dict() \ No newline at end of file + return doclist \ No newline at end of file diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 9a280339124..b6ffb832274 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -234,6 +234,7 @@ class StockEntry(StockController): def validate_finished_goods(self): """validation: finished good quantity should be same as manufacturing quantity""" + import json for d in self.get('mtn_details'): if d.bom_no and flt(d.transfer_qty) != flt(self.fg_completed_qty): msgprint(_("Row #") + " %s: " % d.idx diff --git a/erpnext/support/doctype/customer_issue/customer_issue.py b/erpnext/support/doctype/customer_issue/customer_issue.py index f82488a9030..7c6e1b256c4 100644 --- a/erpnext/support/doctype/customer_issue/customer_issue.py +++ b/erpnext/support/doctype/customer_issue/customer_issue.py @@ -59,4 +59,4 @@ def make_maintenance_visit(source_name, target_doc=None): } }, target_doc) - return doclist.as_dict() \ No newline at end of file + return doclist \ No newline at end of file diff --git a/erpnext/support/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/support/doctype/maintenance_schedule/maintenance_schedule.py index 7178fa21c3a..afc08e8f066 100644 --- a/erpnext/support/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/support/doctype/maintenance_schedule/maintenance_schedule.py @@ -295,4 +295,4 @@ def make_maintenance_visit(source_name, target_doc=None): } }, target_doc) - return doclist.as_dict() \ No newline at end of file + return doclist \ No newline at end of file