This commit is contained in:
Anand Doshi
2014-04-08 20:10:03 +05:30
parent cd71e1d8ab
commit d29465029d
28 changed files with 1095 additions and 1116 deletions

View File

@@ -6,14 +6,12 @@ import frappe
import frappe.defaults import frappe.defaults
from frappe.utils import add_days, cint, cstr, date_diff, flt, getdate, nowdate, \ from frappe.utils import add_days, cint, cstr, date_diff, flt, getdate, nowdate, \
get_first_day, get_last_day get_first_day, get_last_day, comma_and
from frappe.utils import comma_and
from frappe.model.naming import make_autoname from frappe.model.naming import make_autoname
from frappe import _, msgprint from frappe import _, msgprint
from erpnext.accounts.party import get_party_account, get_due_date 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} month_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, 'Yearly': 12}
@@ -68,24 +66,24 @@ class SalesInvoice(SellingController):
self.validate_c_form() self.validate_c_form()
self.validate_time_logs_are_submitted() self.validate_time_logs_are_submitted()
self.validate_recurring_invoice() 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") "delivery_note_details")
def on_submit(self): def on_submit(self):
if cint(self.update_stock) == 1: if cint(self.update_stock) == 1:
self.update_stock_ledger() self.update_stock_ledger()
else: else:
# Check for Approving Authority # Check for Approving Authority
if not self.recurring_id: 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.company, self.grand_total, self)
self.check_prev_docstatus() self.check_prev_docstatus()
self.update_status_updater_args() self.update_status_updater_args()
self.update_prevdoc_status() self.update_prevdoc_status()
self.update_billing_status_for_zero_amount_refdoc("Sales Order") self.update_billing_status_for_zero_amount_refdoc("Sales Order")
# this sequence because outstanding may get -ve # this sequence because outstanding may get -ve
self.make_gl_entries() self.make_gl_entries()
self.check_credit_limit(self.debit_to) self.check_credit_limit(self.debit_to)
@@ -103,18 +101,18 @@ class SalesInvoice(SellingController):
def on_cancel(self): def on_cancel(self):
if cint(self.update_stock) == 1: if cint(self.update_stock) == 1:
self.update_stock_ledger() self.update_stock_ledger()
self.check_stop_sales_order("sales_order") self.check_stop_sales_order("sales_order")
from erpnext.accounts.utils import remove_against_link_from_jv from erpnext.accounts.utils import remove_against_link_from_jv
remove_against_link_from_jv(self.doctype, self.name, "against_invoice") remove_against_link_from_jv(self.doctype, self.name, "against_invoice")
self.update_status_updater_args() self.update_status_updater_args()
self.update_prevdoc_status() self.update_prevdoc_status()
self.update_billing_status_for_zero_amount_refdoc("Sales Order") self.update_billing_status_for_zero_amount_refdoc("Sales Order")
self.make_cancel_gl_entries() self.make_cancel_gl_entries()
def update_status_updater_args(self): def update_status_updater_args(self):
if cint(self.update_stock): if cint(self.update_stock):
self.status_updater.append({ self.status_updater.append({
@@ -133,31 +131,31 @@ class SalesInvoice(SellingController):
'second_source_field': 'qty', 'second_source_field': 'qty',
'second_join_field': 'prevdoc_detail_docname' 'second_join_field': 'prevdoc_detail_docname'
}) })
def on_update_after_submit(self): def on_update_after_submit(self):
self.validate_recurring_invoice() self.validate_recurring_invoice()
self.convert_to_recurring() self.convert_to_recurring()
def get_portal_page(self): def get_portal_page(self):
return "invoice" if self.docstatus==1 else None return "invoice" if self.docstatus==1 else None
def set_missing_values(self, for_validate=False): def set_missing_values(self, for_validate=False):
self.set_pos_fields(for_validate) self.set_pos_fields(for_validate)
if not self.debit_to: if not self.debit_to:
self.debit_to = get_party_account(self.company, self.customer, "Customer") self.debit_to = get_party_account(self.company, self.customer, "Customer")
if not self.due_date: if not self.due_date:
self.due_date = get_due_date(self.posting_date, self.customer, "Customer", self.due_date = get_due_date(self.posting_date, self.customer, "Customer",
self.debit_to, self.company) self.debit_to, self.company)
super(SalesInvoice, self).set_missing_values(for_validate) super(SalesInvoice, self).set_missing_values(for_validate)
def update_time_log_batch(self, sales_invoice): def update_time_log_batch(self, sales_invoice):
for d in self.get(self.fname): for d in self.get(self.fname):
if d.time_log_batch: if d.time_log_batch:
tlb = frappe.get_doc("Time Log Batch", d.time_log_batch) tlb = frappe.get_doc("Time Log Batch", d.time_log_batch)
tlb.sales_invoice = sales_invoice tlb.sales_invoice = sales_invoice
tlb.update_after_submit() tlb.save()
def validate_time_logs_are_submitted(self): def validate_time_logs_are_submitted(self):
for d in self.get(self.fname): for d in self.get(self.fname):
@@ -171,10 +169,10 @@ class SalesInvoice(SellingController):
"""Set retail related fields from pos settings""" """Set retail related fields from pos settings"""
if cint(self.is_pos) != 1: if cint(self.is_pos) != 1:
return 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) pos = get_pos_settings(self.company)
if pos: if pos:
if not for_validate and not self.customer: if not for_validate and not self.customer:
self.customer = pos.customer self.customer = pos.customer
@@ -184,31 +182,31 @@ class SalesInvoice(SellingController):
'selling_price_list', 'company', 'select_print_heading', 'cash_bank_account'): 'selling_price_list', 'company', 'select_print_heading', 'cash_bank_account'):
if (not for_validate) or (for_validate and not self.get(fieldname)): if (not for_validate) or (for_validate and not self.get(fieldname)):
self.set(fieldname, pos.get(fieldname)) self.set(fieldname, pos.get(fieldname))
if not for_validate: if not for_validate:
self.update_stock = cint(pos.get("update_stock")) self.update_stock = cint(pos.get("update_stock"))
# set pos values in items # set pos values in items
for item in self.get("entries"): for item in self.get("entries"):
if item.get('item_code'): 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(): frappe._dict(item.as_dict()), pos).items():
if (not for_validate) or (for_validate and not item.get(fname)): if (not for_validate) or (for_validate and not item.get(fname)):
item.set(fname, val) item.set(fname, val)
# fetch terms # fetch terms
if self.tc_name and not self.terms: if self.tc_name and not self.terms:
self.terms = frappe.db.get_value("Terms and Conditions", self.tc_name, "terms") self.terms = frappe.db.get_value("Terms and Conditions", self.tc_name, "terms")
# fetch charges # fetch charges
if self.taxes_and_charges and not len(self.get("other_charges")): if self.taxes_and_charges and not len(self.get("other_charges")):
self.set_taxes("other_charges", "taxes_and_charges") self.set_taxes("other_charges", "taxes_and_charges")
def get_advances(self): 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") "Sales Invoice Advance", "advance_adjustment_details", "credit")
def get_company_abbr(self): def get_company_abbr(self):
return frappe.db.sql("select abbr from tabCompany where name=%s", self.company)[0][0] 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 2. split into multiple rows if partially adjusted, assign against voucher
3. submit advance voucher 3. submit advance voucher
""" """
lst = [] lst = []
for d in self.get('advance_adjustment_details'): for d in self.get('advance_adjustment_details'):
if flt(d.allocated_amount) > 0: if flt(d.allocated_amount) > 0:
args = { args = {
'voucher_no' : d.journal_voucher, 'voucher_no' : d.journal_voucher,
'voucher_detail_no' : d.jv_detail_no, 'voucher_detail_no' : d.jv_detail_no,
'against_voucher_type' : 'Sales Invoice', 'against_voucher_type' : 'Sales Invoice',
'against_voucher' : self.name, 'against_voucher' : self.name,
'account' : self.debit_to, 'account' : self.debit_to,
'is_advance' : 'Yes', 'is_advance' : 'Yes',
'dr_or_cr' : 'credit', 'dr_or_cr' : 'credit',
'unadjusted_amt' : flt(d.advance_amount), 'unadjusted_amt' : flt(d.advance_amount),
'allocated_amt' : flt(d.allocated_amount) 'allocated_amt' : flt(d.allocated_amount)
} }
lst.append(args) lst.append(args)
if lst: if lst:
from erpnext.accounts.utils import reconcile_against_document from erpnext.accounts.utils import reconcile_against_document
reconcile_against_document(lst) reconcile_against_document(lst)
def validate_customer_account(self): def validate_customer_account(self):
"""Validates Debit To Account and Customer Matches""" """Validates Debit To Account and Customer Matches"""
if self.customer and self.debit_to and not cint(self.is_pos): 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) 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 \ 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())): (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 \ 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): def validate_debit_acc(self):
if frappe.db.get_value("Account", self.debit_to, "report_type") != "Balance Sheet": if frappe.db.get_value("Account", self.debit_to, "report_type") != "Balance Sheet":
frappe.throw(_("Account must be a balance sheet account")) frappe.throw(_("Account must be a balance sheet account"))
def validate_fixed_asset_account(self): def validate_fixed_asset_account(self):
"""Validate Fixed Asset and whether Income Account Entered Exists""" """Validate Fixed Asset and whether Income Account Entered Exists"""
for d in self.get('entries'): for d in self.get('entries'):
item = frappe.db.sql("""select name,is_asset_item,is_sales_item from `tabItem` 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' where name = %s and (ifnull(end_of_life,'')='' or end_of_life = '0000-00-00'
or end_of_life > now())""", d.item_code) 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) where name = %s and docstatus != 2""", d.income_account)
if not acc: if not acc:
msgprint("Account: "+d.income_account+" does not exist in the system", raise_exception=True) 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': 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): def validate_with_previous_doc(self):
super(SalesInvoice, self).validate_with_previous_doc(self.tname, { super(SalesInvoice, self).validate_with_previous_doc(self.tname, {
"Sales Order": { "Sales Order": {
@@ -281,7 +279,7 @@ class SalesInvoice(SellingController):
["currency", "="]], ["currency", "="]],
}, },
}) })
if cint(frappe.defaults.get_global_default('maintain_same_sales_rate')): if cint(frappe.defaults.get_global_default('maintain_same_sales_rate')):
super(SalesInvoice, self).validate_with_previous_doc(self.tname, { super(SalesInvoice, self).validate_with_previous_doc(self.tname, {
"Sales Order Item": { "Sales Order Item": {
@@ -296,7 +294,7 @@ class SalesInvoice(SellingController):
"is_child_table": True "is_child_table": True
} }
}) })
def set_aging_date(self): def set_aging_date(self):
if self.is_opening != 'Yes': if self.is_opening != 'Yes':
@@ -304,7 +302,7 @@ class SalesInvoice(SellingController):
elif not self.aging_date: elif not self.aging_date:
msgprint("Aging Date is mandatory for opening entry") msgprint("Aging Date is mandatory for opening entry")
raise Exception raise Exception
def set_against_income_account(self): def set_against_income_account(self):
"""Set against account for debit to account""" """Set against account for debit to account"""
@@ -333,8 +331,8 @@ class SalesInvoice(SellingController):
def validate_proj_cust(self): def validate_proj_cust(self):
"""check for does customer belong to same project as entered..""" """check for does customer belong to same project as entered.."""
if self.project_name and self.customer: if self.project_name and self.customer:
res = frappe.db.sql("""select name from `tabProject` res = frappe.db.sql("""select name from `tabProject`
where name = %s and (customer = %s or where name = %s and (customer = %s or
ifnull(customer,'')='')""", (self.project_name, self.customer)) ifnull(customer,'')='')""", (self.project_name, self.customer))
if not res: 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)) 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: 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), 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) raise_exception=True)
def validate_delivery_note(self): def validate_delivery_note(self):
for d in self.get("entries"): for d in self.get("entries"):
if d.delivery_note: if d.delivery_note:
@@ -374,7 +372,7 @@ class SalesInvoice(SellingController):
and parent = %s""", (self.amended_from, self.c_form_no)) and parent = %s""", (self.amended_from, self.c_form_no))
frappe.db.set(self, 'c_form_no', '') frappe.db.set(self, 'c_form_no', '')
def update_current_stock(self): def update_current_stock(self):
for d in self.get('entries'): for d in self.get('entries'):
if d.item_code and d.warehouse: 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) 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.actual_qty = bin and flt(bin[0]['actual_qty']) or 0
d.projected_qty = bin and flt(bin[0]['projected_qty']) or 0 d.projected_qty = bin and flt(bin[0]['projected_qty']) or 0
def get_warehouse(self): def get_warehouse(self):
w = frappe.db.sql("""select warehouse from `tabPOS Setting` w = frappe.db.sql("""select warehouse from `tabPOS Setting`
where ifnull(user,'') = %s and company = %s""", where ifnull(user,'') = %s and company = %s""",
(frappe.session['user'], self.company)) (frappe.session['user'], self.company))
w = w and w[0][0] or '' w = w and w[0][0] or ''
if not w: 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) where ifnull(user,'') = '' and company = %s""", self.company)
if not ps: if not ps:
msgprint("To make POS entry, please create POS Setting from Accounts --> POS Setting page and refresh the system.", raise_exception=True) 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') make_packing_list(self, 'entries')
else: else:
self.set('packing_details', []) self.set('packing_details', [])
if cint(self.is_pos) == 1: if cint(self.is_pos) == 1:
if flt(self.paid_amount) == 0: if flt(self.paid_amount) == 0:
if self.cash_bank_account: if self.cash_bank_account:
frappe.db.set(self, 'paid_amount', frappe.db.set(self, 'paid_amount',
(flt(self.grand_total) - flt(self.write_off_amount))) (flt(self.grand_total) - flt(self.write_off_amount)))
else: else:
# show message that the amount is not paid # 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.") frappe.msgprint("Note: Payment Entry will not be created since 'Cash/Bank Account' was not specified.")
else: else:
frappe.db.set(self,'paid_amount',0) frappe.db.set(self,'paid_amount',0)
def check_prev_docstatus(self): def check_prev_docstatus(self):
for d in self.get('entries'): for d in self.get('entries'):
if d.sales_order: 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) where docstatus = 1 and name = %s""", d.sales_order)
if not submitted: if not submitted:
msgprint("Sales Order : "+ cstr(d.sales_order) +" is not submitted") msgprint("Sales Order : "+ cstr(d.sales_order) +" is not submitted")
raise Exception , "Validation Error." raise Exception , "Validation Error."
if d.delivery_note: 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) where docstatus = 1 and name = %s""", d.delivery_note)
if not submitted: if not submitted:
msgprint("Delivery Note : "+ cstr(d.delivery_note) +" is 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), "actual_qty": -1*flt(d.qty),
"stock_uom": frappe.db.get_value("Item", d.item_code, "stock_uom") "stock_uom": frappe.db.get_value("Item", d.item_code, "stock_uom")
})) }))
self.make_sl_entries(sl_entries) self.make_sl_entries(sl_entries)
def make_gl_entries(self, repost_future_gle=True): def make_gl_entries(self, repost_future_gle=True):
gl_entries = self.get_gl_entries() gl_entries = self.get_gl_entries()
if gl_entries: if gl_entries:
from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.general_ledger import make_gl_entries
update_outstanding = cint(self.is_pos) and self.write_off_account \ update_outstanding = cint(self.is_pos) and self.write_off_account \
and 'No' or 'Yes' 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) update_outstanding=update_outstanding, merge_entries=False)
if repost_future_gle and cint(self.update_stock) \ if repost_future_gle and cint(self.update_stock) \
and cint(frappe.defaults.get_global_default("auto_accounting_for_stock")): and cint(frappe.defaults.get_global_default("auto_accounting_for_stock")):
items, warehouse_account = self.get_items_and_warehouse_accounts() 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) warehouse_account, items)
def get_gl_entries(self, warehouse_account=None): def get_gl_entries(self, warehouse_account=None):
from erpnext.accounts.general_ledger import merge_similar_entries from erpnext.accounts.general_ledger import merge_similar_entries
gl_entries = [] gl_entries = []
self.make_customer_gl_entry(gl_entries) self.make_customer_gl_entry(gl_entries)
self.make_tax_gl_entries(gl_entries) self.make_tax_gl_entries(gl_entries)
self.make_item_gl_entries(gl_entries) self.make_item_gl_entries(gl_entries)
# merge gl entries before adding pos entries # merge gl entries before adding pos entries
gl_entries = merge_similar_entries(gl_entries) gl_entries = merge_similar_entries(gl_entries)
self.make_pos_gl_entries(gl_entries) self.make_pos_gl_entries(gl_entries)
return gl_entries return gl_entries
def make_customer_gl_entry(self, gl_entries): def make_customer_gl_entry(self, gl_entries):
if self.grand_total: if self.grand_total:
gl_entries.append( gl_entries.append(
@@ -506,7 +503,7 @@ class SalesInvoice(SellingController):
"against_voucher_type": self.doctype, "against_voucher_type": self.doctype,
}) })
) )
def make_tax_gl_entries(self, gl_entries): def make_tax_gl_entries(self, gl_entries):
for tax in self.get("other_charges"): for tax in self.get("other_charges"):
if flt(tax.tax_amount_after_discount_amount): if flt(tax.tax_amount_after_discount_amount):
@@ -519,9 +516,9 @@ class SalesInvoice(SellingController):
"cost_center": tax.cost_center "cost_center": tax.cost_center
}) })
) )
def make_item_gl_entries(self, gl_entries): def make_item_gl_entries(self, gl_entries):
# income account gl entries # income account gl entries
for item in self.get("entries"): for item in self.get("entries"):
if flt(item.base_amount): if flt(item.base_amount):
gl_entries.append( gl_entries.append(
@@ -533,12 +530,12 @@ class SalesInvoice(SellingController):
"cost_center": item.cost_center "cost_center": item.cost_center
}) })
) )
# expense account gl entries # expense account gl entries
if cint(frappe.defaults.get_global_default("auto_accounting_for_stock")) \ if cint(frappe.defaults.get_global_default("auto_accounting_for_stock")) \
and cint(self.update_stock): and cint(self.update_stock):
gl_entries += super(SalesInvoice, self).get_gl_entries() gl_entries += super(SalesInvoice, self).get_gl_entries()
def make_pos_gl_entries(self, gl_entries): def make_pos_gl_entries(self, gl_entries):
if cint(self.is_pos) and self.cash_bank_account and self.paid_amount: if cint(self.is_pos) and self.cash_bank_account and self.paid_amount:
# POS, make payment entries # POS, make payment entries
@@ -581,81 +578,81 @@ class SalesInvoice(SellingController):
"cost_center": self.write_off_cost_center "cost_center": self.write_off_cost_center
}) })
) )
def update_c_form(self): def update_c_form(self):
"""Update amended id in C-form""" """Update amended id in C-form"""
if self.c_form_no and self.amended_from: if self.c_form_no and self.amended_from:
frappe.db.sql("""update `tabC-Form Invoice Detail` set invoice_no = %s, frappe.db.sql("""update `tabC-Form Invoice Detail` set invoice_no = %s,
invoice_date = %s, territory = %s, net_total = %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)) (self.name, self.amended_from, self.c_form_no))
def validate_recurring_invoice(self): def validate_recurring_invoice(self):
if self.convert_into_recurring_invoice: if self.convert_into_recurring_invoice:
self.validate_notification_email_id() self.validate_notification_email_id()
if not self.recurring_type: if not self.recurring_type:
msgprint(_("Please select: ") + self.meta.get_label("recurring_type"), msgprint(_("Please select: ") + self.meta.get_label("recurring_type"),
raise_exception=1) raise_exception=1)
elif not (self.invoice_period_from_date and \ elif not (self.invoice_period_from_date and \
self.invoice_period_to_date): self.invoice_period_to_date):
msgprint(comma_and([self.meta.get_label("invoice_period_from_date"), msgprint(comma_and([self.meta.get_label("invoice_period_from_date"),
self.meta.get_label("invoice_period_to_date")]) self.meta.get_label("invoice_period_to_date")])
+ _(": Mandatory for a Recurring Invoice."), + _(": Mandatory for a Recurring Invoice."),
raise_exception=True) raise_exception=True)
def convert_to_recurring(self): def convert_to_recurring(self):
if self.convert_into_recurring_invoice: if self.convert_into_recurring_invoice:
if not self.recurring_id: if not self.recurring_id:
frappe.db.set(self, "recurring_id", frappe.db.set(self, "recurring_id",
make_autoname("RECINV/.#####")) make_autoname("RECINV/.#####"))
self.set_next_date() self.set_next_date()
elif self.recurring_id: elif self.recurring_id:
frappe.db.sql("""update `tabSales Invoice` frappe.db.sql("""update `tabSales Invoice`
set convert_into_recurring_invoice = 0 set convert_into_recurring_invoice = 0
where recurring_id = %s""", (self.recurring_id,)) where recurring_id = %s""", (self.recurring_id,))
def validate_notification_email_id(self): def validate_notification_email_id(self):
if self.notification_email_address: if self.notification_email_address:
email_list = filter(None, [cstr(email).strip() for email in email_list = filter(None, [cstr(email).strip() for email in
self.notification_email_address.replace("\n", "").split(",")]) self.notification_email_address.replace("\n", "").split(",")])
from frappe.utils import validate_email_add from frappe.utils import validate_email_add
for email in email_list: for email in email_list:
if not validate_email_add(email): if not validate_email_add(email):
msgprint(self.meta.get_label("notification_email_address") \ msgprint(self.meta.get_label("notification_email_address") \
+ " - " + _("Invalid Email Address") + ": \"%s\"" % email, + " - " + _("Invalid Email Address") + ": \"%s\"" % email,
raise_exception=1) raise_exception=1)
else: else:
msgprint("Notification Email Addresses not specified for recurring invoice", msgprint("Notification Email Addresses not specified for recurring invoice",
raise_exception=1) raise_exception=1)
def set_next_date(self): def set_next_date(self):
""" Set next date on which auto invoice will be created""" """ Set next date on which auto invoice will be created"""
if not self.repeat_on_day_of_month: if not self.repeat_on_day_of_month:
msgprint("""Please enter 'Repeat on Day of Month' field value. msgprint("""Please enter 'Repeat on Day of Month' field value.
The day of the month on which auto invoice The day of the month on which auto invoice
will be generated e.g. 05, 28 etc.""", raise_exception=1) will be generated e.g. 05, 28 etc.""", raise_exception=1)
next_date = get_next_date(self.posting_date, next_date = get_next_date(self.posting_date,
month_map[self.recurring_type], cint(self.repeat_on_day_of_month)) month_map[self.recurring_type], cint(self.repeat_on_day_of_month))
frappe.db.set(self, 'next_date', next_date) frappe.db.set(self, 'next_date', next_date)
def get_next_date(dt, mcount, day=None): def get_next_date(dt, mcount, day=None):
dt = getdate(dt) dt = getdate(dt)
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
dt += relativedelta(months=mcount, day=day) dt += relativedelta(months=mcount, day=day)
return dt return dt
def manage_recurring_invoices(next_date=None, commit=True): def manage_recurring_invoices(next_date=None, commit=True):
""" """
Create recurring invoices on specific date by copying the original one Create recurring invoices on specific date by copying the original one
and notify the concerned people 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 from `tabSales Invoice` where ifnull(convert_into_recurring_invoice, 0)=1
and docstatus=1 and next_date=%s and docstatus=1 and next_date=%s
and next_date <= ifnull(end_date, '2199-12-31')""", next_date) and next_date <= ifnull(end_date, '2199-12-31')""", next_date)
exception_list = [] exception_list = []
for ref_invoice, recurring_id in recurring_invoices: for ref_invoice, recurring_id in recurring_invoices:
if not frappe.db.sql("""select name from `tabSales Invoice` if not frappe.db.sql("""select name from `tabSales Invoice`
@@ -690,21 +687,20 @@ def manage_recurring_invoices(next_date=None, commit=True):
finally: finally:
if commit: if commit:
frappe.db.begin() frappe.db.begin()
if exception_list: if exception_list:
exception_message = "\n\n".join([cstr(d) for d in exception_list]) exception_message = "\n\n".join([cstr(d) for d in exception_list])
raise Exception, exception_message raise Exception, exception_message
def make_new_invoice(ref_wrapper, posting_date): def make_new_invoice(ref_wrapper, posting_date):
from frappe.model.doc import clone
from erpnext.accounts.utils import get_fiscal_year 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] mcount = month_map[ref_wrapper.recurring_type]
invoice_period_from_date = get_next_date(ref_wrapper.invoice_period_from_date, mcount) 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 # and to date is the last day of its own month
if (cstr(get_first_day(ref_wrapper.invoice_period_from_date)) == \ if (cstr(get_first_day(ref_wrapper.invoice_period_from_date)) == \
cstr(ref_wrapper.invoice_period_from_date)) and \ cstr(ref_wrapper.invoice_period_from_date)) and \
@@ -714,7 +710,7 @@ def make_new_invoice(ref_wrapper, posting_date):
mcount)) mcount))
else: else:
invoice_period_to_date = get_next_date(ref_wrapper.invoice_period_to_date, mcount) invoice_period_to_date = get_next_date(ref_wrapper.invoice_period_to_date, mcount)
new_invoice.update({ new_invoice.update({
"posting_date": posting_date, "posting_date": posting_date,
"aging_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], "fiscal_year": get_fiscal_year(posting_date)[0],
"owner": ref_wrapper.owner, "owner": ref_wrapper.owner,
}) })
new_invoice.submit() new_invoice.submit()
return new_invoice return new_invoice
def send_notification(new_rv): def send_notification(new_rv):
"""Notify concerned persons about recurring invoice generation""" """Notify concerned persons about recurring invoice generation"""
from frappe.core.doctype.print_format.print_format import get_html from frappe.core.doctype.print_format.print_format import get_html
frappe.sendmail(new_rv.notification_email_address, frappe.sendmail(new_rv.notification_email_address,
subject="New Invoice : " + new_rv.name, subject="New Invoice : " + new_rv.name,
message = get_html(new_rv, new_rv, "SalesInvoice")) message = get_html(new_rv, new_rv, "SalesInvoice"))
def notify_errors(inv, customer, owner): def notify_errors(inv, customer, owner):
from frappe.utils.user import get_system_managers from frappe.utils.user import get_system_managers
recipients=get_system_managers() recipients=get_system_managers()
frappe.sendmail(recipients + [frappe.db.get_value("User", owner, "email")], frappe.sendmail(recipients + [frappe.db.get_value("User", owner, "email")],
subject="[Urgent] Error while creating recurring invoice for %s" % inv, subject="[Urgent] Error while creating recurring invoice for %s" % inv,
message = frappe.get_template("template/emails/recurring_invoice_failed.html").render({ message = frappe.get_template("template/emails/recurring_invoice_failed.html").render({
"name": inv, "name": inv,
"customer": customer "customer": customer
})) }))
assign_task_to_owner(inv, "Recurring Invoice Failed", recipients) assign_task_to_owner(inv, "Recurring Invoice Failed", recipients)
def assign_task_to_owner(inv, msg, users): 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): def get_income_account(doctype, txt, searchfield, start, page_len, filters):
from erpnext.controllers.queries import get_match_cond from erpnext.controllers.queries import get_match_cond
# income account can be any Credit account, # income account can be any Credit account,
# but can also be a Asset account with account_type='Income Account' in special circumstances. # but can also be a Asset account with account_type='Income Account' in special circumstances.
# Hence the first condition is an "OR" # 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" where (tabAccount.report_type = "Profit and Loss"
or tabAccount.account_type = "Income Account") or tabAccount.account_type = "Income Account")
and tabAccount.group_or_ledger="Ledger" and tabAccount.group_or_ledger="Ledger"
and tabAccount.docstatus!=2 and tabAccount.docstatus!=2
and ifnull(tabAccount.master_type, "")="" and ifnull(tabAccount.master_type, "")=""
and ifnull(tabAccount.master_name, "")="" and ifnull(tabAccount.master_name, "")=""
and tabAccount.company = '%(company)s' and tabAccount.company = '%(company)s'
and tabAccount.%(key)s LIKE '%(txt)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)}) 'txt': "%%%s%%" % txt, 'mcond':get_match_cond(doctype)})
@frappe.whitelist() @frappe.whitelist()
def make_delivery_note(source_name, target_doc=None): def make_delivery_note(source_name, target_doc=None):
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
def set_missing_values(source, target): def set_missing_values(source, target):
doc = frappe.get_doc(target) doc = frappe.get_doc(target)
doc.run_method("onload_post_render") doc.run_method("onload_post_render")
def update_item(source_doc, target_doc, source_parent): def update_item(source_doc, target_doc, source_parent):
target_doc.base_amount = (flt(source_doc.qty) - flt(source_doc.delivered_qty)) * \ target_doc.base_amount = (flt(source_doc.qty) - flt(source_doc.delivered_qty)) * \
flt(source_doc.base_rate) flt(source_doc.base_rate)
target_doc.amount = (flt(source_doc.qty) - flt(source_doc.delivered_qty)) * \ target_doc.amount = (flt(source_doc.qty) - flt(source_doc.delivered_qty)) * \
flt(source_doc.rate) flt(source_doc.rate)
target_doc.qty = flt(source_doc.qty) - flt(source_doc.delivered_qty) target_doc.qty = flt(source_doc.qty) - flt(source_doc.delivered_qty)
doclist = get_mapped_doc("Sales Invoice", source_name, { doclist = get_mapped_doc("Sales Invoice", source_name, {
"Sales Invoice": { "Sales Invoice": {
"doctype": "Delivery Note", "doctype": "Delivery Note",
"validation": { "validation": {
"docstatus": ["=", 1] "docstatus": ["=", 1]
} }
}, },
"Sales Invoice Item": { "Sales Invoice Item": {
"doctype": "Delivery Note Item", "doctype": "Delivery Note Item",
"field_map": { "field_map": {
"name": "prevdoc_detail_docname", "name": "prevdoc_detail_docname",
"parent": "against_sales_invoice", "parent": "against_sales_invoice",
"serial_no": "serial_no" "serial_no": "serial_no"
}, },
"postprocess": update_item "postprocess": update_item
}, },
"Sales Taxes and Charges": { "Sales Taxes and Charges": {
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"add_if_empty": True "add_if_empty": True
}, },
"Sales Team": { "Sales Team": {
"doctype": "Sales Team", "doctype": "Sales Team",
"field_map": { "field_map": {
"incentives": "incentives" "incentives": "incentives"
}, },
"add_if_empty": True "add_if_empty": True
} }
}, target_doc, set_missing_values) }, target_doc, set_missing_values)
return doclist.as_dict() return doclist

View File

@@ -2,7 +2,7 @@
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
import frappe import frappe
import unittest, json import unittest, json, copy
from frappe.utils import flt from frappe.utils import flt
from erpnext.accounts.utils import get_stock_and_account_difference from erpnext.accounts.utils import get_stock_and_account_difference
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
@@ -14,57 +14,46 @@ class TestSalesInvoice(unittest.TestCase):
w.insert() w.insert()
w.submit() w.submit()
return w 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): def test_timestamp_change(self):
w = frappe.copy_doc(test_records[0]) w = frappe.copy_doc(test_records[0])
w.docstatus = '0' w.docstatus = 0
w.insert() w.insert()
w2 = frappe.copy_doc(w) w2 = frappe.get_doc(w.doctype, w.name)
import time import time
time.sleep(1) time.sleep(1)
w.save() w.save()
import time import time
time.sleep(1) time.sleep(1)
self.assertRaises(frappe.TimestampMismatchError, w2.save) self.assertRaises(frappe.TimestampMismatchError, w2.save)
def test_sales_invoice_calculation_base_currency(self): def test_sales_invoice_calculation_base_currency(self):
si = frappe.copy_doc(test_records[2]) si = frappe.copy_doc(test_records[2])
si.insert() si.insert()
expected_values = { expected_values = {
"keys": ["price_list_rate", "discount_percentage", "rate", "amount", "keys": ["price_list_rate", "discount_percentage", "rate", "amount",
"base_price_list_rate", "base_rate", "base_amount"], "base_price_list_rate", "base_rate", "base_amount"],
"_Test Item Home Desktop 100": [50, 0, 50, 500, 50, 50, 500], "_Test Item Home Desktop 100": [50, 0, 50, 500, 50, 50, 500],
"_Test Item Home Desktop 200": [150, 0, 150, 750, 150, 150, 750], "_Test Item Home Desktop 200": [150, 0, 150, 750, 150, 150, 750],
} }
# check if children are saved # check if children are saved
self.assertEquals(len(si.get("entries")), self.assertEquals(len(si.get("entries")),
len(expected_values)-1) len(expected_values)-1)
# check if item values are calculated # check if item values are calculated
for d in si.get("entries"): for d in si.get("entries"):
for i, k in enumerate(expected_values["keys"]): for i, k in enumerate(expected_values["keys"]):
self.assertEquals(d.get(k), expected_values[d.item_code][i]) self.assertEquals(d.get(k), expected_values[d.item_code][i])
# check net total # check net total
self.assertEquals(si.net_total, 1250) self.assertEquals(si.net_total, 1250)
self.assertEquals(si.net_total_export, 1250) self.assertEquals(si.net_total_export, 1250)
# check tax calculation # check tax calculation
expected_values = { expected_values = {
"keys": ["tax_amount", "total"], "keys": ["tax_amount", "total"],
@@ -77,14 +66,14 @@ class TestSalesInvoice(unittest.TestCase):
"_Test Account VAT - _TC": [156.25, 1807.83], "_Test Account VAT - _TC": [156.25, 1807.83],
"_Test Account Discount - _TC": [-180.78, 1627.05] "_Test Account Discount - _TC": [-180.78, 1627.05]
} }
for d in si.get("other_charges"): for d in si.get("other_charges"):
for i, k in enumerate(expected_values["keys"]): for i, k in enumerate(expected_values["keys"]):
self.assertEquals(d.get(k), expected_values[d.account_head][i]) self.assertEquals(d.get(k), expected_values[d.account_head][i])
self.assertEquals(si.grand_total, 1627.05) self.assertEquals(si.grand_total, 1627.05)
self.assertEquals(si.grand_total_export, 1627.05) self.assertEquals(si.grand_total_export, 1627.05)
def test_sales_invoice_calculation_export_currency(self): def test_sales_invoice_calculation_export_currency(self):
si = frappe.copy_doc(test_records[2]) si = frappe.copy_doc(test_records[2])
si.currency = "USD" si.currency = "USD"
@@ -94,27 +83,27 @@ class TestSalesInvoice(unittest.TestCase):
si.get("entries")[1].rate = 3 si.get("entries")[1].rate = 3
si.get("entries")[1].price_list_rate = 3 si.get("entries")[1].price_list_rate = 3
si.insert() si.insert()
expected_values = { expected_values = {
"keys": ["price_list_rate", "discount_percentage", "rate", "amount", "keys": ["price_list_rate", "discount_percentage", "rate", "amount",
"base_price_list_rate", "base_rate", "base_amount"], "base_price_list_rate", "base_rate", "base_amount"],
"_Test Item Home Desktop 100": [1, 0, 1, 10, 50, 50, 500], "_Test Item Home Desktop 100": [1, 0, 1, 10, 50, 50, 500],
"_Test Item Home Desktop 200": [3, 0, 3, 15, 150, 150, 750], "_Test Item Home Desktop 200": [3, 0, 3, 15, 150, 150, 750],
} }
# check if children are saved # check if children are saved
self.assertEquals(len(si.get("entries")), self.assertEquals(len(si.get("entries")),
len(expected_values)-1) len(expected_values)-1)
# check if item values are calculated # check if item values are calculated
for d in si.get("entries"): for d in si.get("entries"):
for i, k in enumerate(expected_values["keys"]): for i, k in enumerate(expected_values["keys"]):
self.assertEquals(d.get(k), expected_values[d.item_code][i]) self.assertEquals(d.get(k), expected_values[d.item_code][i])
# check net total # check net total
self.assertEquals(si.net_total, 1250) self.assertEquals(si.net_total, 1250)
self.assertEquals(si.net_total_export, 25) self.assertEquals(si.net_total_export, 25)
# check tax calculation # check tax calculation
expected_values = { expected_values = {
"keys": ["tax_amount", "total"], "keys": ["tax_amount", "total"],
@@ -127,11 +116,11 @@ class TestSalesInvoice(unittest.TestCase):
"_Test Account VAT - _TC": [156.25, 1807.83], "_Test Account VAT - _TC": [156.25, 1807.83],
"_Test Account Discount - _TC": [-180.78, 1627.05] "_Test Account Discount - _TC": [-180.78, 1627.05]
} }
for d in si.get("other_charges"): for d in si.get("other_charges"):
for i, k in enumerate(expected_values["keys"]): for i, k in enumerate(expected_values["keys"]):
self.assertEquals(d.get(k), expected_values[d.account_head][i]) self.assertEquals(d.get(k), expected_values[d.account_head][i])
self.assertEquals(si.grand_total, 1627.05) self.assertEquals(si.grand_total, 1627.05)
self.assertEquals(si.grand_total_export, 32.54) self.assertEquals(si.grand_total_export, 32.54)
@@ -148,27 +137,27 @@ class TestSalesInvoice(unittest.TestCase):
"row_id": 8, "row_id": 8,
}) })
si.insert() si.insert()
expected_values = { expected_values = {
"keys": ["price_list_rate", "discount_percentage", "rate", "amount", "keys": ["price_list_rate", "discount_percentage", "rate", "amount",
"base_price_list_rate", "base_rate", "base_amount"], "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 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], "_Test Item Home Desktop 200": [190.66, 0, 190.66, 953.3, 150, 150, 698.08],
} }
# check if children are saved # check if children are saved
self.assertEquals(len(si.get("entries")), self.assertEquals(len(si.get("entries")),
len(expected_values)-1) len(expected_values)-1)
# check if item values are calculated # check if item values are calculated
for d in si.get("entries"): for d in si.get("entries"):
for i, k in enumerate(expected_values["keys"]): for i, k in enumerate(expected_values["keys"]):
self.assertEquals(d.get(k), expected_values[d.item_code][i]) self.assertEquals(d.get(k), expected_values[d.item_code][i])
# check net total # check net total
self.assertEquals(si.net_total, 1163.45) self.assertEquals(si.net_total, 1163.45)
self.assertEquals(si.net_total_export, 1578.3) self.assertEquals(si.net_total_export, 1578.3)
# check tax calculation # check tax calculation
expected_values = { expected_values = {
"keys": ["tax_amount", "tax_amount_after_discount_amount", "total"], "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 Discount - _TC": [-180.33, -168.54, 1516.88],
"_Test Account Service Tax - _TC": [-18.03, -16.88, 1500] "_Test Account Service Tax - _TC": [-18.03, -16.88, 1500]
} }
for d in si.get("other_charges"): for d in si.get("other_charges"):
for i, k in enumerate(expected_values["keys"]): for i, k in enumerate(expected_values["keys"]):
self.assertEquals(d.get(k), expected_values[d.account_head][i]) self.assertEquals(d.get(k), expected_values[d.account_head][i])
self.assertEquals(si.grand_total, 1500) self.assertEquals(si.grand_total, 1500)
self.assertEquals(si.grand_total_export, 1500) self.assertEquals(si.grand_total_export, 1500)
@@ -213,15 +202,15 @@ class TestSalesInvoice(unittest.TestCase):
expected_values = sorted([ expected_values = sorted([
[si.debit_to, 1500, 0.0], [si.debit_to, 1500, 0.0],
[test_records[3][1]["income_account"], 0.0, 1163.45], [test_records[3]["entries"][0]["income_account"], 0.0, 1163.45],
[test_records[3][3]["account_head"], 0.0, 130.31], [test_records[3]["other_charges"][0]["account_head"], 0.0, 130.31],
[test_records[3][4]["account_head"], 0.0, 2.61], [test_records[3]["other_charges"][1]["account_head"], 0.0, 2.61],
[test_records[3][5]["account_head"], 0.0, 1.31], [test_records[3]["other_charges"][2]["account_head"], 0.0, 1.31],
[test_records[3][6]["account_head"], 0.0, 25.96], [test_records[3]["other_charges"][3]["account_head"], 0.0, 25.96],
[test_records[3][7]["account_head"], 0.0, 145.43], [test_records[3]["other_charges"][4]["account_head"], 0.0, 145.43],
[test_records[3][8]["account_head"], 0.0, 116.35], [test_records[3]["other_charges"][5]["account_head"], 0.0, 116.35],
[test_records[3][9]["account_head"], 0.0, 100], [test_records[3]["other_charges"][6]["account_head"], 0.0, 100],
[test_records[3][10]["account_head"], 168.54, 0.0], [test_records[3]["other_charges"][7]["account_head"], 168.54, 0.0],
["_Test Account Service Tax - _TC", 16.88, 0.0], ["_Test Account Service Tax - _TC", 16.88, 0.0],
]) ])
@@ -233,7 +222,7 @@ class TestSalesInvoice(unittest.TestCase):
# cancel # cancel
si.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) where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)
self.assertFalse(gle) self.assertFalse(gle)
@@ -242,44 +231,44 @@ class TestSalesInvoice(unittest.TestCase):
si = frappe.copy_doc(test_records[2]) si = frappe.copy_doc(test_records[2])
for i, tax in enumerate(si.get("other_charges")): for i, tax in enumerate(si.get("other_charges")):
tax.idx = i+1 tax.idx = i+1
si.get("entries")[0].price_list_rate = 62.5 si.get("entries")[0].price_list_rate = 62.5
si.get("entries")[0].price_list_rate = 191 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 si.get("other_charges")[i].included_in_print_rate = 1
# tax type "Actual" cannot be inclusive # tax type "Actual" cannot be inclusive
self.assertRaises(frappe.ValidationError, si.insert) self.assertRaises(frappe.ValidationError, si.insert)
# taxes above included type 'On Previous Row Total' should also be included # taxes above included type 'On Previous Row Total' should also be included
si.get("other_charges")[0].included_in_print_rate = 0 si.get("other_charges")[0].included_in_print_rate = 0
self.assertRaises(frappe.ValidationError, si.insert) self.assertRaises(frappe.ValidationError, si.insert)
def test_sales_invoice_calculation_base_currency_with_tax_inclusive_price(self): def test_sales_invoice_calculation_base_currency_with_tax_inclusive_price(self):
# prepare # prepare
si = frappe.copy_doc(test_records[3]) si = frappe.copy_doc(test_records[3])
si.insert() si.insert()
expected_values = { expected_values = {
"keys": ["price_list_rate", "discount_percentage", "rate", "amount", "keys": ["price_list_rate", "discount_percentage", "rate", "amount",
"base_price_list_rate", "base_rate", "base_amount"], "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 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], "_Test Item Home Desktop 200": [190.66, 0, 190.66, 953.3, 150, 150, 750],
} }
# check if children are saved # check if children are saved
self.assertEquals(len(si.get("entries")), self.assertEquals(len(si.get("entries")),
len(expected_values)-1) len(expected_values)-1)
# check if item values are calculated # check if item values are calculated
for d in si.get("entries"): for d in si.get("entries"):
for i, k in enumerate(expected_values["keys"]): for i, k in enumerate(expected_values["keys"]):
self.assertEquals(d.get(k), expected_values[d.item_code][i]) self.assertEquals(d.get(k), expected_values[d.item_code][i])
# check net total # check net total
self.assertEquals(si.net_total, 1249.98) self.assertEquals(si.net_total, 1249.98)
self.assertEquals(si.net_total_export, 1578.3) self.assertEquals(si.net_total_export, 1578.3)
# check tax calculation # check tax calculation
expected_values = { expected_values = {
"keys": ["tax_amount", "total"], "keys": ["tax_amount", "total"],
@@ -292,14 +281,14 @@ class TestSalesInvoice(unittest.TestCase):
"_Test Account Shipping Charges - _TC": [100, 1803.31], "_Test Account Shipping Charges - _TC": [100, 1803.31],
"_Test Account Discount - _TC": [-180.33, 1622.98] "_Test Account Discount - _TC": [-180.33, 1622.98]
} }
for d in si.get("other_charges"): for d in si.get("other_charges"):
for i, k in enumerate(expected_values["keys"]): for i, k in enumerate(expected_values["keys"]):
self.assertEquals(d.get(k), expected_values[d.account_head][i]) self.assertEquals(d.get(k), expected_values[d.account_head][i])
self.assertEquals(si.grand_total, 1622.98) self.assertEquals(si.grand_total, 1622.98)
self.assertEquals(si.grand_total_export, 1622.98) self.assertEquals(si.grand_total_export, 1622.98)
def test_sales_invoice_calculation_export_currency_with_tax_inclusive_price(self): def test_sales_invoice_calculation_export_currency_with_tax_inclusive_price(self):
# prepare # prepare
si = frappe.copy_doc(test_records[3]) 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")[0].discount_percentage = 10
si.get("entries")[1].price_list_rate = 187.5 si.get("entries")[1].price_list_rate = 187.5
si.get("entries")[1].discount_percentage = 20 si.get("entries")[1].discount_percentage = 20
si.get("other_charges")[5].rate = 5000 si.get("other_charges")[6].rate = 5000
si.insert() si.insert()
expected_values = { expected_values = {
"keys": ["price_list_rate", "discount_percentage", "rate", "amount", "keys": ["price_list_rate", "discount_percentage", "rate", "amount",
"base_price_list_rate", "base_rate", "base_amount"], "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 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], "_Test Item Home Desktop 200": [187.5, 20, 150, 750, 7375.66, 5900.53, 29502.66],
} }
# check if children are saved # check if children are saved
self.assertEquals(len(si.get("entries")), self.assertEquals(len(si.get("entries")), len(expected_values)-1)
len(expected_values)-1)
# check if item values are calculated # check if item values are calculated
for d in si.get("entries"): for d in si.get("entries"):
for i, k in enumerate(expected_values["keys"]): for i, k in enumerate(expected_values["keys"]):
self.assertEquals(d.get(k), expected_values[d.item_code][i]) self.assertEquals(d.get(k), expected_values[d.item_code][i])
# check net total # check net total
self.assertEquals(si.net_total, 49501.7) self.assertEquals(si.net_total, 49501.7)
self.assertEquals(si.net_total_export, 1250) self.assertEquals(si.net_total_export, 1250)
# check tax calculation # check tax calculation
expected_values = { expected_values = {
"keys": ["tax_amount", "total"], "keys": ["tax_amount", "total"],
@@ -345,134 +333,134 @@ class TestSalesInvoice(unittest.TestCase):
"_Test Account Shipping Charges - _TC": [5000, 72450.17], "_Test Account Shipping Charges - _TC": [5000, 72450.17],
"_Test Account Discount - _TC": [-7245.01, 65205.16] "_Test Account Discount - _TC": [-7245.01, 65205.16]
} }
for d in si.get("other_charges"): for d in si.get("other_charges"):
for i, k in enumerate(expected_values["keys"]): for i, k in enumerate(expected_values["keys"]):
self.assertEquals(d.get(k), expected_values[d.account_head][i]) self.assertEquals(d.get(k), expected_values[d.account_head][i])
self.assertEquals(si.grand_total, 65205.16) self.assertEquals(si.grand_total, 65205.16)
self.assertEquals(si.grand_total_export, 1304.1) self.assertEquals(si.grand_total_export, 1304.1)
def test_outstanding(self): def test_outstanding(self):
w = self.make() w = self.make()
self.assertEquals(w.outstanding_amount, w.grand_total) self.assertEquals(w.outstanding_amount, w.grand_total)
def test_payment(self): def test_payment(self):
frappe.db.sql("""delete from `tabGL Entry`""") frappe.db.sql("""delete from `tabGL Entry`""")
w = self.make() w = self.make()
from erpnext.accounts.doctype.journal_voucher.test_journal_voucher \ from erpnext.accounts.doctype.journal_voucher.test_journal_voucher \
import test_records as jv_test_records import test_records as jv_test_records
jv = frappe.get_doc(frappe.copy_doc(jv_test_records[0])) jv = frappe.get_doc(frappe.copy_doc(jv_test_records[0]))
jv.get("entries")[0].against_invoice = w.name jv.get("entries")[0].against_invoice = w.name
jv.insert() jv.insert()
jv.submit() jv.submit()
self.assertEquals(frappe.db.get_value("Sales Invoice", w.name, "outstanding_amount"), self.assertEquals(frappe.db.get_value("Sales Invoice", w.name, "outstanding_amount"),
161.8) 161.8)
jv.cancel() jv.cancel()
self.assertEquals(frappe.db.get_value("Sales Invoice", w.name, "outstanding_amount"), self.assertEquals(frappe.db.get_value("Sales Invoice", w.name, "outstanding_amount"),
561.8) 561.8)
def test_time_log_batch(self): def test_time_log_batch(self):
tlb = frappe.get_doc("Time Log Batch", "_T-Time Log Batch-00001") tlb = frappe.get_doc("Time Log Batch", "_T-Time Log Batch-00001")
tlb.submit() tlb.submit()
si = frappe.get_doc(frappe.copy_doc(test_records[0])) si = frappe.get_doc(frappe.copy_doc(test_records[0]))
si.get("entries")[0].time_log_batch = "_T-Time Log Batch-00001" si.get("entries")[0].time_log_batch = "_T-Time Log Batch-00001"
si.insert() si.insert()
si.submit() si.submit()
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"), "Billed") "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") "Billed")
si.cancel() 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") "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") "Batched for Billing")
def test_sales_invoice_gl_entry_without_aii(self): def test_sales_invoice_gl_entry_without_aii(self):
self.clear_stock_account_balance() self.clear_stock_account_balance()
set_perpetual_inventory(0) set_perpetual_inventory(0)
si = frappe.copy_doc(test_records[1]) si = frappe.copy_doc(test_records[1])
si.insert() si.insert()
si.submit() si.submit()
gl_entries = frappe.db.sql("""select account, debit, credit gl_entries = frappe.db.sql("""select account, debit, credit
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
order by account asc""", si.name, as_dict=1) order by account asc""", si.name, as_dict=1)
self.assertTrue(gl_entries) self.assertTrue(gl_entries)
expected_values = sorted([ expected_values = sorted([
[si.debit_to, 630.0, 0.0], [si.debit_to, 630.0, 0.0],
[test_records[1][1]["income_account"], 0.0, 500.0], [test_records[1]["entries"][0]["income_account"], 0.0, 500.0],
[test_records[1][2]["account_head"], 0.0, 80.0], [test_records[1]["other_charges"][0]["account_head"], 0.0, 80.0],
[test_records[1][3]["account_head"], 0.0, 50.0], [test_records[1]["other_charges"][1]["account_head"], 0.0, 50.0],
]) ])
for i, gle in enumerate(gl_entries): for i, gle in enumerate(gl_entries):
self.assertEquals(expected_values[i][0], gle.account) self.assertEquals(expected_values[i][0], gle.account)
self.assertEquals(expected_values[i][1], gle.debit) self.assertEquals(expected_values[i][1], gle.debit)
self.assertEquals(expected_values[i][2], gle.credit) self.assertEquals(expected_values[i][2], gle.credit)
# cancel # cancel
si.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) where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)
self.assertFalse(gle) self.assertFalse(gle)
def test_pos_gl_entry_with_aii(self): def test_pos_gl_entry_with_aii(self):
self.clear_stock_account_balance() self.clear_stock_account_balance()
set_perpetual_inventory() set_perpetual_inventory()
self._insert_purchase_receipt() self._insert_purchase_receipt()
self._insert_pos_settings() self._insert_pos_settings()
pos = frappe.copy_doc(test_records[1]) pos = copy.deepcopy(test_records[1])
pos[0]["is_pos"] = 1 pos["is_pos"] = 1
pos[0]["update_stock"] = 1 pos["update_stock"] = 1
pos[0]["posting_time"] = "12:05" pos["posting_time"] = "12:05"
pos[0]["cash_bank_account"] = "_Test Account Bank Account - _TC" pos["cash_bank_account"] = "_Test Account Bank Account - _TC"
pos[0]["paid_amount"] = 600.0 pos["paid_amount"] = 600.0
si = frappe.copy_doc(pos) si = frappe.copy_doc(pos)
si.insert() si.insert()
si.submit() si.submit()
# check stock ledger entries # check stock ledger entries
sle = frappe.db.sql("""select * from `tabStock Ledger Entry` sle = frappe.db.sql("""select * from `tabStock Ledger Entry`
where voucher_type = 'Sales Invoice' and voucher_no = %s""", where voucher_type = 'Sales Invoice' and voucher_no = %s""",
si.name, as_dict=1)[0] si.name, as_dict=1)[0]
self.assertTrue(sle) 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]) ["_Test Item", "_Test Warehouse - _TC", -1.0])
# check gl entries # check gl entries
gl_entries = frappe.db.sql("""select account, debit, credit gl_entries = frappe.db.sql("""select account, debit, credit
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
order by account asc, debit asc""", si.name, as_dict=1) order by account asc, debit asc""", si.name, as_dict=1)
self.assertTrue(gl_entries) self.assertTrue(gl_entries)
stock_in_hand = frappe.db.get_value("Account", {"master_name": "_Test Warehouse - _TC"}) stock_in_hand = frappe.db.get_value("Account", {"master_name": "_Test Warehouse - _TC"})
expected_gl_entries = sorted([ expected_gl_entries = sorted([
[si.debit_to, 630.0, 0.0], [si.debit_to, 630.0, 0.0],
[pos[1]["income_account"], 0.0, 500.0], [pos["entries"][0]["income_account"], 0.0, 500.0],
[pos[2]["account_head"], 0.0, 80.0], [pos["other_charges"][0]["account_head"], 0.0, 80.0],
[pos[3]["account_head"], 0.0, 50.0], [pos["other_charges"][1]["account_head"], 0.0, 50.0],
[stock_in_hand, 0.0, 75.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], [si.debit_to, 0.0, 600.0],
["_Test Account Bank Account - _TC", 600.0, 0.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][0], gle.account)
self.assertEquals(expected_gl_entries[i][1], gle.debit) self.assertEquals(expected_gl_entries[i][1], gle.debit)
self.assertEquals(expected_gl_entries[i][2], gle.credit) self.assertEquals(expected_gl_entries[i][2], gle.credit)
si.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) where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)
self.assertFalse(gle) self.assertFalse(gle)
self.assertFalse(get_stock_and_account_difference([stock_in_hand])) self.assertFalse(get_stock_and_account_difference([stock_in_hand]))
set_perpetual_inventory(0) set_perpetual_inventory(0)
def test_si_gl_entry_with_aii_and_update_stock_with_warehouse_but_no_account(self): def test_si_gl_entry_with_aii_and_update_stock_with_warehouse_but_no_account(self):
self.clear_stock_account_balance() self.clear_stock_account_balance()
set_perpetual_inventory() set_perpetual_inventory()
frappe.delete_doc("Account", "_Test Warehouse No Account - _TC") frappe.delete_doc("Account", "_Test Warehouse No Account - _TC")
# insert purchase receipt # insert purchase receipt
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import test_records \ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import test_records \
as pr_test_records as pr_test_records
pr = frappe.copy_doc(pr_test_records[0]) pr = frappe.copy_doc(pr_test_records[0])
pr.naming_series = "_T-Purchase Receipt-" 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.insert()
pr.submit() pr.submit()
si_doc = frappe.copy_doc(test_records[1]) si_doc = copy.deepcopy(test_records[1])
si_doc["update_stock"] = 1 si_doc["update_stock"] = 1
si_doc["posting_time"] = "12:05" 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 = frappe.copy_doc(si_doc)
si.insert() si.insert()
si.submit() si.submit()
# check stock ledger entries # check stock ledger entries
sle = frappe.db.sql("""select * from `tabStock Ledger Entry` sle = frappe.db.sql("""select * from `tabStock Ledger Entry`
where voucher_type = 'Sales Invoice' and voucher_no = %s""", where voucher_type = 'Sales Invoice' and voucher_no = %s""",
si.name, as_dict=1)[0] si.name, as_dict=1)[0]
self.assertTrue(sle) 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]) ["_Test Item", "_Test Warehouse No Account - _TC", -1.0])
# check gl entries # check gl entries
gl_entries = frappe.db.sql("""select account, debit, credit gl_entries = frappe.db.sql("""select account, debit, credit
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
order by account asc, debit asc""", si.name, as_dict=1) order by account asc, debit asc""", si.name, as_dict=1)
self.assertTrue(gl_entries) self.assertTrue(gl_entries)
expected_gl_entries = sorted([ expected_gl_entries = sorted([
[si.debit_to, 630.0, 0.0], [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")[0]["account_head"], 0.0, 80.0],
[si_doc.get("other_charges")[1]["account_head"], 0.0, 50.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][0], gle.account)
self.assertEquals(expected_gl_entries[i][1], gle.debit) self.assertEquals(expected_gl_entries[i][1], gle.debit)
self.assertEquals(expected_gl_entries[i][2], gle.credit) self.assertEquals(expected_gl_entries[i][2], gle.credit)
si.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) where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)
self.assertFalse(gle) self.assertFalse(gle)
set_perpetual_inventory(0) set_perpetual_inventory(0)
def test_sales_invoice_gl_entry_with_aii_no_item_code(self): def test_sales_invoice_gl_entry_with_aii_no_item_code(self):
self.clear_stock_account_balance() self.clear_stock_account_balance()
set_perpetual_inventory() set_perpetual_inventory()
si_copy = frappe.copy_doc(test_records[1]) si = frappe.get_doc(test_records[1])
si_copy[1]["item_code"] = None si.get("entries")[0].item_code = None
si = frappe.get_doc(si_copy)
si.insert() si.insert()
si.submit() si.submit()
gl_entries = frappe.db.sql("""select account, debit, credit gl_entries = frappe.db.sql("""select account, debit, credit
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
order by account asc""", si.name, as_dict=1) order by account asc""", si.name, as_dict=1)
self.assertTrue(gl_entries) self.assertTrue(gl_entries)
expected_values = sorted([ expected_values = sorted([
[si.debit_to, 630.0, 0.0], [si.debit_to, 630.0, 0.0],
[test_records[1][1]["income_account"], 0.0, 500.0], [test_records[1]["entries"][0]["income_account"], 0.0, 500.0],
[test_records[1][2]["account_head"], 0.0, 80.0], [test_records[1]["other_charges"][0]["account_head"], 0.0, 80.0],
[test_records[1][3]["account_head"], 0.0, 50.0], [test_records[1]["other_charges"][1]["account_head"], 0.0, 50.0],
]) ])
for i, gle in enumerate(gl_entries): for i, gle in enumerate(gl_entries):
self.assertEquals(expected_values[i][0], gle.account) self.assertEquals(expected_values[i][0], gle.account)
self.assertEquals(expected_values[i][1], gle.debit) self.assertEquals(expected_values[i][1], gle.debit)
self.assertEquals(expected_values[i][2], gle.credit) self.assertEquals(expected_values[i][2], gle.credit)
set_perpetual_inventory(0) set_perpetual_inventory(0)
def test_sales_invoice_gl_entry_with_aii_non_stock_item(self): def test_sales_invoice_gl_entry_with_aii_non_stock_item(self):
self.clear_stock_account_balance() self.clear_stock_account_balance()
set_perpetual_inventory() set_perpetual_inventory()
si_copy = frappe.copy_doc(test_records[1]) si = frappe.get_doc(test_records[1])
si_copy[1]["item_code"] = "_Test Non Stock Item" si.get("entries")[0].item_code = "_Test Non Stock Item"
si = frappe.get_doc(si_copy)
si.insert() si.insert()
si.submit() si.submit()
gl_entries = frappe.db.sql("""select account, debit, credit gl_entries = frappe.db.sql("""select account, debit, credit
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
order by account asc""", si.name, as_dict=1) order by account asc""", si.name, as_dict=1)
self.assertTrue(gl_entries) self.assertTrue(gl_entries)
expected_values = sorted([ expected_values = sorted([
[si.debit_to, 630.0, 0.0], [si.debit_to, 630.0, 0.0],
[test_records[1][1]["income_account"], 0.0, 500.0], [test_records[1]["entries"][0]["income_account"], 0.0, 500.0],
[test_records[1][2]["account_head"], 0.0, 80.0], [test_records[1]["other_charges"][0]["account_head"], 0.0, 80.0],
[test_records[1][3]["account_head"], 0.0, 50.0], [test_records[1]["other_charges"][1]["account_head"], 0.0, 50.0],
]) ])
for i, gle in enumerate(gl_entries): for i, gle in enumerate(gl_entries):
self.assertEquals(expected_values[i][0], gle.account) self.assertEquals(expected_values[i][0], gle.account)
self.assertEquals(expected_values[i][1], gle.debit) self.assertEquals(expected_values[i][1], gle.debit)
self.assertEquals(expected_values[i][2], gle.credit) self.assertEquals(expected_values[i][2], gle.credit)
set_perpetual_inventory(0) set_perpetual_inventory(0)
def _insert_purchase_receipt(self): def _insert_purchase_receipt(self):
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import test_records \ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import test_records \
as pr_test_records as pr_test_records
@@ -608,7 +594,7 @@ class TestSalesInvoice(unittest.TestCase):
pr.naming_series = "_T-Purchase Receipt-" pr.naming_series = "_T-Purchase Receipt-"
pr.insert() pr.insert()
pr.submit() pr.submit()
def _insert_delivery_note(self): def _insert_delivery_note(self):
from erpnext.stock.doctype.delivery_note.test_delivery_note import test_records \ from erpnext.stock.doctype.delivery_note.test_delivery_note import test_records \
as dn_test_records as dn_test_records
@@ -617,23 +603,23 @@ class TestSalesInvoice(unittest.TestCase):
dn.insert() dn.insert()
dn.submit() dn.submit()
return dn return dn
def _insert_pos_settings(self): def _insert_pos_settings(self):
from erpnext.accounts.doctype.pos_setting.test_pos_setting \ from erpnext.accounts.doctype.pos_setting.test_pos_setting \
import test_records as pos_setting_test_records import test_records as pos_setting_test_records
frappe.db.sql("""delete from `tabPOS Setting`""") frappe.db.sql("""delete from `tabPOS Setting`""")
ps = frappe.copy_doc(pos_setting_test_records[0]) ps = frappe.copy_doc(pos_setting_test_records[0])
ps.insert() ps.insert()
def test_sales_invoice_with_advance(self): def test_sales_invoice_with_advance(self):
from erpnext.accounts.doctype.journal_voucher.test_journal_voucher \ from erpnext.accounts.doctype.journal_voucher.test_journal_voucher \
import test_records as jv_test_records import test_records as jv_test_records
jv = frappe.copy_doc(jv_test_records[0]) jv = frappe.copy_doc(jv_test_records[0])
jv.insert() jv.insert()
jv.submit() jv.submit()
si = frappe.copy_doc(test_records[0]) si = frappe.copy_doc(test_records[0])
si.append("advance_adjustment_details", { si.append("advance_adjustment_details", {
"doctype": "Sales Invoice Advance", "doctype": "Sales Invoice Advance",
@@ -646,20 +632,20 @@ class TestSalesInvoice(unittest.TestCase):
si.insert() si.insert()
si.submit() si.submit()
si.load_from_db() si.load_from_db()
self.assertTrue(frappe.db.sql("""select name from `tabJournal Voucher Detail` self.assertTrue(frappe.db.sql("""select name from `tabJournal Voucher Detail`
where against_invoice=%s""", si.name)) where against_invoice=%s""", si.name))
self.assertTrue(frappe.db.sql("""select name from `tabJournal Voucher Detail` self.assertTrue(frappe.db.sql("""select name from `tabJournal Voucher Detail`
where against_invoice=%s and credit=300""", si.name)) where against_invoice=%s and credit=300""", si.name))
self.assertEqual(si.outstanding_amount, 261.8) self.assertEqual(si.outstanding_amount, 261.8)
si.cancel() si.cancel()
self.assertTrue(not frappe.db.sql("""select name from `tabJournal Voucher Detail` self.assertTrue(not frappe.db.sql("""select name from `tabJournal Voucher Detail`
where against_invoice=%s""", si.name)) where against_invoice=%s""", si.name))
def test_recurring_invoice(self): def test_recurring_invoice(self):
from frappe.utils import get_first_day, get_last_day, add_to_date, nowdate, getdate from frappe.utils import get_first_day, get_last_day, add_to_date, nowdate, getdate
from erpnext.accounts.utils import get_fiscal_year 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_from_date": get_first_day(today),
"invoice_period_to_date": get_last_day(today) "invoice_period_to_date": get_last_day(today)
}) })
# monthly # monthly
si1 = frappe.copy_doc(base_si) si1 = frappe.copy_doc(base_si)
si1.insert() si1.insert()
si1.submit() si1.submit()
self._test_recurring_invoice(si1, True) self._test_recurring_invoice(si1, True)
# monthly without a first and last day period # monthly without a first and last day period
si2 = frappe.copy_doc(base_si) si2 = frappe.copy_doc(base_si)
si2.update({ si2.update({
@@ -691,7 +677,7 @@ class TestSalesInvoice(unittest.TestCase):
si2.insert() si2.insert()
si2.submit() si2.submit()
self._test_recurring_invoice(si2, False) self._test_recurring_invoice(si2, False)
# quarterly # quarterly
si3 = frappe.copy_doc(base_si) si3 = frappe.copy_doc(base_si)
si3.update({ si3.update({
@@ -702,7 +688,7 @@ class TestSalesInvoice(unittest.TestCase):
si3.insert() si3.insert()
si3.submit() si3.submit()
self._test_recurring_invoice(si3, True) self._test_recurring_invoice(si3, True)
# quarterly without a first and last day period # quarterly without a first and last day period
si4 = frappe.copy_doc(base_si) si4 = frappe.copy_doc(base_si)
si4.update({ si4.update({
@@ -713,7 +699,7 @@ class TestSalesInvoice(unittest.TestCase):
si4.insert() si4.insert()
si4.submit() si4.submit()
self._test_recurring_invoice(si4, False) self._test_recurring_invoice(si4, False)
# yearly # yearly
si5 = frappe.copy_doc(base_si) si5 = frappe.copy_doc(base_si)
si5.update({ si5.update({
@@ -724,7 +710,7 @@ class TestSalesInvoice(unittest.TestCase):
si5.insert() si5.insert()
si5.submit() si5.submit()
self._test_recurring_invoice(si5, True) self._test_recurring_invoice(si5, True)
# yearly without a first and last day period # yearly without a first and last day period
si6 = frappe.copy_doc(base_si) si6 = frappe.copy_doc(base_si)
si6.update({ si6.update({
@@ -735,7 +721,7 @@ class TestSalesInvoice(unittest.TestCase):
si6.insert() si6.insert()
si6.submit() si6.submit()
self._test_recurring_invoice(si6, False) self._test_recurring_invoice(si6, False)
# change posting date but keep recuring day to be today # change posting date but keep recuring day to be today
si7 = frappe.copy_doc(base_si) si7 = frappe.copy_doc(base_si)
si7.update({ si7.update({
@@ -743,7 +729,7 @@ class TestSalesInvoice(unittest.TestCase):
}) })
si7.insert() si7.insert()
si7.submit() si7.submit()
# setting so that _test function works # setting so that _test function works
si7.posting_date = today si7.posting_date = today
self._test_recurring_invoice(si7, True) self._test_recurring_invoice(si7, True)
@@ -752,52 +738,52 @@ class TestSalesInvoice(unittest.TestCase):
from frappe.utils import add_months, get_last_day from frappe.utils import add_months, get_last_day
from erpnext.accounts.doctype.sales_invoice.sales_invoice \ from erpnext.accounts.doctype.sales_invoice.sales_invoice \
import manage_recurring_invoices, get_next_date import manage_recurring_invoices, get_next_date
no_of_months = ({"Monthly": 1, "Quarterly": 3, "Yearly": 12})[base_si.recurring_type] no_of_months = ({"Monthly": 1, "Quarterly": 3, "Yearly": 12})[base_si.recurring_type]
def _test(i): def _test(i):
self.assertEquals(i+1, frappe.db.sql("""select count(*) from `tabSales Invoice` 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]) 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) base_si.repeat_on_day_of_month)
manage_recurring_invoices(next_date=next_date, commit=False) manage_recurring_invoices(next_date=next_date, commit=False)
recurred_invoices = frappe.db.sql("""select name from `tabSales Invoice` recurred_invoices = frappe.db.sql("""select name from `tabSales Invoice`
where recurring_id=%s and docstatus=1 order by name desc""", where recurring_id=%s and docstatus=1 order by name desc""",
base_si.recurring_id) base_si.recurring_id)
self.assertEquals(i+2, len(recurred_invoices)) self.assertEquals(i+2, len(recurred_invoices))
new_si = frappe.get_doc("Sales Invoice", recurred_invoices[0][0]) new_si = frappe.get_doc("Sales Invoice", recurred_invoices[0][0])
for fieldname in ["convert_into_recurring_invoice", "recurring_type", for fieldname in ["convert_into_recurring_invoice", "recurring_type",
"repeat_on_day_of_month", "notification_email_address"]: "repeat_on_day_of_month", "notification_email_address"]:
self.assertEquals(base_si.get(fieldname), self.assertEquals(base_si.get(fieldname),
new_si.get(fieldname)) new_si.get(fieldname))
self.assertEquals(new_si.posting_date, unicode(next_date)) self.assertEquals(new_si.posting_date, unicode(next_date))
self.assertEquals(new_si.invoice_period_from_date, self.assertEquals(new_si.invoice_period_from_date,
unicode(add_months(base_si.invoice_period_from_date, no_of_months))) unicode(add_months(base_si.invoice_period_from_date, no_of_months)))
if first_and_last_day: 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, unicode(get_last_day(add_months(base_si.invoice_period_to_date,
no_of_months)))) no_of_months))))
else: 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))) unicode(add_months(base_si.invoice_period_to_date, no_of_months)))
return new_si return new_si
# if yearly, test 1 repetition, else test 5 repetitions # if yearly, test 1 repetition, else test 5 repetitions
count = 1 if (no_of_months == 12) else 5 count = 1 if (no_of_months == 12) else 5
for i in xrange(count): for i in xrange(count):
base_si = _test(i) base_si = _test(i)
def clear_stock_account_balance(self): def clear_stock_account_balance(self):
frappe.db.sql("delete from `tabStock Ledger Entry`") frappe.db.sql("delete from `tabStock Ledger Entry`")
frappe.db.sql("delete from tabBin") frappe.db.sql("delete from tabBin")
@@ -806,10 +792,10 @@ class TestSalesInvoice(unittest.TestCase):
def test_serialized(self): def test_serialized(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item 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 from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
se = 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)
si = frappe.copy_doc(test_records[0]) si = frappe.copy_doc(test_records[0])
si.update_stock = 1 si.update_stock = 1
si.get("entries")[0].item_code = "_Test Serialized Item With Series" 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.get("entries")[0].serial_no = serial_nos[0]
si.insert() si.insert()
si.submit() si.submit()
self.assertEquals(frappe.db.get_value("Serial No", serial_nos[0], "status"), "Delivered") 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.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) "delivery_document_no"), si.name)
return si return si
def test_serialized_cancel(self): def test_serialized_cancel(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
si = self.test_serialized() 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], "status"), "Available")
self.assertEquals(frappe.db.get_value("Serial No", serial_nos[0], "warehouse"), "_Test Warehouse - _TC") 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")) "delivery_document_no"))
def test_serialize_status(self): def test_serialize_status(self):
from erpnext.stock.doctype.serial_no.serial_no import SerialNoStatusError, get_serial_nos 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 from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
se = 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 = frappe.get_doc("Serial No", serial_nos[0])
sr.status = "Not Available" sr.status = "Not Available"
sr.save() sr.save()
si = frappe.copy_doc(test_records[0]) si = frappe.copy_doc(test_records[0])
si.update_stock = 1 si.update_stock = 1
si.get("entries")[0].item_code = "_Test Serialized Item With Series" si.get("entries")[0].item_code = "_Test Serialized Item With Series"
@@ -858,5 +844,4 @@ class TestSalesInvoice(unittest.TestCase):
self.assertRaises(SerialNoStatusError, si.submit) self.assertRaises(SerialNoStatusError, si.submit)
test_dependencies = ["Journal Voucher", "POS Setting", "Contact", "Address"] test_dependencies = ["Journal Voucher", "POS Setting", "Contact", "Address"]
test_records = frappe.get_test_records('Sales Invoice')
test_records = frappe.get_test_records('Sales Invoice')

View File

@@ -8,7 +8,7 @@ from frappe.utils import cstr, flt
from frappe import msgprint from frappe import msgprint
from erpnext.controllers.buying_controller import BuyingController from erpnext.controllers.buying_controller import BuyingController
class PurchaseOrder(BuyingController): class PurchaseOrder(BuyingController):
tname = 'Purchase Order Item' tname = 'Purchase Order Item'
@@ -24,15 +24,15 @@ class PurchaseOrder(BuyingController):
'source_field': 'qty', 'source_field': 'qty',
'percent_join_field': 'prevdoc_docname', 'percent_join_field': 'prevdoc_docname',
}] }]
def validate(self): def validate(self):
super(PurchaseOrder, self).validate() super(PurchaseOrder, self).validate()
if not self.status: if not self.status:
self.status = "Draft" self.status = "Draft"
from erpnext.utilities import validate_status from erpnext.utilities import validate_status
validate_status(self.status, ["Draft", "Submitted", "Stopped", validate_status(self.status, ["Draft", "Submitted", "Stopped",
"Cancelled"]) "Cancelled"])
pc_obj = frappe.get_doc('Purchase Common') pc_obj = frappe.get_doc('Purchase Common')
@@ -45,7 +45,7 @@ class PurchaseOrder(BuyingController):
self.validate_with_previous_doc() self.validate_with_previous_doc()
self.validate_for_subcontracting() self.validate_for_subcontracting()
self.update_raw_materials_supplied("po_raw_material_details") self.update_raw_materials_supplied("po_raw_material_details")
def validate_with_previous_doc(self): def validate_with_previous_doc(self):
super(PurchaseOrder, self).validate_with_previous_doc(self.tname, { super(PurchaseOrder, self).validate_with_previous_doc(self.tname, {
"Supplier Quotation": { "Supplier Quotation": {
@@ -54,7 +54,7 @@ class PurchaseOrder(BuyingController):
}, },
"Supplier Quotation Item": { "Supplier Quotation Item": {
"ref_dn_field": "supplier_quotation_item", "ref_dn_field": "supplier_quotation_item",
"compare_fields": [["rate", "="], ["project_name", "="], ["item_code", "="], "compare_fields": [["rate", "="], ["project_name", "="], ["item_code", "="],
["uom", "="]], ["uom", "="]],
"is_child_table": True "is_child_table": True
} }
@@ -65,11 +65,11 @@ class PurchaseOrder(BuyingController):
if d.prevdoc_detail_docname and not d.schedule_date: if d.prevdoc_detail_docname and not d.schedule_date:
d.schedule_date = frappe.db.get_value("Material Request Item", d.schedule_date = frappe.db.get_value("Material Request Item",
d.prevdoc_detail_docname, "schedule_date") d.prevdoc_detail_docname, "schedule_date")
def get_last_purchase_rate(self): def get_last_purchase_rate(self):
frappe.get_doc('Purchase Common').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): def check_for_stopped_status(self, pc_obj):
check_list =[] check_list =[]
for d in self.get('po_details'): for d in self.get('po_details'):
@@ -77,7 +77,7 @@ class PurchaseOrder(BuyingController):
check_list.append(d.prevdoc_docname) check_list.append(d.prevdoc_docname)
pc_obj.check_for_stopped_status( d.prevdoc_doctype, d.prevdoc_docname) pc_obj.check_for_stopped_status( d.prevdoc_doctype, d.prevdoc_docname)
def update_bin(self, is_submit, is_stopped = 0): def update_bin(self, is_submit, is_stopped = 0):
from erpnext.stock.utils import update_bin from erpnext.stock.utils import update_bin
pc_obj = frappe.get_doc('Purchase Common') 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 # this happens when item is changed from non-stock to stock item
if not d.warehouse: if not d.warehouse:
continue continue
ind_qty, po_qty = 0, flt(d.qty) * flt(d.conversion_factor) ind_qty, po_qty = 0, flt(d.qty) * flt(d.conversion_factor)
if is_stopped: if is_stopped:
po_qty = flt(d.qty) > flt(d.received_qty) and \ 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 # No updates in Material Request on Stop / Unstop
if cstr(d.prevdoc_doctype) == 'Material Request' and not is_stopped: 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', 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) 'Material Request - Purchase Order', self.name)
max_qty, qty, curr_qty = flt(curr_ref_qty.split('~~~')[1]), \ max_qty, qty, curr_qty = flt(curr_ref_qty.split('~~~')[1]), \
flt(curr_ref_qty.split('~~~')[0]), 0 flt(curr_ref_qty.split('~~~')[0]), 0
if flt(qty) + flt(po_qty) > flt(max_qty): if flt(qty) + flt(po_qty) > flt(max_qty):
curr_qty = flt(max_qty) - flt(qty) curr_qty = flt(max_qty) - flt(qty)
# special case as there is no restriction # special case as there is no restriction
# for Material Request - Purchase Order # for Material Request - Purchase Order
curr_qty = curr_qty > 0 and curr_qty or 0 curr_qty = curr_qty > 0 and curr_qty or 0
else: else:
curr_qty = flt(po_qty) curr_qty = flt(po_qty)
ind_qty = -flt(curr_qty) ind_qty = -flt(curr_qty)
# Update ordered_qty and indented_qty in bin # Update ordered_qty and indented_qty in bin
@@ -121,12 +121,12 @@ class PurchaseOrder(BuyingController):
"posting_date": self.transaction_date "posting_date": self.transaction_date
} }
update_bin(args) update_bin(args)
def check_modified_date(self): 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) self.name)
date_diff = frappe.db.sql("select TIMEDIFF('%s', '%s')" % ( mod_db[0][0],cstr(self.modified))) date_diff = frappe.db.sql("select TIMEDIFF('%s', '%s')" % ( mod_db[0][0],cstr(self.modified)))
if date_diff and date_diff[0][0]: if date_diff and date_diff[0][0]:
msgprint(cstr(self.doctype) +" => "+ cstr(self.name) +" has been modified. Please Refresh. ") msgprint(cstr(self.doctype) +" => "+ cstr(self.name) +" has been modified. Please Refresh. ")
raise Exception raise Exception
@@ -144,28 +144,28 @@ class PurchaseOrder(BuyingController):
def on_submit(self): def on_submit(self):
purchase_controller = frappe.get_doc("Purchase Common") purchase_controller = frappe.get_doc("Purchase Common")
self.update_prevdoc_status() self.update_prevdoc_status()
self.update_bin(is_submit = 1, is_stopped = 0) 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) self.company, self.grand_total)
purchase_controller.update_last_purchase_rate(self, is_submit = 1) purchase_controller.update_last_purchase_rate(self, is_submit = 1)
frappe.db.set(self,'status','Submitted') frappe.db.set(self,'status','Submitted')
def on_cancel(self): 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) self.check_for_stopped_status(pc_obj)
# Check if Purchase Receipt has been submitted against current Purchase Order # 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') 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 # Check if Purchase Invoice has been submitted against current Purchase Order
submitted = frappe.db.sql("""select t1.name submitted = frappe.db.sql("""select t1.name
from `tabPurchase Invoice` t1,`tabPurchase Invoice Item` t2 from `tabPurchase Invoice` t1,`tabPurchase Invoice Item` t2
where t1.name = t2.parent and t2.purchase_order = %s and t1.docstatus = 1""", where t1.name = t2.parent and t2.purchase_order = %s and t1.docstatus = 1""",
self.name) self.name)
if submitted: if submitted:
msgprint("Purchase Invoice : " + cstr(submitted[0][0]) + " has already been 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_prevdoc_status()
self.update_bin( is_submit = 0, is_stopped = 0) self.update_bin( is_submit = 0, is_stopped = 0)
pc_obj.update_last_purchase_rate(self, is_submit = 0) pc_obj.update_last_purchase_rate(self, is_submit = 0)
def on_update(self): def on_update(self):
pass pass
@frappe.whitelist() @frappe.whitelist()
def make_purchase_receipt(source_name, target_doc=None): def make_purchase_receipt(source_name, target_doc=None):
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
def set_missing_values(source, target): def set_missing_values(source, target):
doc = frappe.get_doc(target) doc = frappe.get_doc(target)
doc.run_method("set_missing_values") 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.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) 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": { "Purchase Order": {
"doctype": "Purchase Receipt", "doctype": "Purchase Receipt",
"validation": { "validation": {
"docstatus": ["=", 1], "docstatus": ["=", 1],
} }
}, },
"Purchase Order Item": { "Purchase Order Item": {
"doctype": "Purchase Receipt Item", "doctype": "Purchase Receipt Item",
"field_map": { "field_map": {
"name": "prevdoc_detail_docname", "name": "prevdoc_detail_docname",
"parent": "prevdoc_docname", "parent": "prevdoc_docname",
"parenttype": "prevdoc_doctype", "parenttype": "prevdoc_doctype",
}, },
"postprocess": update_item, "postprocess": update_item,
"condition": lambda doc: doc.received_qty < doc.qty "condition": lambda doc: doc.received_qty < doc.qty
}, },
"Purchase Taxes and Charges": { "Purchase Taxes and Charges": {
"doctype": "Purchase Taxes and Charges", "doctype": "Purchase Taxes and Charges",
"add_if_empty": True "add_if_empty": True
} }
}, target_doc, set_missing_values) }, target_doc, set_missing_values)
return doclist.as_dict() return doc
@frappe.whitelist() @frappe.whitelist()
def make_purchase_invoice(source_name, target_doc=None): def make_purchase_invoice(source_name, target_doc=None):
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
def set_missing_values(source, target): def set_missing_values(source, target):
doc = frappe.get_doc(target) doc = frappe.get_doc(target)
doc.run_method("set_missing_values") doc.run_method("set_missing_values")
@@ -232,26 +232,26 @@ def make_purchase_invoice(source_name, target_doc=None):
if flt(obj.base_rate): if flt(obj.base_rate):
target.qty = target.base_amount / 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": { "Purchase Order": {
"doctype": "Purchase Invoice", "doctype": "Purchase Invoice",
"validation": { "validation": {
"docstatus": ["=", 1], "docstatus": ["=", 1],
} }
}, },
"Purchase Order Item": { "Purchase Order Item": {
"doctype": "Purchase Invoice Item", "doctype": "Purchase Invoice Item",
"field_map": { "field_map": {
"name": "po_detail", "name": "po_detail",
"parent": "purchase_order", "parent": "purchase_order",
}, },
"postprocess": update_item, "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": { "Purchase Taxes and Charges": {
"doctype": "Purchase Taxes and Charges", "doctype": "Purchase Taxes and Charges",
"add_if_empty": True "add_if_empty": True
} }
}, target_doc, set_missing_values) }, target_doc, set_missing_values)
return doclist.as_dict() return doc

View File

@@ -8,90 +8,87 @@ import frappe.defaults
from frappe.utils import flt from frappe.utils import flt
class TestPurchaseOrder(unittest.TestCase): 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 from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
po = frappe.copy_doc(test_records[0]).insert() 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.name)
po = frappe.get_doc("Purchase Order", po.name) po = frappe.get_doc("Purchase Order", po.name)
po.submit() po.submit()
pr = make_purchase_receipt(po.name) pr = make_purchase_receipt(po.name)
pr[0]["supplier_warehouse"] = "_Test Warehouse 1 - _TC" pr.supplier_warehouse = "_Test Warehouse 1 - _TC"
pr[0]["posting_date"] = "2013-05-12" pr.posting_date = "2013-05-12"
self.assertEquals(pr[0]["doctype"], "Purchase Receipt") self.assertEquals(pr.doctype, "Purchase Receipt")
self.assertEquals(len(pr), len(test_records[0])) self.assertEquals(len(pr.get("purchase_receipt_details")), len(test_records[0]["po_details"]))
pr[0]["naming_series"] = "_T-Purchase Receipt-" pr.naming_series = "_T-Purchase Receipt-"
pr_doc = frappe.get_doc(pr) frappe.get_doc(pr).insert()
pr_doc.insert()
def test_ordered_qty(self): def test_ordered_qty(self):
frappe.db.sql("delete from tabBin") frappe.db.sql("delete from tabBin")
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
po = frappe.copy_doc(test_records[0]).insert() 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.name)
po = frappe.get_doc("Purchase Order", po.name) po = frappe.get_doc("Purchase Order", po.name)
po.is_subcontracted = "No" po.is_subcontracted = "No"
po.get("po_details")[0].item_code = "_Test Item" po.get("po_details")[0].item_code = "_Test Item"
po.submit() 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) "warehouse": "_Test Warehouse - _TC"}, "ordered_qty"), 10)
pr = make_purchase_receipt(po.name) pr = make_purchase_receipt(po.name)
self.assertEquals(pr[0]["doctype"], "Purchase Receipt") self.assertEquals(pr.doctype, "Purchase Receipt")
self.assertEquals(len(pr), len(test_records[0])) self.assertEquals(len(pr.get("purchase_receipt_details", [])), len(test_records[0]["po_details"]))
pr[0]["posting_date"] = "2013-05-12" pr.posting_date = "2013-05-12"
pr[0]["naming_series"] = "_T-Purchase Receipt-" pr.naming_series = "_T-Purchase Receipt-"
pr[1]["qty"] = 4.0 pr.purchase_receipt_details[0].qty = 4.0
pr_doc = frappe.get_doc(pr) pr.insert()
pr_doc.insert() pr.submit()
pr_doc.submit()
self.assertEquals(flt(frappe.db.get_value("Bin", {"item_code": "_Test Item",
self.assertEquals(flt(frappe.db.get_value("Bin", {"item_code": "_Test Item",
"warehouse": "_Test Warehouse - _TC"}, "ordered_qty")), 6.0) "warehouse": "_Test Warehouse - _TC"}, "ordered_qty")), 6.0)
frappe.db.set_value('Item', '_Test Item', 'tolerance', 50) frappe.db.set_value('Item', '_Test Item', 'tolerance', 50)
pr1 = make_purchase_receipt(po.name) pr1 = make_purchase_receipt(po.name)
pr1[0]["naming_series"] = "_T-Purchase Receipt-" pr1.naming_series = "_T-Purchase Receipt-"
pr1[0]["posting_date"] = "2013-05-12" pr1.posting_date = "2013-05-12"
pr1[1]["qty"] = 8 pr1.get("purchase_receipt_details")[0].qty = 8
pr1_doc = frappe.get_doc(pr1) pr1.insert()
pr1_doc.insert() pr1.submit()
pr1_doc.submit()
self.assertEquals(flt(frappe.db.get_value("Bin", {"item_code": "_Test Item",
self.assertEquals(flt(frappe.db.get_value("Bin", {"item_code": "_Test Item",
"warehouse": "_Test Warehouse - _TC"}, "ordered_qty")), 0.0) "warehouse": "_Test Warehouse - _TC"}, "ordered_qty")), 0.0)
def test_make_purchase_invoice(self): def test_make_purchase_invoice(self):
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice
po = frappe.copy_doc(test_records[0]).insert() 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.name)
po = frappe.get_doc("Purchase Order", po.name) po = frappe.get_doc("Purchase Order", po.name)
po.submit() po.submit()
pi = make_purchase_invoice(po.name) pi = make_purchase_invoice(po.name)
self.assertEquals(pi[0]["doctype"], "Purchase Invoice") self.assertEquals(pi.doctype, "Purchase Invoice")
self.assertEquals(len(pi), len(test_records[0])) self.assertEquals(len(pi.get("entries", [])), len(test_records[0]["po_details"]))
pi[0]["posting_date"] = "2013-05-12" pi.posting_date = "2013-05-12"
pi[0]["bill_no"] = "NA" pi.bill_no = "NA"
frappe.get_doc(pi).insert() frappe.get_doc(pi).insert()
def test_subcontracting(self): def test_subcontracting(self):
po = frappe.copy_doc(test_records[0]) po = frappe.copy_doc(test_records[0])
po.insert() po.insert()
@@ -113,4 +110,4 @@ class TestPurchaseOrder(unittest.TestCase):
test_dependencies = ["BOM"] test_dependencies = ["BOM"]
test_records = frappe.get_test_records('Purchase Order') test_records = frappe.get_test_records('Purchase Order')

View File

@@ -88,4 +88,4 @@ def make_purchase_order(source_name, target_doc=None):
}, },
}, target_doc, set_missing_values) }, target_doc, set_missing_values)
return doclist.as_dict() return doclist

View File

@@ -13,22 +13,22 @@ class TestPurchaseOrder(unittest.TestCase):
sq = frappe.copy_doc(test_records[0]).insert() 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.name)
sq = frappe.get_doc("Supplier Quotation", sq.name) sq = frappe.get_doc("Supplier Quotation", sq.name)
sq.submit() sq.submit()
po = make_purchase_order(sq.name) 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"): if doc.get("item_code"):
doc["schedule_date"] = "2013-04-12" doc.set("schedule_date", "2013-04-12")
frappe.get_doc(po).insert() po.insert()
test_records = frappe.get_test_records('Supplier Quotation') test_records = frappe.get_test_records('Supplier Quotation')

View File

@@ -18,16 +18,16 @@ class AccountsController(TransactionBase):
self.calculate_taxes_and_totals() self.calculate_taxes_and_totals()
self.validate_value("grand_total", ">=", 0) self.validate_value("grand_total", ">=", 0)
self.set_total_in_words() self.set_total_in_words()
self.validate_for_freezed_account() self.validate_for_freezed_account()
def set_missing_values(self, for_validate=False): def set_missing_values(self, for_validate=False):
for fieldname in ["posting_date", "transaction_date"]: for fieldname in ["posting_date", "transaction_date"]:
if not self.get(fieldname) and self.meta.get_field(fieldname): if not self.get(fieldname) and self.meta.get_field(fieldname):
self.set(fieldname, today()) self.set(fieldname, today())
if not self.fiscal_year: if not self.fiscal_year:
self.fiscal_year = get_fiscal_year(self.get(fieldname))[0] self.fiscal_year = get_fiscal_year(self.get(fieldname))[0]
def validate_date_with_fiscal_year(self): def validate_date_with_fiscal_year(self):
if self.meta.get_field("fiscal_year") : if self.meta.get_field("fiscal_year") :
date_field = "" date_field = ""
@@ -35,40 +35,40 @@ class AccountsController(TransactionBase):
date_field = "posting_date" date_field = "posting_date"
elif self.meta.get_field("transaction_date"): elif self.meta.get_field("transaction_date"):
date_field = "transaction_date" date_field = "transaction_date"
if date_field and self.get(date_field): 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)) label=self.meta.get_label(date_field))
def validate_for_freezed_account(self): def validate_for_freezed_account(self):
for fieldname in ["customer", "supplier"]: for fieldname in ["customer", "supplier"]:
if self.meta.get_field(fieldname) and self.get(fieldname): if self.meta.get_field(fieldname) and self.get(fieldname):
accounts = frappe.db.get_values("Account", accounts = frappe.db.get_values("Account",
{"master_type": fieldname.title(), "master_name": self.get(fieldname), {"master_type": fieldname.title(), "master_name": self.get(fieldname),
"company": self.company}, "name") "company": self.company}, "name")
if accounts: if accounts:
from erpnext.accounts.doctype.gl_entry.gl_entry import validate_frozen_account 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]) validate_frozen_account(account[0])
def set_price_list_currency(self, buying_or_selling): def set_price_list_currency(self, buying_or_selling):
if self.meta.get_field("currency"): if self.meta.get_field("currency"):
company_currency = get_company_currency(self.company) company_currency = get_company_currency(self.company)
# price list part # price list part
fieldname = "selling_price_list" if buying_or_selling.lower() == "selling" \ fieldname = "selling_price_list" if buying_or_selling.lower() == "selling" \
else "buying_price_list" else "buying_price_list"
if self.meta.get_field(fieldname) and self.get(fieldname): if self.meta.get_field(fieldname) and self.get(fieldname):
self.price_list_currency = frappe.db.get_value("Price List", self.price_list_currency = frappe.db.get_value("Price List",
self.get(fieldname), "currency") self.get(fieldname), "currency")
if self.price_list_currency == company_currency: if self.price_list_currency == company_currency:
self.plc_conversion_rate = 1.0 self.plc_conversion_rate = 1.0
elif not self.plc_conversion_rate: elif not self.plc_conversion_rate:
self.plc_conversion_rate = self.get_exchange_rate( self.plc_conversion_rate = self.get_exchange_rate(
self.price_list_currency, company_currency) self.price_list_currency, company_currency)
# currency # currency
if not self.currency: if not self.currency:
self.currency = self.price_list_currency self.currency = self.price_list_currency
@@ -96,44 +96,44 @@ class AccountsController(TransactionBase):
if item.meta.get_field(fieldname) and \ if item.meta.get_field(fieldname) and \
item.get(fieldname) is None and value is not None: item.get(fieldname) is None and value is not None:
item.set(fieldname, value) item.set(fieldname, value)
def set_taxes(self, tax_parentfield, tax_master_field): def set_taxes(self, tax_parentfield, tax_master_field):
if not self.meta.get_field(tax_parentfield): if not self.meta.get_field(tax_parentfield):
return return
tax_master_doctype = self.meta.get_field(tax_master_field).options tax_master_doctype = self.meta.get_field(tax_master_field).options
if not self.get(tax_parentfield): if not self.get(tax_parentfield):
if not self.get(tax_master_field): if not self.get(tax_master_field):
# get the default tax master # get the default tax master
self.set(tax_master_field, frappe.db.get_value(tax_master_doctype, {"is_default": 1})) 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) 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): def append_taxes_from_master(self, tax_parentfield, tax_master_field, tax_master_doctype=None):
if self.get(tax_master_field): if self.get(tax_master_field):
if not tax_master_doctype: if not tax_master_doctype:
tax_master_doctype = self.meta.get_field(tax_master_field).options tax_master_doctype = self.meta.get_field(tax_master_field).options
tax_doctype = self.meta.get_field(tax_parentfield).options tax_doctype = self.meta.get_field(tax_parentfield).options
from frappe.model import default_fields from frappe.model import default_fields
tax_master = frappe.get_doc(tax_master_doctype, self.get(tax_master_field)) tax_master = frappe.get_doc(tax_master_doctype, self.get(tax_master_field))
for i, tax in enumerate(tax_master.get(tax_parentfield)): for i, tax in enumerate(tax_master.get(tax_parentfield)):
for fieldname in default_fields: for fieldname in default_fields:
tax.set(fieldname, None) tax.set(fieldname, None)
self.append(tax_parentfield, tax) self.append(tax_parentfield, tax)
def get_other_charges(self): def get_other_charges(self):
self.set("other_charges", []) self.set("other_charges", [])
self.set_taxes("other_charges", "taxes_and_charges") self.set_taxes("other_charges", "taxes_and_charges")
def calculate_taxes_and_totals(self): def calculate_taxes_and_totals(self):
self.discount_amount_applied = False self.discount_amount_applied = False
self._calculate_taxes_and_totals() self._calculate_taxes_and_totals()
if self.meta.get_field("discount_amount"): if self.meta.get_field("discount_amount"):
self.apply_discount_amount() self.apply_discount_amount()
@@ -151,23 +151,23 @@ class AccountsController(TransactionBase):
self.conversion_rate = flt(self.conversion_rate) self.conversion_rate = flt(self.conversion_rate)
self.item_doclist = self.get(self.fname) self.item_doclist = self.get(self.fname)
self.tax_doclist = self.get(self.other_fname) self.tax_doclist = self.get(self.other_fname)
self.calculate_item_values() self.calculate_item_values()
self.initialize_taxes() self.initialize_taxes()
if hasattr(self, "determine_exclusive_rate"): if hasattr(self, "determine_exclusive_rate"):
self.determine_exclusive_rate() self.determine_exclusive_rate()
self.calculate_net_total() self.calculate_net_total()
self.calculate_taxes() self.calculate_taxes()
self.calculate_totals() self.calculate_totals()
self._cleanup() self._cleanup()
def initialize_taxes(self): def initialize_taxes(self):
for tax in self.tax_doclist: for tax in self.tax_doclist:
tax.item_wise_tax_detail = {} tax.item_wise_tax_detail = {}
tax_fields = ["total", "tax_amount_after_discount_amount", tax_fields = ["total", "tax_amount_after_discount_amount",
"tax_amount_for_current_item", "grand_total_for_current_item", "tax_amount_for_current_item", "grand_total_for_current_item",
"tax_fraction_for_current_item", "grand_total_fraction_for_current_item"] "tax_fraction_for_current_item", "grand_total_fraction_for_current_item"]
if not self.discount_amount_applied: if not self.discount_amount_applied:
@@ -179,7 +179,7 @@ class AccountsController(TransactionBase):
self.validate_on_previous_row(tax) self.validate_on_previous_row(tax)
self.validate_inclusive_tax(tax) self.validate_inclusive_tax(tax)
self.round_floats_in(tax) self.round_floats_in(tax)
def validate_on_previous_row(self, tax): def validate_on_previous_row(self, tax):
""" """
validate if a valid row id is mentioned in case of 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", "row_id_label": self.meta.get_label("row_id",
parentfield=self.other_fname) parentfield=self.other_fname)
}) })
def validate_inclusive_tax(self, tax): def validate_inclusive_tax(self, tax):
def _on_previous_row_error(row_range): def _on_previous_row_error(row_range):
throw((_("Row") + " # %(idx)s [%(doctype)s]: " + throw((_("Row") + " # %(idx)s [%(doctype)s]: " +
@@ -202,24 +202,21 @@ class AccountsController(TransactionBase):
" [" + _("Row") + " # %(row_range)s] " + _("also be included in Item's rate")) % { " [" + _("Row") + " # %(row_range)s] " + _("also be included in Item's rate")) % {
"idx": tax.idx, "idx": tax.idx,
"doctype": tax.doctype, "doctype": tax.doctype,
"inclusive_label": self.meta.get_label("included_in_print_rate", "inclusive_label": frappe.get_meta(tax.doctype).get_label("included_in_print_rate"),
parentfield=self.other_fname), "charge_type_label": frappe.get_meta(tax.doctype).get_label("charge_type"),
"charge_type_label": self.meta.get_label("charge_type",
parentfield=self.other_fname),
"charge_type": tax.charge_type, "charge_type": tax.charge_type,
"row_range": row_range "row_range": row_range
}) })
if cint(getattr(tax, "included_in_print_rate", None)): if cint(getattr(tax, "included_in_print_rate", None)):
if tax.charge_type == "Actual": if tax.charge_type == "Actual":
# inclusive tax cannot be of type Actual # inclusive tax cannot be of type Actual
throw((_("Row") throw((_("Row")
+ " # %(idx)s [%(doctype)s]: %(charge_type_label)s = \"%(charge_type)s\" " + " # %(idx)s [%(doctype)s]: %(charge_type_label)s = \"%(charge_type)s\" "
+ "cannot be included in Item's rate") % { + "cannot be included in Item's rate") % {
"idx": tax.idx, "idx": tax.idx,
"doctype": tax.doctype, "doctype": tax.doctype,
"charge_type_label": self.meta.get_label("charge_type", "charge_type_label": frappe.get_meta(tax.doctype).get_label("charge_type"),
parentfield=self.other_fname),
"charge_type": tax.charge_type, "charge_type": tax.charge_type,
}) })
elif tax.charge_type == "On Previous Row Amount" and \ 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]]): 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 # all rows about the reffered tax should be inclusive
_on_previous_row_error("1 - %d" % (tax.row_id,)) _on_previous_row_error("1 - %d" % (tax.row_id,))
def calculate_taxes(self): def calculate_taxes(self):
# maintain actual tax rate based on idx # 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"]) if tax.charge_type == "Actual"])
for n, item in enumerate(self.item_doclist): for n, item in enumerate(self.item_doclist):
@@ -258,26 +255,26 @@ class AccountsController(TransactionBase):
tax.tax_amount += current_tax_amount tax.tax_amount += current_tax_amount
tax.tax_amount_after_discount_amount += current_tax_amount tax.tax_amount_after_discount_amount += current_tax_amount
if getattr(tax, "category", None): if getattr(tax, "category", None):
# if just for valuation, do not add the tax amount in total # if just for valuation, do not add the tax amount in total
# hence, setting it as 0 for further steps # hence, setting it as 0 for further steps
current_tax_amount = 0.0 if (tax.category == "Valuation") \ current_tax_amount = 0.0 if (tax.category == "Valuation") \
else current_tax_amount else current_tax_amount
current_tax_amount *= -1.0 if (tax.add_deduct_tax == "Deduct") else 1.0 current_tax_amount *= -1.0 if (tax.add_deduct_tax == "Deduct") else 1.0
# Calculate tax.total viz. grand total till that step # 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 # item's amount, previously applied tax and the current tax on that item
if i==0: if i==0:
tax.grand_total_for_current_item = flt(item.base_amount + current_tax_amount, tax.grand_total_for_current_item = flt(item.base_amount + current_tax_amount,
self.precision("total", tax)) self.precision("total", tax))
else: else:
tax.grand_total_for_current_item = \ 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)) current_tax_amount, self.precision("total", tax))
# in tax.total, accumulate grand total of each item # in tax.total, accumulate grand total of each item
tax.total += tax.grand_total_for_current_item tax.total += tax.grand_total_for_current_item
@@ -292,12 +289,12 @@ class AccountsController(TransactionBase):
def round_off_totals(self, tax): def round_off_totals(self, tax):
tax.total = flt(tax.total, self.precision("total", 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 = 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)) self.precision("tax_amount", tax))
def adjust_discount_amount_loss(self, tax): def adjust_discount_amount_loss(self, tax):
discount_amount_loss = self.grand_total - flt(self.discount_amount) - tax.total 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)) discount_amount_loss, self.precision("tax_amount", tax))
tax.total = flt(tax.total + discount_amount_loss, self.precision("total", 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] tax.item_wise_tax_detail[key] = [tax_rate, current_tax_amount]
return current_tax_amount return current_tax_amount
def _load_item_tax_rate(self, item_tax_rate): def _load_item_tax_rate(self, item_tax_rate):
return json.loads(item_tax_rate) if item_tax_rate else {} return json.loads(item_tax_rate) if item_tax_rate else {}
def _get_tax_rate(self, tax, item_tax_map): def _get_tax_rate(self, tax, item_tax_map):
if item_tax_map.has_key(tax.account_head): if item_tax_map.has_key(tax.account_head):
return flt(item_tax_map.get(tax.account_head), self.precision("rate", tax)) return flt(item_tax_map.get(tax.account_head), self.precision("rate", tax))
else: else:
return tax.rate return tax.rate
def _cleanup(self): def _cleanup(self):
for tax in self.tax_doclist: for tax in self.tax_doclist:
tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail) tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail)
def _set_in_company_currency(self, item, print_field, base_field): def _set_in_company_currency(self, item, print_field, base_field):
"""set values in base currency""" """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)), flt(item.get(print_field), self.precision(print_field, item)),
self.precision(base_field, item)) self.precision(base_field, item))
item.set(base_field, value_in_company_currency) item.set(base_field, value_in_company_currency)
def calculate_total_advance(self, parenttype, advance_parentfield): def calculate_total_advance(self, parenttype, advance_parentfield):
if self.doctype == parenttype and self.docstatus < 2: 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)]) for adv in self.get(advance_parentfield)])
self.total_advance = flt(sum_of_allocated_amount, self.precision("total_advance")) self.total_advance = flt(sum_of_allocated_amount, self.precision("total_advance"))
self.calculate_outstanding_amount() self.calculate_outstanding_amount()
def get_gl_dict(self, args): def get_gl_dict(self, args):
"""this method populates the common properties of a gl entry record""" """this method populates the common properties of a gl entry record"""
gl_dict = frappe._dict({ gl_dict = frappe._dict({
'company': self.company, 'company': self.company,
'posting_date': self.posting_date, 'posting_date': self.posting_date,
'voucher_type': self.doctype, 'voucher_type': self.doctype,
'voucher_no': self.name, 'voucher_no': self.name,
@@ -377,24 +374,24 @@ class AccountsController(TransactionBase):
}) })
gl_dict.update(args) gl_dict.update(args)
return gl_dict return gl_dict
def clear_unallocated_advances(self, childtype, parentfield): def clear_unallocated_advances(self, childtype, parentfield):
self.set(parentfield, self.get(parentfield, {"allocated_amount": ["not in", [0, None, ""]]})) 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)) and ifnull(allocated_amount, 0) = 0""" % (childtype, '%s', '%s'), (parentfield, self.name))
def get_advances(self, account_head, child_doctype, parentfield, dr_or_cr): 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 t2.%s as amount, t2.name as jv_detail_no
from `tabJournal Voucher` t1, `tabJournal Voucher Detail` t2 from `tabJournal Voucher` t1, `tabJournal Voucher Detail` t2
where t1.name = t2.parent and t2.account = %s and t2.is_advance = 'Yes' 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_voucher is null or t2.against_voucher = '')
and (t2.against_invoice is null or t2.against_invoice = '') and (t2.against_invoice is null or t2.against_invoice = '')
and (t2.against_jv is null or t2.against_jv = '') and (t2.against_jv is null or t2.against_jv = '')
and t1.docstatus = 1 order by t1.posting_date""" % and t1.docstatus = 1 order by t1.posting_date""" %
(dr_or_cr, '%s'), account_head, as_dict=1) (dr_or_cr, '%s'), account_head, as_dict=1)
self.set(parentfield, []) self.set(parentfield, [])
for d in res: for d in res:
self.append(parentfield, { self.append(parentfield, {
@@ -405,74 +402,74 @@ class AccountsController(TransactionBase):
"advance_amount": flt(d.amount), "advance_amount": flt(d.amount),
"allocate_amount": 0 "allocate_amount": 0
}) })
def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield): def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield):
from erpnext.controllers.status_updater import get_tolerance_for from erpnext.controllers.status_updater import get_tolerance_for
item_tolerance = {} item_tolerance = {}
global_tolerance = None global_tolerance = None
for item in self.get("entries"): for item in self.get("entries"):
if item.get(item_ref_dn): 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)) item.get(item_ref_dn), based_on), self.precision(based_on, item))
if not ref_amt: 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")) ref_dt + _(" is zero, system will not check for over-billed"))
else: else:
already_billed = frappe.db.sql("""select sum(%s) from `tab%s` already_billed = frappe.db.sql("""select sum(%s) from `tab%s`
where %s=%s and docstatus=1 and parent != %s""" % where %s=%s and docstatus=1 and parent != %s""" %
(based_on, self.tname, item_ref_dn, '%s', '%s'), (based_on, self.tname, item_ref_dn, '%s', '%s'),
(item.get(item_ref_dn), self.name))[0][0] (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)) 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) item_tolerance, global_tolerance)
max_allowed_amt = flt(ref_amt * (100 + tolerance) / 100) max_allowed_amt = flt(ref_amt * (100 + tolerance) / 100)
if total_billed_amt - max_allowed_amt > 0.01: if total_billed_amt - max_allowed_amt > 0.01:
reduce_by = total_billed_amt - max_allowed_amt reduce_by = total_billed_amt - max_allowed_amt
frappe.throw(_("Row #") + cstr(item.idx) + ": " + frappe.throw(_("Row #") + cstr(item.idx) + ": " +
_(" Max amount allowed for Item ") + cstr(item.item_code) + _(" Max amount allowed for Item ") + cstr(item.item_code) +
_(" against ") + ref_dt + " " + _(" against ") + ref_dt + " " +
cstr(item.get(ref_dt.lower().replace(" ", "_"))) + _(" is ") + cstr(item.get(ref_dt.lower().replace(" ", "_"))) + _(" is ") +
cstr(max_allowed_amt) + ". \n" + cstr(max_allowed_amt) + ". \n" +
_("""If you want to increase your overflow tolerance, please increase \ _("""If you want to increase your overflow tolerance, please increase \
tolerance % in Global Defaults or Item master. tolerance % in Global Defaults or Item master.
Or, you must reduce the amount by """) + cstr(reduce_by) + "\n" + Or, you must reduce the amount by """) + cstr(reduce_by) + "\n" +
_("""Also, please check if the order item has already been billed \ _("""Also, please check if the order item has already been billed \
in the Sales Order""")) in the Sales Order"""))
def get_company_default(self, fieldname): def get_company_default(self, fieldname):
from erpnext.accounts.utils import get_company_default from erpnext.accounts.utils import get_company_default
return get_company_default(self.company, fieldname) return get_company_default(self.company, fieldname)
def get_stock_items(self): def get_stock_items(self):
stock_items = [] 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))) self.get(self.fname)))
if item_codes: if item_codes:
stock_items = [r[0] for r in frappe.db.sql("""select name stock_items = [r[0] for r in frappe.db.sql("""select name
from `tabItem` where name in (%s) and is_stock_item='Yes'""" % \ from `tabItem` where name in (%s) and is_stock_item='Yes'""" % \
(", ".join((["%s"]*len(item_codes))),), item_codes)] (", ".join((["%s"]*len(item_codes))),), item_codes)]
return stock_items return stock_items
@property @property
def company_abbr(self): def company_abbr(self):
if not hasattr(self, "_abbr"): if not hasattr(self, "_abbr"):
self._abbr = frappe.db.get_value("Company", self.company, "abbr") self._abbr = frappe.db.get_value("Company", self.company, "abbr")
return self._abbr return self._abbr
def check_credit_limit(self, account): def check_credit_limit(self, account):
total_outstanding = frappe.db.sql(""" 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) from `tabGL Entry` where account = %s""", account)
total_outstanding = total_outstanding[0][0] if total_outstanding else 0 total_outstanding = total_outstanding[0][0] if total_outstanding else 0
if total_outstanding: if total_outstanding:
frappe.get_doc('Account', account).check_credit_limit(total_outstanding) frappe.get_doc('Account', account).check_credit_limit(total_outstanding)
@@ -480,4 +477,4 @@ class AccountsController(TransactionBase):
@frappe.whitelist() @frappe.whitelist()
def get_tax_rate(account_head): def get_tax_rate(account_head):
return frappe.db.get_value("Account", account_head, "tax_rate") return frappe.db.get_value("Account", account_head, "tax_rate")

View File

@@ -13,52 +13,52 @@ class SellingController(StockController):
def onload_post_render(self): def onload_post_render(self):
# contact, address, item details and pos details (if applicable) # contact, address, item details and pos details (if applicable)
self.set_missing_values() self.set_missing_values()
def validate(self): def validate(self):
super(SellingController, self).validate() super(SellingController, self).validate()
self.validate_max_discount() self.validate_max_discount()
check_active_sales_items(self) check_active_sales_items(self)
def get_sender(self, comm): def get_sender(self, comm):
return frappe.db.get_value('Sales Email Settings', None, 'email_id') return frappe.db.get_value('Sales Email Settings', None, 'email_id')
def set_missing_values(self, for_validate=False): def set_missing_values(self, for_validate=False):
super(SellingController, self).set_missing_values(for_validate) super(SellingController, self).set_missing_values(for_validate)
# set contact and address details for customer, if they are not mentioned # set contact and address details for customer, if they are not mentioned
self.set_missing_lead_customer_details() self.set_missing_lead_customer_details()
self.set_price_list_and_item_details() self.set_price_list_and_item_details()
if self.get("__islocal"): if self.get("__islocal"):
self.set_taxes("other_charges", "taxes_and_charges") self.set_taxes("other_charges", "taxes_and_charges")
def set_missing_lead_customer_details(self): def set_missing_lead_customer_details(self):
if getattr(self, "customer", None): if getattr(self, "customer", None):
from erpnext.accounts.party import _get_party_details from erpnext.accounts.party import _get_party_details
self.update_if_missing(_get_party_details(self.customer, self.update_if_missing(_get_party_details(self.customer,
ignore_permissions=getattr(self, "ignore_permissions", None))) ignore_permissions=getattr(self, "ignore_permissions", None)))
elif getattr(self, "lead", None): elif getattr(self, "lead", None):
from erpnext.selling.doctype.lead.lead import get_lead_details from erpnext.selling.doctype.lead.lead import get_lead_details
self.update_if_missing(get_lead_details(self.lead)) self.update_if_missing(get_lead_details(self.lead))
def set_price_list_and_item_details(self): def set_price_list_and_item_details(self):
self.set_price_list_currency("Selling") self.set_price_list_currency("Selling")
self.set_missing_item_details() self.set_missing_item_details()
def apply_shipping_rule(self): def apply_shipping_rule(self):
if self.shipping_rule: if self.shipping_rule:
shipping_rule = frappe.get_doc("Shipping Rule", self.shipping_rule) shipping_rule = frappe.get_doc("Shipping Rule", self.shipping_rule)
value = self.net_total value = self.net_total
# TODO # TODO
# shipping rule calculation based on item's net weight # shipping rule calculation based on item's net weight
shipping_amount = 0.0 shipping_amount = 0.0
for condition in shipping_rule.get("shipping_rule_conditions"): for condition in shipping_rule.get("shipping_rule_conditions"):
if not condition.to_value or (flt(condition.from_value) <= value <= flt(condition.to_value)): if not condition.to_value or (flt(condition.from_value) <= value <= flt(condition.to_value)):
shipping_amount = condition.shipping_amount shipping_amount = condition.shipping_amount
break break
self.append("other_charges", { self.append("other_charges", {
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"charge_type": "Actual", "charge_type": "Actual",
@@ -67,86 +67,86 @@ class SellingController(StockController):
"description": shipping_rule.label, "description": shipping_rule.label,
"rate": shipping_amount "rate": shipping_amount
}) })
def set_total_in_words(self): def set_total_in_words(self):
from frappe.utils import money_in_words from frappe.utils import money_in_words
company_currency = get_company_currency(self.company) 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")) "disable_rounded_total"))
if self.meta.get_field("in_words"): 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) self.grand_total or self.rounded_total, company_currency)
if self.meta.get_field("in_words_export"): 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) self.grand_total_export or self.rounded_total_export, self.currency)
def calculate_taxes_and_totals(self): def calculate_taxes_and_totals(self):
self.other_fname = "other_charges" self.other_fname = "other_charges"
super(SellingController, self).calculate_taxes_and_totals() super(SellingController, self).calculate_taxes_and_totals()
self.calculate_total_advance("Sales Invoice", "advance_adjustment_details") self.calculate_total_advance("Sales Invoice", "advance_adjustment_details")
self.calculate_commission() self.calculate_commission()
self.calculate_contribution() self.calculate_contribution()
def determine_exclusive_rate(self): def determine_exclusive_rate(self):
if not any((cint(tax.included_in_print_rate) for tax in self.tax_doclist)): if not any((cint(tax.included_in_print_rate) for tax in self.tax_doclist)):
# no inclusive tax # no inclusive tax
return return
for item in self.item_doclist: for item in self.item_doclist:
item_tax_map = self._load_item_tax_rate(item.item_tax_rate) item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
cumulated_tax_fraction = 0 cumulated_tax_fraction = 0
for i, tax in enumerate(self.tax_doclist): for i, tax in enumerate(self.tax_doclist):
tax.tax_fraction_for_current_item = self.get_current_tax_fraction(tax, item_tax_map) tax.tax_fraction_for_current_item = self.get_current_tax_fraction(tax, item_tax_map)
if i==0: if i==0:
tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item
else: else:
tax.grand_total_fraction_for_current_item = \ tax.grand_total_fraction_for_current_item = \
self.tax_doclist[i-1].grand_total_fraction_for_current_item \ self.tax_doclist[i-1].grand_total_fraction_for_current_item \
+ tax.tax_fraction_for_current_item + tax.tax_fraction_for_current_item
cumulated_tax_fraction += tax.tax_fraction_for_current_item cumulated_tax_fraction += tax.tax_fraction_for_current_item
if cumulated_tax_fraction and not self.discount_amount_applied: if cumulated_tax_fraction and not self.discount_amount_applied:
item.base_amount = flt((item.amount * self.conversion_rate) / item.base_amount = flt((item.amount * self.conversion_rate) /
(1 + cumulated_tax_fraction), self.precision("base_amount", item)) (1 + cumulated_tax_fraction), self.precision("base_amount", item))
item.base_rate = flt(item.base_amount / item.qty, self.precision("base_rate", item)) item.base_rate = flt(item.base_amount / item.qty, self.precision("base_rate", item))
if item.discount_percentage == 100: if item.discount_percentage == 100:
item.base_price_list_rate = item.base_rate item.base_price_list_rate = item.base_rate
item.base_rate = 0.0 item.base_rate = 0.0
else: else:
item.base_price_list_rate = flt(item.base_rate / (1 - (item.discount_percentage / 100.0)), item.base_price_list_rate = flt(item.base_rate / (1 - (item.discount_percentage / 100.0)),
self.precision("base_price_list_rate", item)) self.precision("base_price_list_rate", item))
def get_current_tax_fraction(self, tax, item_tax_map): def get_current_tax_fraction(self, tax, item_tax_map):
""" """
Get tax fraction for calculating tax exclusive amount Get tax fraction for calculating tax exclusive amount
from tax inclusive amount from tax inclusive amount
""" """
current_tax_fraction = 0 current_tax_fraction = 0
if cint(tax.included_in_print_rate): if cint(tax.included_in_print_rate):
tax_rate = self._get_tax_rate(tax, item_tax_map) tax_rate = self._get_tax_rate(tax, item_tax_map)
if tax.charge_type == "On Net Total": if tax.charge_type == "On Net Total":
current_tax_fraction = tax_rate / 100.0 current_tax_fraction = tax_rate / 100.0
elif tax.charge_type == "On Previous Row Amount": elif tax.charge_type == "On Previous Row Amount":
current_tax_fraction = (tax_rate / 100.0) * \ current_tax_fraction = (tax_rate / 100.0) * \
self.tax_doclist[cint(tax.row_id) - 1].tax_fraction_for_current_item self.tax_doclist[cint(tax.row_id) - 1].tax_fraction_for_current_item
elif tax.charge_type == "On Previous Row Total": elif tax.charge_type == "On Previous Row Total":
current_tax_fraction = (tax_rate / 100.0) * \ current_tax_fraction = (tax_rate / 100.0) * \
self.tax_doclist[cint(tax.row_id) - 1].grand_total_fraction_for_current_item self.tax_doclist[cint(tax.row_id) - 1].grand_total_fraction_for_current_item
return current_tax_fraction return current_tax_fraction
def calculate_item_values(self): def calculate_item_values(self):
if not self.discount_amount_applied: if not self.discount_amount_applied:
for item in self.item_doclist: for item in self.item_doclist:
@@ -171,22 +171,22 @@ class SellingController(StockController):
for item in self.item_doclist: for item in self.item_doclist:
self.net_total += item.base_amount self.net_total += item.base_amount
self.net_total_export += item.amount self.net_total_export += item.amount
self.round_floats_in(self, ["net_total", "net_total_export"]) self.round_floats_in(self, ["net_total", "net_total_export"])
def calculate_totals(self): def calculate_totals(self):
self.grand_total = flt(self.tax_doclist and \ self.grand_total = flt(self.tax_doclist and \
self.tax_doclist[-1].total or self.net_total, self.precision("grand_total")) 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.precision("grand_total_export"))
self.other_charges_total = flt(self.grand_total - self.net_total, self.other_charges_total = flt(self.grand_total - self.net_total,
self.precision("other_charges_total")) self.precision("other_charges_total"))
self.other_charges_total_export = flt(self.grand_total_export - self.other_charges_total_export = flt(self.grand_total_export -
self.net_total_export + flt(self.discount_amount), self.net_total_export + flt(self.discount_amount),
self.precision("other_charges_total_export")) self.precision("other_charges_total_export"))
self.rounded_total = _round(self.grand_total) self.rounded_total = _round(self.grand_total)
self.rounded_total_export = _round(self.grand_total_export) self.rounded_total_export = _round(self.grand_total_export)
@@ -214,12 +214,12 @@ class SellingController(StockController):
flt(tax.rate) / 100 flt(tax.rate) / 100
actual_taxes_dict.setdefault(tax.idx, actual_tax_amount) 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")) self.precision("grand_total"))
return grand_total_for_discount_amount return grand_total_for_discount_amount
def calculate_outstanding_amount(self): def calculate_outstanding_amount(self):
# NOTE: # NOTE:
# write_off_amount is only for POS Invoice # write_off_amount is only for POS Invoice
# total_advance is only for non POS Invoice # total_advance is only for non POS Invoice
if self.doctype == "Sales Invoice" and self.docstatus == 0: 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 total_amount_to_pay = self.grand_total - self.write_off_amount
self.outstanding_amount = flt(total_amount_to_pay - self.total_advance \ self.outstanding_amount = flt(total_amount_to_pay - self.total_advance \
- self.paid_amount, self.precision("outstanding_amount")) - self.paid_amount, self.precision("outstanding_amount"))
def calculate_commission(self): def calculate_commission(self):
if self.meta.get_field("commission_rate"): if self.meta.get_field("commission_rate"):
self.round_floats_in(self, ["net_total", "commission_rate"]) self.round_floats_in(self, ["net_total", "commission_rate"])
if self.commission_rate > 100.0: 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) _("cannot be greater than 100"), raise_exception=True)
self.total_commission = flt(self.net_total * self.commission_rate / 100.0, self.total_commission = flt(self.net_total * self.commission_rate / 100.0,
self.precision("total_commission")) self.precision("total_commission"))
@@ -248,66 +248,66 @@ class SellingController(StockController):
sales_person.allocated_amount = flt( sales_person.allocated_amount = flt(
self.net_total * sales_person.allocated_percentage / 100.0, self.net_total * sales_person.allocated_percentage / 100.0,
self.precision("allocated_amount", sales_person)) self.precision("allocated_amount", sales_person))
total += sales_person.allocated_percentage total += sales_person.allocated_percentage
if sales_team and total != 100.0: if sales_team and total != 100.0:
msgprint(_("Total") + " " + msgprint(_("Total") + " " +
_(self.meta.get_label("allocated_percentage", parentfield="sales_team")) + _(self.meta.get_label("allocated_percentage", parentfield="sales_team")) +
" " + _("should be 100%"), raise_exception=True) " " + _("should be 100%"), raise_exception=True)
def validate_order_type(self): def validate_order_type(self):
valid_types = ["Sales", "Maintenance", "Shopping Cart"] valid_types = ["Sales", "Maintenance", "Shopping Cart"]
if not self.order_type: if not self.order_type:
self.order_type = "Sales" self.order_type = "Sales"
elif self.order_type not in valid_types: 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) _("must be one of") + ": " + comma_or(valid_types), raise_exception=True)
def check_credit(self, grand_total): 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") "master_name": self.customer}, "name")
if customer_account: if customer_account:
total_outstanding = frappe.db.sql("""select total_outstanding = frappe.db.sql("""select
sum(ifnull(debit, 0)) - sum(ifnull(credit, 0)) sum(ifnull(debit, 0)) - sum(ifnull(credit, 0))
from `tabGL Entry` where account = %s""", customer_account) from `tabGL Entry` where account = %s""", customer_account)
total_outstanding = total_outstanding[0][0] if total_outstanding else 0 total_outstanding = total_outstanding[0][0] if total_outstanding else 0
outstanding_including_current = flt(total_outstanding) + flt(grand_total) 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) outstanding_including_current)
def validate_max_discount(self): def validate_max_discount(self):
for d in self.get(self.fname): for d in self.get(self.fname):
discount = flt(frappe.db.get_value("Item", d.item_code, "max_discount")) discount = flt(frappe.db.get_value("Item", d.item_code, "max_discount"))
if discount and flt(d.discount_percentage) > 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)) _("discount on Item Code") + ": " + cstr(d.item_code))
def get_item_list(self): def get_item_list(self):
il = [] il = []
for d in self.get(self.fname): for d in self.get(self.fname):
reserved_warehouse = "" reserved_warehouse = ""
reserved_qty_for_main_item = 0 reserved_qty_for_main_item = 0
if self.doctype == "Sales Order": 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: 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")) d.item_code + _(" as it is stock Item or packing item"))
reserved_warehouse = d.warehouse reserved_warehouse = d.warehouse
if flt(d.qty) > flt(d.delivered_qty): if flt(d.qty) > flt(d.delivered_qty):
reserved_qty_for_main_item = 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. # 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 # 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) d.against_sales_order, d.prevdoc_detail_docname)
so_qty, reserved_warehouse = self.get_so_qty_and_warehouse(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: if already_delivered_qty + d.qty > so_qty:
reserved_qty_for_main_item = -(so_qty - already_delivered_qty) reserved_qty_for_main_item = -(so_qty - already_delivered_qty)
else: else:
@@ -341,15 +341,15 @@ class SellingController(StockController):
'name': d.name 'name': d.name
})) }))
return il return il
def has_sales_bom(self, item_code): 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) where new_item_code=%s and docstatus != 2""", item_code)
def get_already_delivered_qty(self, dn, so, so_detail): def get_already_delivered_qty(self, dn, so, so_detail):
qty = frappe.db.sql("""select sum(qty) from `tabDelivery Note Item` qty = frappe.db.sql("""select sum(qty) from `tabDelivery Note Item`
where prevdoc_detail_docname = %s and docstatus = 1 where prevdoc_detail_docname = %s and docstatus = 1
and against_sales_order = %s and against_sales_order = %s
and parent != %s""", (so_detail, so, dn)) and parent != %s""", (so_detail, so, dn))
return qty and flt(qty[0][0]) or 0.0 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_qty = so_item and flt(so_item[0]["qty"]) or 0.0
so_warehouse = so_item and so_item[0]["warehouse"] or "" so_warehouse = so_item and so_item[0]["warehouse"] or ""
return so_qty, so_warehouse return so_qty, so_warehouse
def check_stop_sales_order(self, ref_fieldname): def check_stop_sales_order(self, ref_fieldname):
for d in self.get(self.fname): for d in self.get(self.fname):
if d.get(ref_fieldname): if d.get(ref_fieldname):
status = frappe.db.get_value("Sales Order", d.get(ref_fieldname), "status") status = frappe.db.get_value("Sales Order", d.get(ref_fieldname), "status")
if status == "Stopped": if status == "Stopped":
frappe.throw(self.doctype + frappe.throw(self.doctype +
_(" can not be created/modified against stopped Sales Order ") + _(" can not be created/modified against stopped Sales Order ") +
d.get(ref_fieldname)) d.get(ref_fieldname))
def check_active_sales_items(obj): def check_active_sales_items(obj):
for d in obj.get(obj.fname): for d in obj.get(obj.fname):
if d.item_code: if d.item_code:
item = frappe.db.sql("""select docstatus, is_sales_item, item = frappe.db.sql("""select docstatus, is_sales_item,
is_service_item, income_account from tabItem where name = %s""", is_service_item, income_account from tabItem where name = %s""",
d.item_code, as_dict=True)[0] d.item_code, as_dict=True)[0]
if item.is_sales_item == 'No' and item.is_service_item == 'No': if item.is_sales_item == 'No' and item.is_service_item == 'No':
frappe.throw(_("Item is neither Sales nor Service Item") + ": " + d.item_code) frappe.throw(_("Item is neither Sales nor Service Item") + ": " + d.item_code)
if getattr(d, "income_account", None) and not item.income_account: 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) d.income_account)

View File

@@ -14,15 +14,15 @@ from frappe.model.document import Document
class BOM(Document): class BOM(Document):
def autoname(self): 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('"', '\\"')) where name like "BOM/%s/%%" """ % cstr(self.item).replace('"', '\\"'))
if last_name: if last_name:
idx = cint(cstr(last_name[0][0]).split('/')[-1].split('-')[0]) + 1 idx = cint(cstr(last_name[0][0]).split('/')[-1].split('-')[0]) + 1
else: else:
idx = 1 idx = 1
self.name = 'BOM/' + self.item + ('/%.3i' % idx) self.name = 'BOM/' + self.item + ('/%.3i' % idx)
def validate(self): def validate(self):
self.clear_operations() self.clear_operations()
self.validate_main_item() self.validate_main_item()
@@ -34,12 +34,11 @@ class BOM(Document):
self.validate_materials() self.validate_materials()
self.set_bom_material_details() self.set_bom_material_details()
self.calculate_cost() self.calculate_cost()
def on_update(self): def on_update(self):
self.check_recursion() self.check_recursion()
self.update_exploded_items() self.update_exploded_items()
self.db_update()
def on_submit(self): def on_submit(self):
self.manage_default_bom() self.manage_default_bom()
@@ -50,48 +49,48 @@ class BOM(Document):
# check if used in any other bom # check if used in any other bom
self.validate_bom_links() self.validate_bom_links()
self.manage_default_bom() self.manage_default_bom()
def on_update_after_submit(self): def on_update_after_submit(self):
self.validate_bom_links() self.validate_bom_links()
self.manage_default_bom() self.manage_default_bom()
def get_item_det(self, item_code): def get_item_det(self, item_code):
item = frappe.db.sql("""select name, is_asset_item, is_purchase_item, item = frappe.db.sql("""select name, is_asset_item, is_purchase_item,
docstatus, description, is_sub_contracted_item, stock_uom, default_bom, docstatus, description, is_sub_contracted_item, stock_uom, default_bom,
last_purchase_rate, standard_rate, is_manufactured_item last_purchase_rate, standard_rate, is_manufactured_item
from `tabItem` where name=%s""", item_code, as_dict = 1) from `tabItem` where name=%s""", item_code, as_dict = 1)
return item return item
def validate_rm_item(self, item): def validate_rm_item(self, item):
if item[0]['name'] == 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) item[0]['name'], raise_exception=1)
if not item or item[0]['docstatus'] == 2: if not item or item[0]['docstatus'] == 2:
msgprint("Item %s does not exist in system" % item[0]['item_code'], raise_exception = 1) msgprint("Item %s does not exist in system" % item[0]['item_code'], raise_exception = 1)
def set_bom_material_details(self): def set_bom_material_details(self):
for item in self.get("bom_materials"): 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}) "qty": item.qty})
for r in ret: for r in ret:
if not item.get(r): if not item.get(r):
item.set(r, ret[r]) item.set(r, ret[r])
def get_bom_material_detail(self, args=None): def get_bom_material_detail(self, args=None):
""" Get raw material details like uom, desc and rate""" """ Get raw material details like uom, desc and rate"""
if not args: if not args:
args = frappe.form_dict.get('args') args = frappe.form_dict.get('args')
if isinstance(args, basestring): if isinstance(args, basestring):
import json import json
args = json.loads(args) args = json.loads(args)
item = self.get_item_det(args['item_code']) item = self.get_item_det(args['item_code'])
self.validate_rm_item(item) self.validate_rm_item(item)
args['bom_no'] = args['bom_no'] or item and cstr(item[0]['default_bom']) or '' args['bom_no'] = args['bom_no'] or item and cstr(item[0]['default_bom']) or ''
args.update(item[0]) args.update(item[0])
@@ -117,27 +116,23 @@ class BOM(Document):
elif self.rm_cost_as_per == "Price List": elif self.rm_cost_as_per == "Price List":
if not self.buying_price_list: if not self.buying_price_list:
frappe.throw(_("Please select 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 "item_code": arg["item_code"]}, "price_list_rate") or 0
elif self.rm_cost_as_per == 'Standard Rate': elif self.rm_cost_as_per == 'Standard Rate':
rate = arg['standard_rate'] rate = arg['standard_rate']
return rate return rate
def update_cost(self): def update_cost(self):
for d in self.get("bom_materials"): for d in self.get("bom_materials"):
d.rate = self.get_bom_material_detail({ d.rate = self.get_bom_material_detail({
'item_code': d.item_code, 'item_code': d.item_code,
'bom_no': d.bom_no, 'bom_no': d.bom_no,
'qty': d.qty 'qty': d.qty
})["rate"] })["rate"]
if self.docstatus == 0: if self.docstatus in (0, 1):
self.save() self.save()
elif self.docstatus == 1:
self.calculate_cost()
self.update_exploded_items()
self.update_after_submit()
def get_bom_unitcost(self, bom_no): def get_bom_unitcost(self, bom_no):
bom = frappe.db.sql("""select name, total_cost/quantity as unit_cost from `tabBOM` 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 return bom and bom[0]['unit_cost'] or 0
def get_valuation_rate(self, args): def get_valuation_rate(self, args):
""" Get average valuation rate of relevant warehouses """ Get average valuation rate of relevant warehouses
as per valuation method (MAR/FIFO) as per valuation method (MAR/FIFO)
as on costing date as on costing date
""" """
from erpnext.stock.utils import get_incoming_rate from erpnext.stock.utils import get_incoming_rate
dt = self.costing_date or nowdate() dt = self.costing_date or nowdate()
@@ -168,19 +163,19 @@ class BOM(Document):
return rate and flt(sum(rate))/len(rate) or 0 return rate and flt(sum(rate))/len(rate) or 0
def manage_default_bom(self): 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 update default bom in item master
""" """
if self.is_default and self.is_active: if self.is_default and self.is_active:
from frappe.model.utils import set_default from frappe.model.utils import set_default
set_default(self, "item") set_default(self, "item")
frappe.db.set_value("Item", self.item, "default_bom", self.name) frappe.db.set_value("Item", self.item, "default_bom", self.name)
else: else:
if not self.is_active: if not self.is_active:
frappe.db.set(self, "is_default", 0) 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)) (self.item, self.name))
def clear_operations(self): def clear_operations(self):
@@ -193,7 +188,7 @@ class BOM(Document):
""" Validate main FG item""" """ Validate main FG item"""
item = self.get_item_det(self.item) item = self.get_item_det(self.item)
if not 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) self.item, raise_exception = 1)
elif item[0]['is_manufactured_item'] != 'Yes' \ elif item[0]['is_manufactured_item'] != 'Yes' \
and item[0]['is_sub_contracted_item'] != 'Yes': and item[0]['is_sub_contracted_item'] != 'Yes':
@@ -209,7 +204,7 @@ class BOM(Document):
self.op = [] self.op = []
for d in self.get('bom_operations'): for d in self.get('bom_operations'):
if cstr(d.operation_no) in self.op: 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) d.operation_no, raise_exception=1)
else: else:
# add operation in op list # add operation in op list
@@ -222,36 +217,36 @@ class BOM(Document):
# check if operation no not in op table # check if operation no not in op table
if self.with_operations and cstr(m.operation_no) not in self.op: if self.with_operations and cstr(m.operation_no) not in self.op:
msgprint("""Operation no: %s against item: %s at row no: %s \ 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) (m.operation_no, m.item_code, m.idx), raise_exception = 1)
item = self.get_item_det(m.item_code) item = self.get_item_det(m.item_code)
if item[0]['is_manufactured_item'] == 'Yes': if item[0]['is_manufactured_item'] == 'Yes':
if not m.bom_no: 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) (m.item_code, m.idx), raise_exception=1)
else: else:
self.validate_bom_no(m.item_code, m.bom_no, m.idx) self.validate_bom_no(m.item_code, m.bom_no, m.idx)
elif m.bom_no: elif m.bom_no:
msgprint("""As Item %s is not a manufactured / sub-contracted item, \ 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) (m.item_code, m.idx), raise_exception = 1)
if flt(m.qty) <= 0: 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) (m.item_code, m.idx), raise_exception = 1)
self.check_if_item_repeated(m.item_code, m.operation_no, check_list) self.check_if_item_repeated(m.item_code, m.operation_no, check_list)
def validate_bom_no(self, item, bom_no, idx): def validate_bom_no(self, item, bom_no, idx):
"""Validate BOM No of sub-contracted items""" """Validate BOM No of sub-contracted items"""
bom = frappe.db.sql("""select name from `tabBOM` where name = %s and item = %s bom = frappe.db.sql("""select name from `tabBOM` where name = %s and item = %s
and is_active=1 and docstatus=1""", and is_active=1 and docstatus=1""",
(bom_no, item), as_dict =1) (bom_no, item), as_dict =1)
if not bom: if not bom:
msgprint("""Incorrect BOM No: %s against item: %s at row no: %s. 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) (bom_no, item, idx), raise_exception = 1)
def check_if_item_repeated(self, item, op, check_list): def check_if_item_repeated(self, item, op, check_list):
@@ -268,7 +263,7 @@ class BOM(Document):
for d in check_list: for d in check_list:
bom_list, count = [self.name], 0 bom_list, count = [self.name], 0
while (len(bom_list) > count ): 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])) (d[0], d[1], '%s'), cstr(bom_list[count]))
count = count + 1 count = count + 1
for b in boms: for b in boms:
@@ -277,24 +272,24 @@ class BOM(Document):
""" % (cstr(b[0]), cstr(d[2]), self.name), raise_exception = 1) """ % (cstr(b[0]), cstr(d[2]), self.name), raise_exception = 1)
if b[0]: if b[0]:
bom_list.append(b[0]) bom_list.append(b[0])
def update_cost_and_exploded_items(self, bom_list=[]): def update_cost_and_exploded_items(self, bom_list=[]):
bom_list = self.traverse_tree(bom_list) bom_list = self.traverse_tree(bom_list)
for bom in bom_list: for bom in bom_list:
bom_obj = frappe.get_doc("BOM", bom) bom_obj = frappe.get_doc("BOM", bom)
bom_obj.on_update() bom_obj.on_update()
return bom_list return bom_list
def traverse_tree(self, bom_list=[]): def traverse_tree(self, bom_list=[]):
def _get_children(bom_no): 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)] where parent = %s and ifnull(bom_no, '') != ''""", bom_no)]
count = 0 count = 0
if self.name not in bom_list: if self.name not in bom_list:
bom_list.append(self.name) bom_list.append(self.name)
while(count < len(bom_list)): while(count < len(bom_list)):
for child_bom in _get_children(bom_list[count]): for child_bom in _get_children(bom_list[count]):
if child_bom not in bom_list: if child_bom not in bom_list:
@@ -302,7 +297,7 @@ class BOM(Document):
count += 1 count += 1
bom_list.reverse() bom_list.reverse()
return bom_list return bom_list
def calculate_cost(self): def calculate_cost(self):
"""Calculate bom totals""" """Calculate bom totals"""
self.calculate_op_cost() 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 d.operating_cost = flt(d.hour_rate) * flt(d.time_in_mins) / 60.0
total_op_cost += flt(d.operating_cost) total_op_cost += flt(d.operating_cost)
self.operating_cost = total_op_cost self.operating_cost = total_op_cost
def calculate_rm_cost(self): def calculate_rm_cost(self):
"""Fetch RM rate as per today's valuation rate and calculate totals""" """Fetch RM rate as per today's valuation rate and calculate totals"""
total_rm_cost = 0 total_rm_cost = 0
@@ -329,7 +324,7 @@ class BOM(Document):
d.amount = flt(d.rate) * flt(d.qty) d.amount = flt(d.rate) * flt(d.qty)
d.qty_consumed_per_unit = flt(d.qty) / flt(self.quantity) d.qty_consumed_per_unit = flt(d.qty) / flt(self.quantity)
total_rm_cost += d.amount total_rm_cost += d.amount
self.raw_material_cost = total_rm_cost self.raw_material_cost = total_rm_cost
def update_exploded_items(self): def update_exploded_items(self):
@@ -345,38 +340,38 @@ class BOM(Document):
self.get_child_exploded_items(d.bom_no, d.qty) self.get_child_exploded_items(d.bom_no, d.qty)
else: else:
self.add_to_cur_exploded_items(frappe._dict({ self.add_to_cur_exploded_items(frappe._dict({
'item_code' : d.item_code, 'item_code' : d.item_code,
'description' : d.description, 'description' : d.description,
'stock_uom' : d.stock_uom, 'stock_uom' : d.stock_uom,
'qty' : flt(d.qty), 'qty' : flt(d.qty),
'rate' : flt(d.rate), 'rate' : flt(d.rate),
})) }))
def add_to_cur_exploded_items(self, args): def add_to_cur_exploded_items(self, args):
if self.cur_exploded_items.get(args.item_code): if self.cur_exploded_items.get(args.item_code):
self.cur_exploded_items[args.item_code]["qty"] += args.qty self.cur_exploded_items[args.item_code]["qty"] += args.qty
else: else:
self.cur_exploded_items[args.item_code] = args self.cur_exploded_items[args.item_code] = args
def get_child_exploded_items(self, bom_no, qty): def get_child_exploded_items(self, bom_no, qty):
""" Add all items from Flat BOM of child BOM""" """ Add all items from Flat BOM of child BOM"""
child_fb_items = frappe.db.sql("""select item_code, description, stock_uom, qty, rate, child_fb_items = frappe.db.sql("""select item_code, description, stock_uom, qty, rate,
qty_consumed_per_unit from `tabBOM Explosion Item` qty_consumed_per_unit from `tabBOM Explosion Item`
where parent = %s and docstatus = 1""", bom_no, as_dict = 1) where parent = %s and docstatus = 1""", bom_no, as_dict = 1)
for d in child_fb_items: for d in child_fb_items:
self.add_to_cur_exploded_items(frappe._dict({ self.add_to_cur_exploded_items(frappe._dict({
'item_code' : d['item_code'], 'item_code' : d['item_code'],
'description' : d['description'], 'description' : d['description'],
'stock_uom' : d['stock_uom'], 'stock_uom' : d['stock_uom'],
'qty' : flt(d['qty_consumed_per_unit'])*qty, 'qty' : flt(d['qty_consumed_per_unit'])*qty,
'rate' : flt(d['rate']), 'rate' : flt(d['rate']),
})) }))
def add_exploded_items(self): def add_exploded_items(self):
"Add items to Flat BOM table" "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: for d in self.cur_exploded_items:
ch = self.append('flat_bom_details', {}) ch = self.append('flat_bom_details', {})
for i in self.cur_exploded_items[d].keys(): 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.amount = flt(ch.qty) * flt(ch.rate)
ch.qty_consumed_per_unit = flt(ch.qty) / flt(self.quantity) ch.qty_consumed_per_unit = flt(ch.qty) / flt(self.quantity)
ch.docstatus = self.docstatus ch.docstatus = self.docstatus
ch.db_update() ch.db_insert()
def validate_bom_links(self): def validate_bom_links(self):
if not self.is_active: if not self.is_active:
@@ -399,26 +394,27 @@ class BOM(Document):
raise_exception=1) raise_exception=1)
def get_bom_items_as_dict(bom, qty=1, fetch_exploded=1): def get_bom_items_as_dict(bom, qty=1, fetch_exploded=1):
import json
item_dict = {} item_dict = {}
query = """select query = """select
bom_item.item_code, bom_item.item_code,
item.item_name, item.item_name,
ifnull(sum(bom_item.qty_consumed_per_unit),0) * %(qty)s as qty, ifnull(sum(bom_item.qty_consumed_per_unit),0) * %(qty)s as qty,
item.description, item.description,
item.stock_uom, item.stock_uom,
item.default_warehouse, item.default_warehouse,
item.expense_account as expense_account, item.expense_account as expense_account,
item.buying_cost_center as cost_center item.buying_cost_center as cost_center
from from
`tab%(table)s` bom_item, `tabItem` item `tab%(table)s` bom_item, `tabItem` item
where where
bom_item.docstatus < 2 bom_item.docstatus < 2
and bom_item.parent = "%(bom)s" and bom_item.parent = "%(bom)s"
and item.name = bom_item.item_code and item.name = bom_item.item_code
%(conditions)s %(conditions)s
group by item_code, stock_uom""" group by item_code, stock_uom"""
if fetch_exploded: if fetch_exploded:
items = frappe.db.sql(query % { items = frappe.db.sql(query % {
"qty": qty, "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) item_dict[item.item_code]["qty"] += flt(item.qty)
else: else:
item_dict[item.item_code] = item item_dict[item.item_code] = item
return item_dict return item_dict
@frappe.whitelist() @frappe.whitelist()

View File

@@ -12,19 +12,19 @@ class TestBOM(unittest.TestCase):
def test_get_items(self): def test_get_items(self):
from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict 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) 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]["bom_materials"][0]["item_code"] in items_dict)
self.assertTrue(test_records[2][2]["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) self.assertEquals(len(items_dict.values()), 2)
def test_get_items_exploded(self): def test_get_items_exploded(self):
from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict 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) 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.assertTrue(test_records[2]["bom_materials"][0]["item_code"] in items_dict)
self.assertFalse(test_records[2][2]["item_code"] in items_dict) self.assertFalse(test_records[2]["bom_materials"][1]["item_code"] in items_dict)
self.assertTrue(test_records[0][1]["item_code"] in items_dict) self.assertTrue(test_records[0]["bom_materials"][0]["item_code"] in items_dict)
self.assertTrue(test_records[0][2]["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) self.assertEquals(len(items_dict.values()), 3)
def test_get_items_list(self): def test_get_items_list(self):
from erpnext.manufacturing.doctype.bom.bom import get_bom_items 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) self.assertEquals(len(get_bom_items(bom="BOM/_Test FG Item 2/001", qty=1, fetch_exploded=1)), 3)

View File

@@ -16,51 +16,53 @@ class TestProductionOrder(unittest.TestCase):
frappe.db.sql("delete from `tabStock Ledger Entry`") frappe.db.sql("delete from `tabStock Ledger Entry`")
frappe.db.sql("""delete from `tabBin`""") frappe.db.sql("""delete from `tabBin`""")
frappe.db.sql("""delete from `tabGL Entry`""") frappe.db.sql("""delete from `tabGL Entry`""")
pro_doc = frappe.copy_doc(test_records[0]) pro_doc = frappe.copy_doc(test_records[0])
pro_doc.insert() pro_doc.insert()
pro_doc.submit() pro_doc.submit()
from erpnext.stock.doctype.stock_entry.test_stock_entry import test_records as se_test_records 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 = frappe.copy_doc(se_test_records[0])
mr1.insert() mr1.insert()
mr1.submit() mr1.submit()
mr2 = frappe.copy_doc(se_test_records[0]) mr2 = frappe.copy_doc(se_test_records[0])
mr2.get("mtn_details")[0].item_code = "_Test Item Home Desktop 100" mr2.get("mtn_details")[0].item_code = "_Test Item Home Desktop 100"
mr2.insert() mr2.insert()
mr2.submit() mr2.submit()
stock_entry = make_stock_entry(pro_doc.name, "Manufacture/Repack") stock_entry = make_stock_entry(pro_doc.name, "Manufacture/Repack")
stock_entry = frappe.get_doc(stock_entry) stock_entry = frappe.get_doc(stock_entry)
stock_entry.fiscal_year = "_Test Fiscal Year 2013" stock_entry.fiscal_year = "_Test Fiscal Year 2013"
stock_entry.fg_completed_qty = 4 stock_entry.fg_completed_qty = 4
stock_entry.posting_date = "2013-05-12" stock_entry.posting_date = "2013-05-12"
stock_entry.fiscal_year = "_Test Fiscal Year 2013" stock_entry.fiscal_year = "_Test Fiscal Year 2013"
stock_entry.set("mtn_details", [])
stock_entry.run_method("get_items") stock_entry.run_method("get_items")
stock_entry.submit() 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) "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) "warehouse": "_Test Warehouse 1 - _TC"}, "planned_qty"), 6)
return pro_doc.name return pro_doc.name
def test_over_production(self): def test_over_production(self):
from erpnext.stock.doctype.stock_entry.stock_entry import StockOverProductionError from erpnext.stock.doctype.stock_entry.stock_entry import StockOverProductionError
pro_order = self.test_planned_qty() pro_order = self.test_planned_qty()
stock_entry = make_stock_entry(pro_order, "Manufacture/Repack") stock_entry = make_stock_entry(pro_order, "Manufacture/Repack")
stock_entry = frappe.get_doc(stock_entry) stock_entry = frappe.get_doc(stock_entry)
stock_entry.posting_date = "2013-05-12" stock_entry.posting_date = "2013-05-12"
stock_entry.fiscal_year = "_Test Fiscal Year 2013" stock_entry.fiscal_year = "_Test Fiscal Year 2013"
stock_entry.fg_completed_qty = 15 stock_entry.fg_completed_qty = 15
stock_entry.set("mtn_details", [])
stock_entry.run_method("get_items") stock_entry.run_method("get_items")
stock_entry.insert() stock_entry.insert()
self.assertRaises(StockOverProductionError, stock_entry.submit)
test_records = frappe.get_test_records('Production Order') self.assertRaises(StockOverProductionError, stock_entry.submit)
test_records = frappe.get_test_records('Production Order')

View File

@@ -7,8 +7,9 @@ import unittest
from erpnext.projects.doctype.time_log.time_log import OverlapError from erpnext.projects.doctype.time_log.time_log import OverlapError
class TestTimeLog(unittest.TestCase): class TestTimeLog(unittest.TestCase):
def test_duplication(self): def test_duplication(self):
ts = frappe.get_doc(frappe.copy_doc(test_records[0])) ts = frappe.get_doc(frappe.copy_doc(test_records[0]))
self.assertRaises(OverlapError, ts.insert) self.assertRaises(OverlapError, ts.insert)
test_records = frappe.get_test_records('Time Log') test_records = frappe.get_test_records('Time Log')
test_ignore = ["Time Log Batch", "Sales Invoice"]

View File

@@ -14,10 +14,10 @@ class TimeLogBatchTest(unittest.TestCase):
}) })
time_log.insert() time_log.insert()
time_log.submit() time_log.submit()
self.assertEquals(frappe.db.get_value("Time Log", time_log.name, "status"), "Submitted") self.assertEquals(frappe.db.get_value("Time Log", time_log.name, "status"), "Submitted")
tlb = frappe.copy_doc(test_records[0]) 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.insert()
tlb.submit() tlb.submit()
@@ -25,4 +25,6 @@ class TimeLogBatchTest(unittest.TestCase):
tlb.cancel() tlb.cancel()
self.assertEquals(frappe.db.get_value("Time Log", time_log.name, "status"), "Submitted") self.assertEquals(frappe.db.get_value("Time Log", time_log.name, "status"), "Submitted")
test_records = frappe.get_test_records('Time Log Batch') test_records = frappe.get_test_records('Time Log Batch')
test_dependencies = ["Time Log"]
test_ignore = ["Sales Invoice"]

View File

@@ -31,17 +31,17 @@ class TimeLogBatch(Document):
if tl.status != "Submitted" and self.docstatus == 0: if tl.status != "Submitted" and self.docstatus == 0:
frappe.msgprint(_("Time Log must have status 'Submitted'") + \ frappe.msgprint(_("Time Log must have status 'Submitted'") + \
" :" + tl.name + " (" + _(tl.status) + ")", raise_exception=True) " :" + tl.name + " (" + _(tl.status) + ")", raise_exception=True)
def set_status(self): def set_status(self):
self.status = { self.status = {
"0": "Draft", "0": "Draft",
"1": "Submitted", "1": "Submitted",
"2": "Cancelled" "2": "Cancelled"
}[str(self.docstatus or 0)] }[str(self.docstatus or 0)]
if self.sales_invoice: if self.sales_invoice:
self.status = "Billed" self.status = "Billed"
def on_submit(self): def on_submit(self):
self.update_status(self.name) self.update_status(self.name)
@@ -57,4 +57,4 @@ class TimeLogBatch(Document):
tl = frappe.get_doc("Time Log", d.time_log) tl = frappe.get_doc("Time Log", d.time_log)
tl.time_log_batch = time_log_batch tl.time_log_batch = time_log_batch
tl.sales_invoice = self.sales_invoice tl.sales_invoice = self.sales_invoice
tl.update_after_submit() tl.save()

View File

@@ -12,7 +12,7 @@ from erpnext.utilities.transaction_base import TransactionBase
from erpnext.accounts.party import create_party_account from erpnext.accounts.party import create_party_account
class Customer(TransactionBase): class Customer(TransactionBase):
def autoname(self): def autoname(self):
cust_master_name = frappe.defaults.get_global_default('cust_master_name') cust_master_name = frappe.defaults.get_global_default('cust_master_name')
if cust_master_name == 'Customer Name': if cust_master_name == 'Customer Name':
@@ -24,7 +24,7 @@ class Customer(TransactionBase):
def get_company_abbr(self): def get_company_abbr(self):
return frappe.db.get_value('Company', self.company, 'abbr') return frappe.db.get_value('Company', self.company, 'abbr')
def validate_values(self): def validate_values(self):
if frappe.defaults.get_global_default('cust_master_name') == 'Naming Series' and not self.naming_series: if frappe.defaults.get_global_default('cust_master_name') == 'Naming Series' and not self.naming_series:
frappe.throw("Series is Mandatory.", frappe.MandatoryError) 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) frappe.db.sql("update `tabLead` set status='Converted' where name = %s", self.lead_name)
def update_address(self): 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)) where customer=%s""", (self.customer_name, self.name))
def update_contact(self): 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)) where customer=%s""", (self.customer_name, self.name))
def update_credit_days_limit(self): def update_credit_days_limit(self):
frappe.db.sql("""update tabAccount set credit_days = %s, credit_limit = %s frappe.db.sql("""update tabAccount set credit_days = %s, credit_limit = %s
where master_type='Customer' and master_name = %s""", where master_type='Customer' and master_name = %s""",
(self.credit_days or 0, self.credit_limit or 0, self.name)) (self.credit_days or 0, self.credit_limit or 0, self.name))
def create_lead_address_contact(self): def create_lead_address_contact(self):
if self.lead_name: if self.lead_name:
if not frappe.db.get_value("Address", {"lead": self.lead_name, "customer": self.customer}): 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""", frappe.db.sql("""update `tabAddress` set customer=%s, customer_name=%s where lead=%s""",
(self.name, self.customer_name, self.lead_name)) (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) 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 = frappe.new_doc('Contact')
c.set("__islocal", 1) c.first_name = lead.lead_name
c.first_name = lead.lead_name
c.email_id = lead.email_id c.email_id = lead.email_id
c.phone = lead.phone c.phone = lead.phone
c.mobile_no = lead.mobile_no c.mobile_no = lead.mobile_no
@@ -72,7 +71,7 @@ class Customer(TransactionBase):
def on_update(self): def on_update(self):
self.validate_name_with_customer_group() self.validate_name_with_customer_group()
self.update_lead_status() self.update_lead_status()
self.update_address() self.update_address()
self.update_contact() self.update_contact()
@@ -84,29 +83,29 @@ class Customer(TransactionBase):
self.update_credit_days_limit() self.update_credit_days_limit()
#create address and contact from lead #create address and contact from lead
self.create_lead_address_contact() self.create_lead_address_contact()
def validate_name_with_customer_group(self): def validate_name_with_customer_group(self):
if frappe.db.exists("Customer Group", self.name): if frappe.db.exists("Customer Group", self.name):
frappe.msgprint("An Customer Group exists with same name (%s), \ 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) self.name, raise_exception=1)
def delete_customer_address(self): def delete_customer_address(self):
addresses = frappe.db.sql("""select name, lead from `tabAddress` addresses = frappe.db.sql("""select name, lead from `tabAddress`
where customer=%s""", (self.name,)) where customer=%s""", (self.name,))
for name, lead in addresses: for name, lead in addresses:
if lead: if lead:
frappe.db.sql("""update `tabAddress` set customer=null, customer_name=null frappe.db.sql("""update `tabAddress` set customer=null, customer_name=null
where name=%s""", name) where name=%s""", name)
else: else:
frappe.db.sql("""delete from `tabAddress` where name=%s""", name) frappe.db.sql("""delete from `tabAddress` where name=%s""", name)
def delete_customer_contact(self): 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): where customer=%s""", self.name):
frappe.delete_doc("Contact", contact) frappe.delete_doc("Contact", contact)
def delete_customer_account(self): def delete_customer_account(self):
"""delete customer's ledger if exist and check balance before deletion""" """delete customer's ledger if exist and check balance before deletion"""
acc = frappe.db.sql("select name from `tabAccount` where master_type = 'Customer' \ acc = frappe.db.sql("select name from `tabAccount` where master_type = 'Customer' \
@@ -120,7 +119,7 @@ class Customer(TransactionBase):
self.delete_customer_account() self.delete_customer_account()
if self.lead_name: if self.lead_name:
frappe.db.sql("update `tabLead` set status='Interested' where name=%s",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): def before_rename(self, olddn, newdn, merge=False):
from erpnext.accounts.utils import rename_account_for from erpnext.accounts.utils import rename_account_for
rename_account_for("Customer", olddn, newdn, merge, self.company) rename_account_for("Customer", olddn, newdn, merge, self.company)
@@ -134,7 +133,7 @@ class Customer(TransactionBase):
self.update_customer_address(newdn, set_field) self.update_customer_address(newdn, set_field)
def update_customer_address(self, 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"""\ {set_field} where customer=%(newdn)s"""\
.format(set_field=set_field), ({"newdn": newdn})) .format(set_field=set_field), ({"newdn": newdn}))
@@ -142,21 +141,21 @@ class Customer(TransactionBase):
def get_dashboard_info(customer): def get_dashboard_info(customer):
if not frappe.has_permission("Customer", "read", customer): if not frappe.has_permission("Customer", "read", customer):
frappe.msgprint("No Permission", raise_exception=True) frappe.msgprint("No Permission", raise_exception=True)
out = {} out = {}
for doctype in ["Opportunity", "Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]: 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(*)") {"customer": customer, "docstatus": ["!=", 2] }, "count(*)")
billing = frappe.db.sql("""select sum(grand_total), sum(outstanding_amount) billing = frappe.db.sql("""select sum(grand_total), sum(outstanding_amount)
from `tabSales Invoice` from `tabSales Invoice`
where customer=%s where customer=%s
and docstatus = 1 and docstatus = 1
and fiscal_year = %s""", (customer, frappe.db.get_default("fiscal_year"))) and fiscal_year = %s""", (customer, frappe.db.get_default("fiscal_year")))
out["total_billing"] = billing[0][0] out["total_billing"] = billing[0][0]
out["total_unpaid"] = billing[0][1] out["total_unpaid"] = billing[0][1]
return out return out
@@ -165,11 +164,11 @@ def get_customer_list(doctype, txt, searchfield, start, page_len, filters):
fields = ["name", "customer_group", "territory"] fields = ["name", "customer_group", "territory"]
else: else:
fields = ["name", "customer_name", "customer_group", "territory"] fields = ["name", "customer_name", "customer_group", "territory"]
return frappe.db.sql("""select %s from `tabCustomer` where docstatus < 2 return frappe.db.sql("""select %s from `tabCustomer` where docstatus < 2
and (%s like %s or customer_name like %s) order by and (%s like %s or customer_name like %s) order by
case when name like %s then 0 else 1 end, case when name like %s then 0 else 1 end,
case when customer_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""" % name, customer_name limit %s, %s""" %
(", ".join(fields), searchfield, "%s", "%s", "%s", "%s", "%s", "%s"), (", ".join(fields), searchfield, "%s", "%s", "%s", "%s", "%s", "%s"),
("%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, start, page_len)) ("%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, start, page_len))

View File

@@ -96,7 +96,7 @@ def _make_customer(source_name, target_doc=None, ignore_permissions=False):
} }
}}, target_doc, set_missing_values, ignore_permissions=ignore_permissions) }}, target_doc, set_missing_values, ignore_permissions=ignore_permissions)
return doclist.as_dict() return doclist
@frappe.whitelist() @frappe.whitelist()
def make_opportunity(source_name, target_doc=None): def make_opportunity(source_name, target_doc=None):

View File

@@ -13,9 +13,9 @@ class TestLead(unittest.TestCase):
from erpnext.selling.doctype.lead.lead import make_customer from erpnext.selling.doctype.lead.lead import make_customer
customer = make_customer("_T-Lead-00001") customer = make_customer("_T-Lead-00001")
self.assertEquals(customer[0]["doctype"], "Customer") self.assertEquals(customer.doctype, "Customer")
self.assertEquals(customer[0]["lead_name"], "_T-Lead-00001") self.assertEquals(customer.lead_name, "_T-Lead-00001")
customer[0]["company"] = "_Test Company" customer.company = "_Test Company"
customer[0]["customer_group"] = "_Test Customer Group" customer.customer_group = "_Test Customer Group"
frappe.get_doc(customer).insert() customer.insert()

View File

@@ -161,4 +161,4 @@ def make_quotation(source_name, target_doc=None):
} }
}, target_doc, set_missing_values) }, target_doc, set_missing_values)
return doclist.as_dict() return doclist

View File

@@ -137,7 +137,7 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
# postprocess: fetch shipping address, set missing values # postprocess: fetch shipping address, set missing values
return doclist.as_dict() return doclist
def _make_customer(source_name, ignore_permissions=False): def _make_customer(source_name, ignore_permissions=False):
quotation = frappe.db.get_value("Quotation", source_name, ["lead", "order_type"]) quotation = frappe.db.get_value("Quotation", source_name, ["lead", "order_type"])

View File

@@ -10,26 +10,26 @@ test_dependencies = ["Sales BOM"]
class TestQuotation(unittest.TestCase): class TestQuotation(unittest.TestCase):
def test_make_sales_order(self): def test_make_sales_order(self):
from erpnext.selling.doctype.quotation.quotation import make_sales_order from erpnext.selling.doctype.quotation.quotation import make_sales_order
quotation = frappe.copy_doc(test_records[0]) quotation = frappe.copy_doc(test_records[0])
quotation.insert() quotation.insert()
self.assertRaises(frappe.ValidationError, make_sales_order, quotation.name) self.assertRaises(frappe.ValidationError, make_sales_order, quotation.name)
quotation.submit() quotation.submit()
sales_order = make_sales_order(quotation.name) sales_order = make_sales_order(quotation.name)
self.assertEquals(sales_order[0]["doctype"], "Sales Order") self.assertEquals(sales_order.doctype, "Sales Order")
self.assertEquals(len(sales_order), 2) self.assertEquals(len(sales_order.get("sales_order_details")), 2)
self.assertEquals(sales_order[1]["doctype"], "Sales Order Item") self.assertEquals(sales_order.get("sales_order_details")[0]["doctype"], "Sales Order Item")
self.assertEquals(sales_order[1]["prevdoc_docname"], quotation.name) self.assertEquals(sales_order.get("sales_order_details")[0]["prevdoc_docname"], quotation.name)
self.assertEquals(sales_order[0]["customer"], "_Test Customer") self.assertEquals(sales_order.customer, "_Test Customer")
sales_order[0]["delivery_date"] = "2014-01-01" sales_order.delivery_date = "2014-01-01"
sales_order[0]["naming_series"] = "_T-Quotation-" sales_order.naming_series = "_T-Quotation-"
sales_order[0]["transaction_date"] = "2013-05-12" sales_order.transaction_date = "2013-05-12"
frappe.get_doc(sales_order).insert() sales_order.insert()
test_records = frappe.get_test_records('Quotation') test_records = frappe.get_test_records('Quotation')

View File

@@ -18,28 +18,28 @@ class SalesOrder(SellingController):
person_tname = 'Target Detail' person_tname = 'Target Detail'
partner_tname = 'Partner Target Detail' partner_tname = 'Partner Target Detail'
territory_tname = 'Territory Target Detail' territory_tname = 'Territory Target Detail'
def validate_mandatory(self): def validate_mandatory(self):
# validate transaction date v/s delivery date # validate transaction date v/s delivery date
if self.delivery_date: if self.delivery_date:
if getdate(self.transaction_date) > getdate(self.delivery_date): if getdate(self.transaction_date) > getdate(self.delivery_date):
msgprint("Expected Delivery Date cannot be before Sales Order Date") msgprint("Expected Delivery Date cannot be before Sales Order Date")
raise Exception raise Exception
def validate_po(self): def validate_po(self):
# validate p.o date v/s delivery date # 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): 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") msgprint("Expected Delivery Date cannot be before Purchase Order Date")
raise Exception raise Exception
if self.po_no and self.customer: if self.po_no and self.customer:
so = frappe.db.sql("select name from `tabSales Order` \ so = frappe.db.sql("select name from `tabSales Order` \
where ifnull(po_no, '') = %s and name != %s and docstatus < 2\ where ifnull(po_no, '') = %s and name != %s and docstatus < 2\
and customer = %s", (self.po_no, self.name, self.customer)) and customer = %s", (self.po_no, self.name, self.customer))
if so and so[0][0]: 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]) Please be sure, you are not making duplicate entry.""" % so[0][0])
def validate_for_items(self): def validate_for_items(self):
check_list, flag = [], 0 check_list, flag = [], 0
chk_dupl_itm = [] chk_dupl_itm = []
@@ -49,9 +49,9 @@ class SalesOrder(SellingController):
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 'Yes': if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 'Yes':
if not d.warehouse: 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) as it is stock Item""" % d.item_code, raise_exception=1)
if e in check_list: if e in check_list:
msgprint("Item %s has been entered twice." % d.item_code) msgprint("Item %s has been entered twice." % d.item_code)
else: else:
@@ -64,7 +64,7 @@ class SalesOrder(SellingController):
# used for production plan # used for production plan
d.transaction_date = self.transaction_date d.transaction_date = self.transaction_date
tot_avail_qty = frappe.db.sql("select projected_qty from `tabBin` \ tot_avail_qty = frappe.db.sql("select projected_qty from `tabBin` \
where item_code = %s and warehouse = %s", (d.item_code,d.warehouse)) 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 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): def validate_order_type(self):
super(SalesOrder, self).validate_order_type() super(SalesOrder, self).validate_order_type()
def validate_delivery_date(self): def validate_delivery_date(self):
if self.order_type == 'Sales' and not self.delivery_date: if self.order_type == 'Sales' and not self.delivery_date:
msgprint("Please enter 'Expected Delivery Date'") msgprint("Please enter 'Expected Delivery Date'")
raise Exception raise Exception
self.validate_sales_mntc_quotation() self.validate_sales_mntc_quotation()
def validate_proj_cust(self): def validate_proj_cust(self):
if self.project_name and self.customer_name: if self.project_name and self.customer_name:
res = frappe.db.sql("""select name from `tabProject` where name = %s res = frappe.db.sql("""select name from `tabProject` where name = %s
and (customer = %s or ifnull(customer,'')='')""", and (customer = %s or ifnull(customer,'')='')""",
(self.project_name, self.customer)) (self.project_name, self.customer))
if not res: 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)) 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 raise Exception
def validate(self): def validate(self):
super(SalesOrder, self).validate() super(SalesOrder, self).validate()
self.validate_order_type() self.validate_order_type()
self.validate_delivery_date() self.validate_delivery_date()
self.validate_mandatory() self.validate_mandatory()
@@ -111,28 +111,28 @@ class SalesOrder(SellingController):
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
make_packing_list(self,'sales_order_details') make_packing_list(self,'sales_order_details')
self.validate_with_previous_doc() self.validate_with_previous_doc()
if not self.status: if not self.status:
self.status = "Draft" self.status = "Draft"
from erpnext.utilities import validate_status from erpnext.utilities import validate_status
validate_status(self.status, ["Draft", "Submitted", "Stopped", validate_status(self.status, ["Draft", "Submitted", "Stopped",
"Cancelled"]) "Cancelled"])
if not self.billing_status: self.billing_status = 'Not Billed' 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): def validate_warehouse(self):
from erpnext.stock.utils import validate_warehouse_company 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])) self.get(self.fname) if d.warehouse]))
for w in warehouses: for w in warehouses:
validate_warehouse_company(w, self.company) validate_warehouse_company(w, self.company)
def validate_with_previous_doc(self): def validate_with_previous_doc(self):
super(SalesOrder, self).validate_with_previous_doc(self.tname, { super(SalesOrder, self).validate_with_previous_doc(self.tname, {
"Quotation": { "Quotation": {
@@ -141,31 +141,31 @@ class SalesOrder(SellingController):
} }
}) })
def update_enquiry_status(self, prevdoc, flag): 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) 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: if enq:
frappe.db.sql("update `tabOpportunity` set status = %s where name=%s",(flag,enq[0][0])) 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)])): for quotation in list(set([d.prevdoc_docname for d in self.get(self.fname)])):
if quotation: if quotation:
doc = frappe.get_doc("Quotation", quotation) doc = frappe.get_doc("Quotation", quotation)
if doc.docstatus==2: if doc.docstatus==2:
frappe.throw(quotation + ": " + frappe._("Quotation is cancelled.")) frappe.throw(quotation + ": " + frappe._("Quotation is cancelled."))
doc.set_status(update=True) doc.set_status(update=True)
def on_submit(self): def on_submit(self):
self.update_stock_ledger(update_stock = 1) self.update_stock_ledger(update_stock = 1)
self.check_credit(self.grand_total) self.check_credit(self.grand_total)
frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.grand_total, self) frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.grand_total, self)
self.update_prevdoc_status('submit') self.update_prevdoc_status('submit')
frappe.db.set(self, 'status', 'Submitted') frappe.db.set(self, 'status', 'Submitted')
def on_cancel(self): def on_cancel(self):
# Cannot cancel stopped SO # Cannot cancel stopped SO
if self.status == 'Stopped': if self.status == 'Stopped':
@@ -173,45 +173,45 @@ class SalesOrder(SellingController):
raise Exception raise Exception
self.check_nextdoc_docstatus() self.check_nextdoc_docstatus()
self.update_stock_ledger(update_stock = -1) self.update_stock_ledger(update_stock = -1)
self.update_prevdoc_status('cancel') self.update_prevdoc_status('cancel')
frappe.db.set(self, 'status', 'Cancelled') frappe.db.set(self, 'status', 'Cancelled')
def check_nextdoc_docstatus(self): def check_nextdoc_docstatus(self):
# Checks Delivery Note # 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) 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: 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) 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 # Checks Sales Invoice
submit_rv = frappe.db.sql("""select t1.name submit_rv = frappe.db.sql("""select t1.name
from `tabSales Invoice` t1,`tabSales Invoice Item` t2 from `tabSales Invoice` t1,`tabSales Invoice Item` t2
where t1.name = t2.parent and t2.sales_order = %s and t1.docstatus = 1""", where t1.name = t2.parent and t2.sales_order = %s and t1.docstatus = 1""",
self.name) self.name)
if submit_rv: 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) 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 #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) 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: 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) 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 # 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) 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: 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) 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 # check production order
pro_order = frappe.db.sql("""select name from `tabProduction Order` where sales_order = %s and docstatus = 1""", self.name) pro_order = frappe.db.sql("""select name from `tabProduction Order` where sales_order = %s and docstatus = 1""", self.name)
if pro_order: if pro_order:
msgprint("""Production Order: %s exists against this sales order. msgprint("""Production Order: %s exists against this sales order.
Please cancel production order first and then cancel this sales order""" % Please cancel production order first and then cancel this sales order""" %
pro_order[0][0], raise_exception=1) pro_order[0][0], raise_exception=1)
def check_modified_date(self): def check_modified_date(self):
mod_db = frappe.db.get_value("Sales Order", self.name, "modified") 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))) ( mod_db, cstr(self.modified)))
if date_diff and date_diff[0][0]: if date_diff and date_diff[0][0]:
msgprint("%s: %s has been modified after you have opened. Please Refresh" msgprint("%s: %s has been modified after you have opened. Please Refresh"
@@ -221,7 +221,7 @@ class SalesOrder(SellingController):
self.check_modified_date() self.check_modified_date()
self.update_stock_ledger(-1) self.update_stock_ledger(-1)
frappe.db.set(self, 'status', 'Stopped') 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)) you need to Unstop it.""" % (self.doctype, self.name))
def unstop_sales_order(self): 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": if frappe.db.get_value("Item", d['item_code'], "is_stock_item") == "Yes":
args = { args = {
"item_code": d['item_code'], "item_code": d['item_code'],
"warehouse": d['reserved_warehouse'], "warehouse": d['reserved_warehouse'],
"reserved_qty": flt(update_stock) * flt(d['reserved_qty']), "reserved_qty": flt(update_stock) * flt(d['reserved_qty']),
"posting_date": self.transaction_date, "posting_date": self.transaction_date,
"voucher_type": self.doctype, "voucher_type": self.doctype,
@@ -248,76 +248,76 @@ class SalesOrder(SellingController):
def on_update(self): def on_update(self):
pass pass
def get_portal_page(self): def get_portal_page(self):
return "order" if self.docstatus==1 else None return "order" if self.docstatus==1 else None
def set_missing_values(source, target): def set_missing_values(source, target):
doc = frappe.get_doc(target) doc = frappe.get_doc(target)
doc.run_method("onload_post_render") doc.run_method("onload_post_render")
@frappe.whitelist() @frappe.whitelist()
def make_material_request(source_name, target_doc=None): def make_material_request(source_name, target_doc=None):
def postprocess(source, doclist): def postprocess(source, doc):
doclist[0].material_request_type = "Purchase" doc.material_request_type = "Purchase"
doclist = get_mapped_doc("Sales Order", source_name, { doc = get_mapped_doc("Sales Order", source_name, {
"Sales Order": { "Sales Order": {
"doctype": "Material Request", "doctype": "Material Request",
"validation": { "validation": {
"docstatus": ["=", 1] "docstatus": ["=", 1]
} }
}, },
"Sales Order Item": { "Sales Order Item": {
"doctype": "Material Request Item", "doctype": "Material Request Item",
"field_map": { "field_map": {
"parent": "sales_order_no", "parent": "sales_order_no",
"stock_uom": "uom" "stock_uom": "uom"
} }
} }
}, target_doc, postprocess) }, target_doc, postprocess)
return doclist return doc
@frappe.whitelist() @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): def update_item(obj, target, source_parent):
target.base_amount = (flt(obj.qty) - flt(obj.delivered_qty)) * flt(obj.base_rate) 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.amount = (flt(obj.qty) - flt(obj.delivered_qty)) * flt(obj.rate)
target.qty = flt(obj.qty) - flt(obj.delivered_qty) target.qty = flt(obj.qty) - flt(obj.delivered_qty)
doclist = get_mapped_doc("Sales Order", source_name, { doclist = get_mapped_doc("Sales Order", source_name, {
"Sales Order": { "Sales Order": {
"doctype": "Delivery Note", "doctype": "Delivery Note",
"field_map": { "field_map": {
"shipping_address": "address_display", "shipping_address": "address_display",
"shipping_address_name": "customer_address", "shipping_address_name": "customer_address",
}, },
"validation": { "validation": {
"docstatus": ["=", 1] "docstatus": ["=", 1]
} }
}, },
"Sales Order Item": { "Sales Order Item": {
"doctype": "Delivery Note Item", "doctype": "Delivery Note Item",
"field_map": { "field_map": {
"rate": "rate", "rate": "rate",
"name": "prevdoc_detail_docname", "name": "prevdoc_detail_docname",
"parent": "against_sales_order", "parent": "against_sales_order",
}, },
"postprocess": update_item, "postprocess": update_item,
"condition": lambda doc: doc.delivered_qty < doc.qty "condition": lambda doc: doc.delivered_qty < doc.qty
}, },
"Sales Taxes and Charges": { "Sales Taxes and Charges": {
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"add_if_empty": True "add_if_empty": True
}, },
"Sales Team": { "Sales Team": {
"doctype": "Sales Team", "doctype": "Sales Team",
"add_if_empty": True "add_if_empty": True
} }
}, target_doc, set_missing_values) }, target_doc, set_missing_values)
return doclist.as_dict() return doclist
@frappe.whitelist() @frappe.whitelist()
def make_sales_invoice(source_name, target_doc=None): 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 = frappe.get_doc(target)
doc.is_pos = 0 doc.is_pos = 0
doc.run_method("onload_post_render") doc.run_method("onload_post_render")
def update_item(obj, target, source_parent): def update_item(obj, target, source_parent):
target.amount = flt(obj.amount) - flt(obj.billed_amt) target.amount = flt(obj.amount) - flt(obj.billed_amt)
target.base_amount = target.amount * flt(source_parent.conversion_rate) target.base_amount = target.amount * flt(source_parent.conversion_rate)
target.qty = obj.rate and target.amount / flt(obj.rate) or obj.qty target.qty = obj.rate and target.amount / flt(obj.rate) or obj.qty
doclist = get_mapped_doc("Sales Order", source_name, { doclist = get_mapped_doc("Sales Order", source_name, {
"Sales Order": { "Sales Order": {
"doctype": "Sales Invoice", "doctype": "Sales Invoice",
"validation": { "validation": {
"docstatus": ["=", 1] "docstatus": ["=", 1]
} }
}, },
"Sales Order Item": { "Sales Order Item": {
"doctype": "Sales Invoice Item", "doctype": "Sales Invoice Item",
"field_map": { "field_map": {
"name": "so_detail", "name": "so_detail",
"parent": "sales_order", "parent": "sales_order",
}, },
"postprocess": update_item, "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
}, },
"Sales Taxes and Charges": { "Sales Taxes and Charges": {
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"add_if_empty": True "add_if_empty": True
}, },
"Sales Team": { "Sales Team": {
"doctype": "Sales Team", "doctype": "Sales Team",
"add_if_empty": True "add_if_empty": True
} }
}, target_doc, set_missing_values) }, target_doc, set_missing_values)
return doclist.as_dict() return doclist
@frappe.whitelist() @frappe.whitelist()
def make_maintenance_schedule(source_name, target_doc=None): def make_maintenance_schedule(source_name, target_doc=None):
maint_schedule = frappe.db.sql("""select t1.name maint_schedule = frappe.db.sql("""select t1.name
from `tabMaintenance Schedule` t1, `tabMaintenance Schedule Item` t2 from `tabMaintenance Schedule` t1, `tabMaintenance Schedule Item` t2
where t2.parent=t1.name and t2.prevdoc_docname=%s and t1.docstatus=1""", source_name) where t2.parent=t1.name and t2.prevdoc_docname=%s and t1.docstatus=1""", source_name)
if not maint_schedule: if not maint_schedule:
doclist = get_mapped_doc("Sales Order", source_name, { doclist = get_mapped_doc("Sales Order", source_name, {
"Sales Order": { "Sales Order": {
"doctype": "Maintenance Schedule", "doctype": "Maintenance Schedule",
"field_map": { "field_map": {
"name": "sales_order_no" "name": "sales_order_no"
}, },
"validation": { "validation": {
"docstatus": ["=", 1] "docstatus": ["=", 1]
} }
}, },
"Sales Order Item": { "Sales Order Item": {
"doctype": "Maintenance Schedule Item", "doctype": "Maintenance Schedule Item",
"field_map": { "field_map": {
"parent": "prevdoc_docname" "parent": "prevdoc_docname"
}, },
"add_if_empty": True "add_if_empty": True
} }
}, target_doc) }, target_doc)
return doclist.as_dict() return doclist
@frappe.whitelist() @frappe.whitelist()
def make_maintenance_visit(source_name, target_doc=None): def make_maintenance_visit(source_name, target_doc=None):
visit = frappe.db.sql("""select t1.name visit = frappe.db.sql("""select t1.name
from `tabMaintenance Visit` t1, `tabMaintenance Visit Purpose` t2 from `tabMaintenance Visit` t1, `tabMaintenance Visit Purpose` t2
where t2.parent=t1.name and t2.prevdoc_docname=%s where t2.parent=t1.name and t2.prevdoc_docname=%s
and t1.docstatus=1 and t1.completion_status='Fully Completed'""", source_name) and t1.docstatus=1 and t1.completion_status='Fully Completed'""", source_name)
if not visit: if not visit:
doclist = get_mapped_doc("Sales Order", source_name, { doclist = get_mapped_doc("Sales Order", source_name, {
"Sales Order": { "Sales Order": {
"doctype": "Maintenance Visit", "doctype": "Maintenance Visit",
"field_map": { "field_map": {
"name": "sales_order_no" "name": "sales_order_no"
}, },
"validation": { "validation": {
"docstatus": ["=", 1] "docstatus": ["=", 1]
} }
}, },
"Sales Order Item": { "Sales Order Item": {
"doctype": "Maintenance Visit Purpose", "doctype": "Maintenance Visit Purpose",
"field_map": { "field_map": {
"parent": "prevdoc_docname", "parent": "prevdoc_docname",
"parenttype": "prevdoc_doctype" "parenttype": "prevdoc_doctype"
}, },
"add_if_empty": True "add_if_empty": True
} }
}, target_doc) }, target_doc)
return doclist.as_dict() return doclist

View File

@@ -4,82 +4,82 @@
import frappe import frappe
from frappe.utils import flt from frappe.utils import flt
import unittest import unittest
import copy
class TestSalesOrder(unittest.TestCase): class TestSalesOrder(unittest.TestCase):
def tearDown(self): def tearDown(self):
frappe.set_user("Administrator") frappe.set_user("Administrator")
def test_make_material_request(self): def test_make_material_request(self):
from erpnext.selling.doctype.sales_order.sales_order import make_material_request from erpnext.selling.doctype.sales_order.sales_order import make_material_request
so = frappe.copy_doc(test_records[0]).insert() so = frappe.copy_doc(test_records[0]).insert()
self.assertRaises(frappe.ValidationError, make_material_request, self.assertRaises(frappe.ValidationError, make_material_request,
so.name) so.name)
sales_order = frappe.get_doc("Sales Order", so.name) sales_order = frappe.get_doc("Sales Order", so.name)
sales_order.submit() sales_order.submit()
mr = make_material_request(so.name) mr = make_material_request(so.name)
self.assertEquals(mr[0]["material_request_type"], "Purchase") self.assertEquals(mr.material_request_type, "Purchase")
self.assertEquals(len(mr), len(sales_order)) self.assertEquals(len(mr.get("indent_details")), len(sales_order.get("sales_order_details")))
def test_make_delivery_note(self): def test_make_delivery_note(self):
from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note
so = frappe.copy_doc(test_records[0]).insert() so = frappe.copy_doc(test_records[0]).insert()
self.assertRaises(frappe.ValidationError, make_delivery_note, self.assertRaises(frappe.ValidationError, make_delivery_note,
so.name) so.name)
sales_order = frappe.get_doc("Sales Order", so.name) sales_order = frappe.get_doc("Sales Order", so.name)
sales_order.submit() sales_order.submit()
dn = make_delivery_note(so.name) dn = make_delivery_note(so.name)
self.assertEquals(dn[0]["doctype"], "Delivery Note") self.assertEquals(dn.doctype, "Delivery Note")
self.assertEquals(len(dn), len(sales_order)) self.assertEquals(len(dn.get("delivery_note_details")), len(sales_order.get("sales_order_details")))
def test_make_sales_invoice(self): def test_make_sales_invoice(self):
from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
so = frappe.copy_doc(test_records[0]).insert() so = frappe.copy_doc(test_records[0]).insert()
self.assertRaises(frappe.ValidationError, make_sales_invoice, self.assertRaises(frappe.ValidationError, make_sales_invoice,
so.name) so.name)
sales_order = frappe.get_doc("Sales Order", so.name) sales_order = frappe.get_doc("Sales Order", so.name)
sales_order.submit() sales_order.submit()
si = make_sales_invoice(so.name) si = make_sales_invoice(so.name)
self.assertEquals(si[0]["doctype"], "Sales Invoice") self.assertEquals(si.doctype, "Sales Invoice")
self.assertEquals(len(si), len(sales_order)) self.assertEquals(len(si.get("entries")), len(sales_order.get("sales_order_details")))
self.assertEquals(len([d for d in si if d["doctype"]=="Sales Invoice Item"]), 1) self.assertEquals(len(si.get("entries")), 1)
si = frappe.get_doc(si)
si.posting_date = "2013-10-10" si.posting_date = "2013-10-10"
si.insert() si.insert()
si.submit() si.submit()
si1 = make_sales_invoice(so.name) 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): def create_so(self, so_doc = None):
if not so_doc: if not so_doc:
so_doc = test_records[0] so_doc = test_records[0]
w = frappe.copy_doc(so_doc) w = frappe.copy_doc(so_doc)
w.insert() w.insert()
w.submit() w.submit()
return w return w
def create_dn_against_so(self, so, delivered_qty=0): 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 test_records as dn_test_records
from erpnext.stock.doctype.delivery_note.test_delivery_note import _insert_purchase_receipt from erpnext.stock.doctype.delivery_note.test_delivery_note import _insert_purchase_receipt
_insert_purchase_receipt(so.get("sales_order_details")[0].item_code) _insert_purchase_receipt(so.get("sales_order_details")[0].item_code)
dn = frappe.get_doc(frappe.copy_doc(dn_test_records[0])) 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].item_code = so.get("sales_order_details")[0].item_code
dn.get("delivery_note_details")[0].against_sales_order = so.name dn.get("delivery_note_details")[0].against_sales_order = so.name
@@ -89,76 +89,79 @@ class TestSalesOrder(unittest.TestCase):
dn.insert() dn.insert()
dn.submit() dn.submit()
return dn return dn
def get_bin_reserved_qty(self, item_code, warehouse): 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")) "reserved_qty"))
def delete_bin(self, item_code, warehouse): 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}) "warehouse": warehouse})
if bin: if bin:
frappe.delete_doc("Bin", bin[0][0]) frappe.delete_doc("Bin", bin[0][0])
def check_reserved_qty(self, item_code, warehouse, qty): def check_reserved_qty(self, item_code, warehouse, qty):
bin_reserved_qty = self.get_bin_reserved_qty(item_code, warehouse) bin_reserved_qty = self.get_bin_reserved_qty(item_code, warehouse)
self.assertEqual(bin_reserved_qty, qty) self.assertEqual(bin_reserved_qty, qty)
def test_reserved_qty_for_so(self): def test_reserved_qty_for_so(self):
# reset bin # 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 # submit
so = self.create_so() 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) self.check_reserved_qty(so.get("sales_order_details")[0].item_code, so.get("sales_order_details")[0].warehouse, 10.0)
# cancel # cancel
so.cancel() so.cancel()
self.check_reserved_qty(so.get("sales_order_details")[0].item_code, so.get("sales_order_details")[0].warehouse, 0.0) 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): def test_reserved_qty_for_partial_delivery(self):
# reset bin # 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 # submit so
so = self.create_so() so = self.create_so()
# allow negative stock # allow negative stock
frappe.db.set_default("allow_negative_stock", 1) frappe.db.set_default("allow_negative_stock", 1)
# submit dn # submit dn
dn = self.create_dn_against_so(so) 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) self.check_reserved_qty(so.get("sales_order_details")[0].item_code, so.get("sales_order_details")[0].warehouse, 5.0)
# stop so # stop so
so.load_from_db() 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) self.check_reserved_qty(so.get("sales_order_details")[0].item_code, so.get("sales_order_details")[0].warehouse, 0.0)
# unstop so # unstop so
so.load_from_db() 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) self.check_reserved_qty(so.get("sales_order_details")[0].item_code, so.get("sales_order_details")[0].warehouse, 5.0)
# cancel dn # cancel dn
dn.cancel() dn.cancel()
self.check_reserved_qty(so.get("sales_order_details")[0].item_code, so.get("sales_order_details")[0].warehouse, 10.0) 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): def test_reserved_qty_for_over_delivery(self):
# reset bin # 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 # submit so
so = self.create_so() so = self.create_so()
# allow negative stock # allow negative stock
frappe.db.set_default("allow_negative_stock", 1) frappe.db.set_default("allow_negative_stock", 1)
# set over-delivery tolerance # set over-delivery tolerance
frappe.db.set_value('Item', so.get("sales_order_details")[0].item_code, 'tolerance', 50) frappe.db.set_value('Item', so.get("sales_order_details")[0].item_code, 'tolerance', 50)
# submit dn # submit dn
dn = self.create_dn_against_so(so, 15) 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) 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 # cancel dn
dn.cancel() dn.cancel()
self.check_reserved_qty(so.get("sales_order_details")[0].item_code, so.get("sales_order_details")[0].warehouse, 10.0) 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): 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 from erpnext.selling.doctype.sales_bom.test_sales_bom import test_records as sbom_test_records
# change item in test so record # change item in test so record
test_record = test_records[0][:] test_record = copy.deepcopy(test_records[0])
test_record[1]["item_code"] = "_Test Sales BOM Item" test_record["sales_order_details"][0]["item_code"] = "_Test Sales BOM Item"
# reset bin # reset bin
self.delete_bin(sbom_test_records[0][1]["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][2]["item_code"], test_record[1]["warehouse"]) self.delete_bin(sbom_test_records[0]["sales_bom_items"][1]["item_code"], test_record.get("sales_order_details")[0]["warehouse"])
# submit # submit
so = self.create_so(test_record) 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) 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) so.get("sales_order_details")[0].warehouse, 20.0)
# cancel # cancel
so.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) 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) so.get("sales_order_details")[0].warehouse, 0.0)
def test_reserved_qty_for_partial_delivery_with_packing_list(self): 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 from erpnext.selling.doctype.sales_bom.test_sales_bom import test_records as sbom_test_records
# change item in test so record # change item in test so record
test_record = frappe.copy_doc(test_records[0]) 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 # reset bin
self.delete_bin(sbom_test_records[0][1]["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][2]["item_code"], test_record[1]["warehouse"]) self.delete_bin(sbom_test_records[0]["sales_bom_items"][1]["item_code"], test_record.get("sales_order_details")[0].warehouse)
# submit # submit
so = self.create_so(test_record) so = self.create_so(test_record)
# allow negative stock # allow negative stock
frappe.db.set_default("allow_negative_stock", 1) frappe.db.set_default("allow_negative_stock", 1)
# submit dn # submit dn
dn = self.create_dn_against_so(so) 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) 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) so.get("sales_order_details")[0].warehouse, 10.0)
# stop so # stop so
so.load_from_db() so.load_from_db()
so.obj.stop_sales_order() so.stop_sales_order()
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) 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) so.get("sales_order_details")[0].warehouse, 0.0)
# unstop so # unstop so
so.load_from_db() so.load_from_db()
so.obj.unstop_sales_order() so.unstop_sales_order()
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) 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) so.get("sales_order_details")[0].warehouse, 10.0)
# cancel dn # cancel dn
dn.cancel() 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) 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) so.get("sales_order_details")[0].warehouse, 20.0)
def test_reserved_qty_for_over_delivery_with_packing_list(self): 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 from erpnext.selling.doctype.sales_bom.test_sales_bom import test_records as sbom_test_records
# change item in test so record # change item in test so record
test_record = frappe.copy_doc(test_records[0]) 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 # reset bin
self.delete_bin(sbom_test_records[0][1]["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][2]["item_code"], test_record[1]["warehouse"]) self.delete_bin(sbom_test_records[0]["sales_bom_items"][1]["item_code"], test_record.get("sales_order_details")[0].warehouse)
# submit # submit
so = self.create_so(test_record) so = self.create_so(test_record)
# allow negative stock # allow negative stock
frappe.db.set_default("allow_negative_stock", 1) frappe.db.set_default("allow_negative_stock", 1)
# set over-delivery tolerance # set over-delivery tolerance
frappe.db.set_value('Item', so.get("sales_order_details")[0].item_code, 'tolerance', 50) frappe.db.set_value('Item', so.get("sales_order_details")[0].item_code, 'tolerance', 50)
# submit dn # submit dn
dn = self.create_dn_against_so(so, 15) 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) 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) so.get("sales_order_details")[0].warehouse, 0.0)
# cancel dn # cancel dn
dn.cancel() 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) 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) so.get("sales_order_details")[0].warehouse, 20.0)
def test_warehouse_user(self): def test_warehouse_user(self):
frappe.defaults.add_default("Warehouse", "_Test Warehouse 1 - _TC1", "test@example.com", "Restriction") frappe.defaults.add_default("Warehouse", "_Test Warehouse 1 - _TC1", "test@example.com", "Restriction")
frappe.get_doc("User", "test@example.com")\ frappe.get_doc("User", "test@example.com")\
.add_roles("Sales User", "Sales Manager", "Material User", "Material Manager") .add_roles("Sales User", "Sales Manager", "Material User", "Material Manager")
frappe.get_doc("User", "test2@example.com")\ frappe.get_doc("User", "test2@example.com")\
.add_roles("Sales User", "Sales Manager", "Material User", "Material Manager") .add_roles("Sales User", "Sales Manager", "Material User", "Material Manager")
frappe.set_user("test@example.com") frappe.set_user("test@example.com")
so = frappe.copy_doc(test_records[0]) so = frappe.copy_doc(test_records[0])
@@ -298,9 +301,9 @@ class TestSalesOrder(unittest.TestCase):
frappe.set_user("test2@example.com") frappe.set_user("test2@example.com")
so.insert() so.insert()
frappe.defaults.clear_default("Warehouse", "_Test Warehouse 1 - _TC1", "test@example.com", parenttype="Restriction") frappe.defaults.clear_default("Warehouse", "_Test Warehouse 1 - _TC1", "test@example.com", parenttype="Restriction")
test_dependencies = ["Sales BOM", "Currency Exchange"] test_dependencies = ["Sales BOM", "Currency Exchange"]
test_records = frappe.get_test_records('Sales Order') test_records = frappe.get_test_records('Sales Order')

View File

@@ -28,21 +28,21 @@ class DeliveryNote(SellingController):
'status_field': 'delivery_status', 'status_field': 'delivery_status',
'keyword': 'Delivered' 'keyword': 'Delivered'
}] }]
def onload(self): def onload(self):
billed_qty = frappe.db.sql("""select sum(ifnull(qty, 0)) from `tabSales Invoice Item` billed_qty = frappe.db.sql("""select sum(ifnull(qty, 0)) from `tabSales Invoice Item`
where docstatus=1 and delivery_note=%s""", self.name) where docstatus=1 and delivery_note=%s""", self.name)
if billed_qty: if billed_qty:
total_qty = sum((item.qty for item in self.get("delivery_note_details"))) total_qty = sum((item.qty for item in self.get("delivery_note_details")))
self.set("__billing_complete", billed_qty[0][0] == total_qty) self.set("__billing_complete", billed_qty[0][0] == total_qty)
def get_portal_page(self): def get_portal_page(self):
return "shipment" if self.docstatus==1 else None return "shipment" if self.docstatus==1 else None
def set_actual_qty(self): def set_actual_qty(self):
for d in self.get('delivery_note_details'): for d in self.get('delivery_note_details'):
if d.item_code and d.warehouse: 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)) 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 d.actual_qty = actual_qty and flt(actual_qty[0][0]) or 0
@@ -57,7 +57,7 @@ class DeliveryNote(SellingController):
def validate(self): def validate(self):
super(DeliveryNote, self).validate() super(DeliveryNote, self).validate()
from erpnext.utilities import validate_status from erpnext.utilities import validate_status
validate_status(self.status, ["Draft", "Submitted", "Cancelled"]) validate_status(self.status, ["Draft", "Submitted", "Cancelled"])
@@ -67,18 +67,18 @@ class DeliveryNote(SellingController):
self.validate_for_items() self.validate_for_items()
self.validate_warehouse() self.validate_warehouse()
self.validate_uom_is_integer("stock_uom", "qty") self.validate_uom_is_integer("stock_uom", "qty")
self.update_current_stock() self.update_current_stock()
self.validate_with_previous_doc() self.validate_with_previous_doc()
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
make_packing_list(self, 'delivery_note_details') make_packing_list(self, 'delivery_note_details')
self.status = 'Draft' 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): def validate_with_previous_doc(self):
items = self.get("delivery_note_details") items = self.get("delivery_note_details")
for fn in (("Sales Order", "against_sales_order"), ("Sales Invoice", "against_sales_invoice")): 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]): if filter(None, [getattr(d, fn[1], None) for d in items]):
super(DeliveryNote, self).validate_with_previous_doc(self.tname, { super(DeliveryNote, self).validate_with_previous_doc(self.tname, {
@@ -97,12 +97,12 @@ class DeliveryNote(SellingController):
"is_child_table": True "is_child_table": True
} }
}) })
def validate_proj_cust(self): def validate_proj_cust(self):
"""check for does customer belong to same project as entered..""" """check for does customer belong to same project as entered.."""
if self.project_name and self.customer: if self.project_name and self.customer:
res = frappe.db.sql("""select name from `tabProject` res = frappe.db.sql("""select name from `tabProject`
where name = %s and (customer = %s or where name = %s and (customer = %s or
ifnull(customer,'')='')""", (self.project_name, self.customer)) ifnull(customer,'')='')""", (self.project_name, self.customer))
if not res: 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)) 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 frappe.db.get_value("Item", d.item_code, "is_stock_item") == 'Yes':
if e in check_list: 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) % d.item_code)
else: else:
check_list.append(e) check_list.append(e)
else: else:
if f in chk_dupl_itm: 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) % d.item_code)
else: else:
chk_dupl_itm.append(f) chk_dupl_itm.append(f)
@@ -133,7 +133,7 @@ class DeliveryNote(SellingController):
if not d['warehouse']: if not d['warehouse']:
msgprint("Please enter Warehouse for item %s as it is stock item" msgprint("Please enter Warehouse for item %s as it is stock item"
% d['item_code'], raise_exception=1) % d['item_code'], raise_exception=1)
def update_current_stock(self): def update_current_stock(self):
for d in self.get('delivery_note_details'): for d in self.get('delivery_note_details'):
@@ -150,15 +150,15 @@ class DeliveryNote(SellingController):
# Check for Approving Authority # Check for Approving Authority
frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.company, self.grand_total, self) 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() self.update_prevdoc_status()
# create stock ledger entry # create stock ledger entry
self.update_stock_ledger() self.update_stock_ledger()
self.credit_limit() self.credit_limit()
self.make_gl_entries() self.make_gl_entries()
# set DN status # set DN status
@@ -168,14 +168,14 @@ class DeliveryNote(SellingController):
def on_cancel(self): def on_cancel(self):
self.check_stop_sales_order("against_sales_order") self.check_stop_sales_order("against_sales_order")
self.check_next_docstatus() self.check_next_docstatus()
self.update_prevdoc_status() self.update_prevdoc_status()
self.update_stock_ledger() self.update_stock_ledger()
frappe.db.set(self, 'status', 'Cancelled') frappe.db.set(self, 'status', 'Cancelled')
self.cancel_packing_slips() self.cancel_packing_slips()
self.make_cancel_gl_entries() self.make_cancel_gl_entries()
def validate_packed_qty(self): def validate_packed_qty(self):
@@ -198,17 +198,17 @@ class DeliveryNote(SellingController):
frappe.msgprint("Packing Error:\n" + err_msg, raise_exception=1) frappe.msgprint("Packing Error:\n" + err_msg, raise_exception=1)
def check_next_docstatus(self): def check_next_docstatus(self):
submit_rv = frappe.db.sql("""select t1.name submit_rv = frappe.db.sql("""select t1.name
from `tabSales Invoice` t1,`tabSales Invoice Item` t2 from `tabSales Invoice` t1,`tabSales Invoice Item` t2
where t1.name = t2.parent and t2.delivery_note = %s and t1.docstatus = 1""", where t1.name = t2.parent and t2.delivery_note = %s and t1.docstatus = 1""",
(self.name)) (self.name))
if submit_rv: if submit_rv:
msgprint("Sales Invoice : " + cstr(submit_rv[0][0]) + " has already been submitted !") msgprint("Sales Invoice : " + cstr(submit_rv[0][0]) + " has already been submitted !")
raise Exception , "Validation Error." raise Exception , "Validation Error."
submit_in = frappe.db.sql("""select t1.name submit_in = frappe.db.sql("""select t1.name
from `tabInstallation Note` t1, `tabInstallation Note Item` t2 from `tabInstallation Note` t1, `tabInstallation Note Item` t2
where t1.name = t2.parent and t2.prevdoc_docname = %s and t1.docstatus = 1""", where t1.name = t2.parent and t2.prevdoc_docname = %s and t1.docstatus = 1""",
(self.name)) (self.name))
if submit_in: if submit_in:
msgprint("Installation Note : "+cstr(submit_in[0][0]) +" has already been submitted !") 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 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) AND docstatus = 1""", self.name)
if res: if res:
@@ -234,19 +234,19 @@ class DeliveryNote(SellingController):
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == "Yes" \ if frappe.db.get_value("Item", d.item_code, "is_stock_item") == "Yes" \
and d.warehouse: and d.warehouse:
self.update_reserved_qty(d) self.update_reserved_qty(d)
sl_entries.append(self.get_sl_entries(d, { sl_entries.append(self.get_sl_entries(d, {
"actual_qty": -1*flt(d['qty']), "actual_qty": -1*flt(d['qty']),
})) }))
self.make_sl_entries(sl_entries) self.make_sl_entries(sl_entries)
def update_reserved_qty(self, d): def update_reserved_qty(self, d):
if d['reserved_qty'] < 0 : if d['reserved_qty'] < 0 :
# Reduce reserved qty from reserved warehouse mentioned in so # Reduce reserved qty from reserved warehouse mentioned in so
if not d["reserved_warehouse"]: if not d["reserved_warehouse"]:
frappe.throw(_("Reserved Warehouse is missing in Sales Order")) frappe.throw(_("Reserved Warehouse is missing in Sales Order"))
args = { args = {
"item_code": d['item_code'], "item_code": d['item_code'],
"warehouse": d["reserved_warehouse"], "warehouse": d["reserved_warehouse"],
@@ -271,88 +271,88 @@ class DeliveryNote(SellingController):
def get_invoiced_qty_map(delivery_note): def get_invoiced_qty_map(delivery_note):
"""returns a map: {dn_detail: invoiced_qty}""" """returns a map: {dn_detail: invoiced_qty}"""
invoiced_qty_map = {} invoiced_qty_map = {}
for dn_detail, qty in frappe.db.sql("""select dn_detail, qty from `tabSales Invoice Item` 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): where delivery_note=%s and docstatus=1""", delivery_note):
if not invoiced_qty_map.get(dn_detail): if not invoiced_qty_map.get(dn_detail):
invoiced_qty_map[dn_detail] = 0 invoiced_qty_map[dn_detail] = 0
invoiced_qty_map[dn_detail] += qty invoiced_qty_map[dn_detail] += qty
return invoiced_qty_map return invoiced_qty_map
@frappe.whitelist() @frappe.whitelist()
def make_sales_invoice(source_name, target_doc=None): def make_sales_invoice(source_name, target_doc=None):
invoiced_qty_map = get_invoiced_qty_map(source_name) invoiced_qty_map = get_invoiced_qty_map(source_name)
def update_accounts(source, target): def update_accounts(source, target):
si = frappe.get_doc(target) si = frappe.get_doc(target)
si.is_pos = 0 si.is_pos = 0
si.run_method("onload_post_render") si.run_method("onload_post_render")
if len(si.get("entries")) == 0: if len(si.get("entries")) == 0:
frappe.msgprint(_("All these items have already been invoiced."), frappe.msgprint(_("All these items have already been invoiced."),
raise_exception=True) raise_exception=True)
def update_item(source_doc, target_doc, source_parent): def update_item(source_doc, target_doc, source_parent):
target_doc.qty = source_doc.qty - invoiced_qty_map.get(source_doc.name, 0) target_doc.qty = source_doc.qty - invoiced_qty_map.get(source_doc.name, 0)
doc = get_mapped_doc("Delivery Note", source_name, { doc = get_mapped_doc("Delivery Note", source_name, {
"Delivery Note": { "Delivery Note": {
"doctype": "Sales Invoice", "doctype": "Sales Invoice",
"validation": { "validation": {
"docstatus": ["=", 1] "docstatus": ["=", 1]
} }
}, },
"Delivery Note Item": { "Delivery Note Item": {
"doctype": "Sales Invoice Item", "doctype": "Sales Invoice Item",
"field_map": { "field_map": {
"name": "dn_detail", "name": "dn_detail",
"parent": "delivery_note", "parent": "delivery_note",
"prevdoc_detail_docname": "so_detail", "prevdoc_detail_docname": "so_detail",
"against_sales_order": "sales_order", "against_sales_order": "sales_order",
"serial_no": "serial_no" "serial_no": "serial_no"
}, },
"postprocess": update_item, "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": { "Sales Taxes and Charges": {
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"add_if_empty": True "add_if_empty": True
}, },
"Sales Team": { "Sales Team": {
"doctype": "Sales Team", "doctype": "Sales Team",
"field_map": { "field_map": {
"incentives": "incentives" "incentives": "incentives"
}, },
"add_if_empty": True "add_if_empty": True
} }
}, target_doc, update_accounts) }, target_doc, update_accounts)
return doc.as_dict() return doc
@frappe.whitelist() @frappe.whitelist()
def make_installation_note(source_name, target_doc=None): def make_installation_note(source_name, target_doc=None):
def update_item(obj, target, source_parent): def update_item(obj, target, source_parent):
target.qty = flt(obj.qty) - flt(obj.installed_qty) target.qty = flt(obj.qty) - flt(obj.installed_qty)
target.serial_no = obj.serial_no target.serial_no = obj.serial_no
doclist = get_mapped_doc("Delivery Note", source_name, { doclist = get_mapped_doc("Delivery Note", source_name, {
"Delivery Note": { "Delivery Note": {
"doctype": "Installation Note", "doctype": "Installation Note",
"validation": { "validation": {
"docstatus": ["=", 1] "docstatus": ["=", 1]
} }
}, },
"Delivery Note Item": { "Delivery Note Item": {
"doctype": "Installation Note Item", "doctype": "Installation Note Item",
"field_map": { "field_map": {
"name": "prevdoc_detail_docname", "name": "prevdoc_detail_docname",
"parent": "prevdoc_docname", "parent": "prevdoc_docname",
"parenttype": "prevdoc_doctype", "parenttype": "prevdoc_doctype",
}, },
"postprocess": update_item, "postprocess": update_item,
"condition": lambda doc: doc.installed_qty < doc.qty "condition": lambda doc: doc.installed_qty < doc.qty
} }
}, target_doc) }, target_doc)
return doclist.as_dict() return doclist

View File

@@ -245,7 +245,7 @@ def make_purchase_order(source_name, target_doc=None):
} }
}, target_doc, set_missing_values) }, target_doc, set_missing_values)
return doclist.as_dict() return doclist
@frappe.whitelist() @frappe.whitelist()
def make_purchase_order_based_on_supplier(source_name, target_doc=None): 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) }, target_doc, set_missing_values)
return doclist.as_dict() return doclist
@frappe.whitelist() @frappe.whitelist()
def make_stock_entry(source_name, target_doc=None): 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) }, target_doc, set_missing_values)
return doclist.as_dict() return doclist

View File

@@ -323,4 +323,4 @@ def make_purchase_invoice(source_name, target_doc=None):
} }
}, target_doc, set_missing_values) }, target_doc, set_missing_values)
return doclist.as_dict() return doclist

View File

@@ -234,6 +234,7 @@ class StockEntry(StockController):
def validate_finished_goods(self): def validate_finished_goods(self):
"""validation: finished good quantity should be same as manufacturing quantity""" """validation: finished good quantity should be same as manufacturing quantity"""
import json
for d in self.get('mtn_details'): for d in self.get('mtn_details'):
if d.bom_no and flt(d.transfer_qty) != flt(self.fg_completed_qty): if d.bom_no and flt(d.transfer_qty) != flt(self.fg_completed_qty):
msgprint(_("Row #") + " %s: " % d.idx msgprint(_("Row #") + " %s: " % d.idx

View File

@@ -59,4 +59,4 @@ def make_maintenance_visit(source_name, target_doc=None):
} }
}, target_doc) }, target_doc)
return doclist.as_dict() return doclist

View File

@@ -295,4 +295,4 @@ def make_maintenance_visit(source_name, target_doc=None):
} }
}, target_doc) }, target_doc)
return doclist.as_dict() return doclist