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

View File

@ -2,7 +2,7 @@
# License: GNU General Public License v3. See license.txt
import frappe
import unittest, json
import unittest, json, copy
from frappe.utils import flt
from erpnext.accounts.utils import get_stock_and_account_difference
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
@ -14,57 +14,46 @@ class TestSalesInvoice(unittest.TestCase):
w.insert()
w.submit()
return w
def test_double_submission(self):
w = frappe.copy_doc(test_records[0])
w.docstatus = '0'
w.insert()
w2 = frappe.copy_doc(test_records[0])
w.submit()
w = frappe.get_doc(w2)
self.assertRaises(frappe.DocstatusTransitionError, w.submit)
def test_timestamp_change(self):
w = frappe.copy_doc(test_records[0])
w.docstatus = '0'
w.docstatus = 0
w.insert()
w2 = frappe.copy_doc(w)
w2 = frappe.get_doc(w.doctype, w.name)
import time
time.sleep(1)
w.save()
import time
time.sleep(1)
self.assertRaises(frappe.TimestampMismatchError, w2.save)
def test_sales_invoice_calculation_base_currency(self):
si = frappe.copy_doc(test_records[2])
si.insert()
expected_values = {
"keys": ["price_list_rate", "discount_percentage", "rate", "amount",
"keys": ["price_list_rate", "discount_percentage", "rate", "amount",
"base_price_list_rate", "base_rate", "base_amount"],
"_Test Item Home Desktop 100": [50, 0, 50, 500, 50, 50, 500],
"_Test Item Home Desktop 200": [150, 0, 150, 750, 150, 150, 750],
}
# check if children are saved
self.assertEquals(len(si.get("entries")),
len(expected_values)-1)
# check if item values are calculated
for d in si.get("entries"):
for i, k in enumerate(expected_values["keys"]):
self.assertEquals(d.get(k), expected_values[d.item_code][i])
# check net total
self.assertEquals(si.net_total, 1250)
self.assertEquals(si.net_total_export, 1250)
# check tax calculation
expected_values = {
"keys": ["tax_amount", "total"],
@ -77,14 +66,14 @@ class TestSalesInvoice(unittest.TestCase):
"_Test Account VAT - _TC": [156.25, 1807.83],
"_Test Account Discount - _TC": [-180.78, 1627.05]
}
for d in si.get("other_charges"):
for i, k in enumerate(expected_values["keys"]):
self.assertEquals(d.get(k), expected_values[d.account_head][i])
self.assertEquals(si.grand_total, 1627.05)
self.assertEquals(si.grand_total_export, 1627.05)
def test_sales_invoice_calculation_export_currency(self):
si = frappe.copy_doc(test_records[2])
si.currency = "USD"
@ -94,27 +83,27 @@ class TestSalesInvoice(unittest.TestCase):
si.get("entries")[1].rate = 3
si.get("entries")[1].price_list_rate = 3
si.insert()
expected_values = {
"keys": ["price_list_rate", "discount_percentage", "rate", "amount",
"keys": ["price_list_rate", "discount_percentage", "rate", "amount",
"base_price_list_rate", "base_rate", "base_amount"],
"_Test Item Home Desktop 100": [1, 0, 1, 10, 50, 50, 500],
"_Test Item Home Desktop 200": [3, 0, 3, 15, 150, 150, 750],
}
# check if children are saved
self.assertEquals(len(si.get("entries")),
len(expected_values)-1)
# check if item values are calculated
for d in si.get("entries"):
for i, k in enumerate(expected_values["keys"]):
self.assertEquals(d.get(k), expected_values[d.item_code][i])
# check net total
self.assertEquals(si.net_total, 1250)
self.assertEquals(si.net_total_export, 25)
# check tax calculation
expected_values = {
"keys": ["tax_amount", "total"],
@ -127,11 +116,11 @@ class TestSalesInvoice(unittest.TestCase):
"_Test Account VAT - _TC": [156.25, 1807.83],
"_Test Account Discount - _TC": [-180.78, 1627.05]
}
for d in si.get("other_charges"):
for i, k in enumerate(expected_values["keys"]):
self.assertEquals(d.get(k), expected_values[d.account_head][i])
self.assertEquals(si.grand_total, 1627.05)
self.assertEquals(si.grand_total_export, 32.54)
@ -148,27 +137,27 @@ class TestSalesInvoice(unittest.TestCase):
"row_id": 8,
})
si.insert()
expected_values = {
"keys": ["price_list_rate", "discount_percentage", "rate", "amount",
"keys": ["price_list_rate", "discount_percentage", "rate", "amount",
"base_price_list_rate", "base_rate", "base_amount"],
"_Test Item Home Desktop 100": [62.5, 0, 62.5, 625.0, 50, 50, 465.37],
"_Test Item Home Desktop 200": [190.66, 0, 190.66, 953.3, 150, 150, 698.08],
}
# check if children are saved
self.assertEquals(len(si.get("entries")),
len(expected_values)-1)
# check if item values are calculated
for d in si.get("entries"):
for i, k in enumerate(expected_values["keys"]):
self.assertEquals(d.get(k), expected_values[d.item_code][i])
# check net total
self.assertEquals(si.net_total, 1163.45)
self.assertEquals(si.net_total_export, 1578.3)
# check tax calculation
expected_values = {
"keys": ["tax_amount", "tax_amount_after_discount_amount", "total"],
@ -182,11 +171,11 @@ class TestSalesInvoice(unittest.TestCase):
"_Test Account Discount - _TC": [-180.33, -168.54, 1516.88],
"_Test Account Service Tax - _TC": [-18.03, -16.88, 1500]
}
for d in si.get("other_charges"):
for i, k in enumerate(expected_values["keys"]):
self.assertEquals(d.get(k), expected_values[d.account_head][i])
self.assertEquals(si.grand_total, 1500)
self.assertEquals(si.grand_total_export, 1500)
@ -213,15 +202,15 @@ class TestSalesInvoice(unittest.TestCase):
expected_values = sorted([
[si.debit_to, 1500, 0.0],
[test_records[3][1]["income_account"], 0.0, 1163.45],
[test_records[3][3]["account_head"], 0.0, 130.31],
[test_records[3][4]["account_head"], 0.0, 2.61],
[test_records[3][5]["account_head"], 0.0, 1.31],
[test_records[3][6]["account_head"], 0.0, 25.96],
[test_records[3][7]["account_head"], 0.0, 145.43],
[test_records[3][8]["account_head"], 0.0, 116.35],
[test_records[3][9]["account_head"], 0.0, 100],
[test_records[3][10]["account_head"], 168.54, 0.0],
[test_records[3]["entries"][0]["income_account"], 0.0, 1163.45],
[test_records[3]["other_charges"][0]["account_head"], 0.0, 130.31],
[test_records[3]["other_charges"][1]["account_head"], 0.0, 2.61],
[test_records[3]["other_charges"][2]["account_head"], 0.0, 1.31],
[test_records[3]["other_charges"][3]["account_head"], 0.0, 25.96],
[test_records[3]["other_charges"][4]["account_head"], 0.0, 145.43],
[test_records[3]["other_charges"][5]["account_head"], 0.0, 116.35],
[test_records[3]["other_charges"][6]["account_head"], 0.0, 100],
[test_records[3]["other_charges"][7]["account_head"], 168.54, 0.0],
["_Test Account Service Tax - _TC", 16.88, 0.0],
])
@ -233,7 +222,7 @@ class TestSalesInvoice(unittest.TestCase):
# cancel
si.cancel()
gle = frappe.db.sql("""select * from `tabGL Entry`
gle = frappe.db.sql("""select * from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)
self.assertFalse(gle)
@ -242,44 +231,44 @@ class TestSalesInvoice(unittest.TestCase):
si = frappe.copy_doc(test_records[2])
for i, tax in enumerate(si.get("other_charges")):
tax.idx = i+1
si.get("entries")[0].price_list_rate = 62.5
si.get("entries")[0].price_list_rate = 191
for i in [2, 4, 5, 6, 7, 8]:
for i in xrange(6):
si.get("other_charges")[i].included_in_print_rate = 1
# tax type "Actual" cannot be inclusive
self.assertRaises(frappe.ValidationError, si.insert)
# taxes above included type 'On Previous Row Total' should also be included
si.get("other_charges")[0].included_in_print_rate = 0
self.assertRaises(frappe.ValidationError, si.insert)
def test_sales_invoice_calculation_base_currency_with_tax_inclusive_price(self):
# prepare
si = frappe.copy_doc(test_records[3])
si.insert()
expected_values = {
"keys": ["price_list_rate", "discount_percentage", "rate", "amount",
"keys": ["price_list_rate", "discount_percentage", "rate", "amount",
"base_price_list_rate", "base_rate", "base_amount"],
"_Test Item Home Desktop 100": [62.5, 0, 62.5, 625.0, 50, 50, 499.98],
"_Test Item Home Desktop 200": [190.66, 0, 190.66, 953.3, 150, 150, 750],
}
# check if children are saved
self.assertEquals(len(si.get("entries")),
len(expected_values)-1)
# check if item values are calculated
for d in si.get("entries"):
for i, k in enumerate(expected_values["keys"]):
self.assertEquals(d.get(k), expected_values[d.item_code][i])
# check net total
self.assertEquals(si.net_total, 1249.98)
self.assertEquals(si.net_total_export, 1578.3)
# check tax calculation
expected_values = {
"keys": ["tax_amount", "total"],
@ -292,14 +281,14 @@ class TestSalesInvoice(unittest.TestCase):
"_Test Account Shipping Charges - _TC": [100, 1803.31],
"_Test Account Discount - _TC": [-180.33, 1622.98]
}
for d in si.get("other_charges"):
for i, k in enumerate(expected_values["keys"]):
self.assertEquals(d.get(k), expected_values[d.account_head][i])
self.assertEquals(si.grand_total, 1622.98)
self.assertEquals(si.grand_total_export, 1622.98)
def test_sales_invoice_calculation_export_currency_with_tax_inclusive_price(self):
# prepare
si = frappe.copy_doc(test_records[3])
@ -309,30 +298,29 @@ class TestSalesInvoice(unittest.TestCase):
si.get("entries")[0].discount_percentage = 10
si.get("entries")[1].price_list_rate = 187.5
si.get("entries")[1].discount_percentage = 20
si.get("other_charges")[5].rate = 5000
si.get("other_charges")[6].rate = 5000
si.insert()
expected_values = {
"keys": ["price_list_rate", "discount_percentage", "rate", "amount",
"keys": ["price_list_rate", "discount_percentage", "rate", "amount",
"base_price_list_rate", "base_rate", "base_amount"],
"_Test Item Home Desktop 100": [55.56, 10, 50, 500, 2222.11, 1999.9, 19999.04],
"_Test Item Home Desktop 200": [187.5, 20, 150, 750, 7375.66, 5900.53, 29502.66],
}
# check if children are saved
self.assertEquals(len(si.get("entries")),
len(expected_values)-1)
self.assertEquals(len(si.get("entries")), len(expected_values)-1)
# check if item values are calculated
for d in si.get("entries"):
for i, k in enumerate(expected_values["keys"]):
self.assertEquals(d.get(k), expected_values[d.item_code][i])
# check net total
self.assertEquals(si.net_total, 49501.7)
self.assertEquals(si.net_total_export, 1250)
# check tax calculation
expected_values = {
"keys": ["tax_amount", "total"],
@ -345,134 +333,134 @@ class TestSalesInvoice(unittest.TestCase):
"_Test Account Shipping Charges - _TC": [5000, 72450.17],
"_Test Account Discount - _TC": [-7245.01, 65205.16]
}
for d in si.get("other_charges"):
for i, k in enumerate(expected_values["keys"]):
self.assertEquals(d.get(k), expected_values[d.account_head][i])
self.assertEquals(si.grand_total, 65205.16)
self.assertEquals(si.grand_total_export, 1304.1)
def test_outstanding(self):
w = self.make()
self.assertEquals(w.outstanding_amount, w.grand_total)
def test_payment(self):
frappe.db.sql("""delete from `tabGL Entry`""")
w = self.make()
from erpnext.accounts.doctype.journal_voucher.test_journal_voucher \
import test_records as jv_test_records
jv = frappe.get_doc(frappe.copy_doc(jv_test_records[0]))
jv.get("entries")[0].against_invoice = w.name
jv.insert()
jv.submit()
self.assertEquals(frappe.db.get_value("Sales Invoice", w.name, "outstanding_amount"),
161.8)
jv.cancel()
self.assertEquals(frappe.db.get_value("Sales Invoice", w.name, "outstanding_amount"),
561.8)
def test_time_log_batch(self):
tlb = frappe.get_doc("Time Log Batch", "_T-Time Log Batch-00001")
tlb.submit()
si = frappe.get_doc(frappe.copy_doc(test_records[0]))
si.get("entries")[0].time_log_batch = "_T-Time Log Batch-00001"
si.insert()
si.submit()
self.assertEquals(frappe.db.get_value("Time Log Batch", "_T-Time Log Batch-00001",
"status"), "Billed")
self.assertEquals(frappe.db.get_value("Time Log", "_T-Time Log-00001", "status"),
self.assertEquals(frappe.db.get_value("Time Log", "_T-Time Log-00001", "status"),
"Billed")
si.cancel()
self.assertEquals(frappe.db.get_value("Time Log Batch", "_T-Time Log Batch-00001",
self.assertEquals(frappe.db.get_value("Time Log Batch", "_T-Time Log Batch-00001",
"status"), "Submitted")
self.assertEquals(frappe.db.get_value("Time Log", "_T-Time Log-00001", "status"),
self.assertEquals(frappe.db.get_value("Time Log", "_T-Time Log-00001", "status"),
"Batched for Billing")
def test_sales_invoice_gl_entry_without_aii(self):
self.clear_stock_account_balance()
set_perpetual_inventory(0)
si = frappe.copy_doc(test_records[1])
si.insert()
si.submit()
gl_entries = frappe.db.sql("""select account, debit, credit
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
order by account asc""", si.name, as_dict=1)
self.assertTrue(gl_entries)
expected_values = sorted([
[si.debit_to, 630.0, 0.0],
[test_records[1][1]["income_account"], 0.0, 500.0],
[test_records[1][2]["account_head"], 0.0, 80.0],
[test_records[1][3]["account_head"], 0.0, 50.0],
[test_records[1]["entries"][0]["income_account"], 0.0, 500.0],
[test_records[1]["other_charges"][0]["account_head"], 0.0, 80.0],
[test_records[1]["other_charges"][1]["account_head"], 0.0, 50.0],
])
for i, gle in enumerate(gl_entries):
self.assertEquals(expected_values[i][0], gle.account)
self.assertEquals(expected_values[i][1], gle.debit)
self.assertEquals(expected_values[i][2], gle.credit)
# cancel
si.cancel()
gle = frappe.db.sql("""select * from `tabGL Entry`
gle = frappe.db.sql("""select * from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)
self.assertFalse(gle)
def test_pos_gl_entry_with_aii(self):
self.clear_stock_account_balance()
set_perpetual_inventory()
self._insert_purchase_receipt()
self._insert_pos_settings()
pos = frappe.copy_doc(test_records[1])
pos[0]["is_pos"] = 1
pos[0]["update_stock"] = 1
pos[0]["posting_time"] = "12:05"
pos[0]["cash_bank_account"] = "_Test Account Bank Account - _TC"
pos[0]["paid_amount"] = 600.0
pos = copy.deepcopy(test_records[1])
pos["is_pos"] = 1
pos["update_stock"] = 1
pos["posting_time"] = "12:05"
pos["cash_bank_account"] = "_Test Account Bank Account - _TC"
pos["paid_amount"] = 600.0
si = frappe.copy_doc(pos)
si.insert()
si.submit()
# check stock ledger entries
sle = frappe.db.sql("""select * from `tabStock Ledger Entry`
where voucher_type = 'Sales Invoice' and voucher_no = %s""",
sle = frappe.db.sql("""select * from `tabStock Ledger Entry`
where voucher_type = 'Sales Invoice' and voucher_no = %s""",
si.name, as_dict=1)[0]
self.assertTrue(sle)
self.assertEquals([sle.item_code, sle.warehouse, sle.actual_qty],
self.assertEquals([sle.item_code, sle.warehouse, sle.actual_qty],
["_Test Item", "_Test Warehouse - _TC", -1.0])
# check gl entries
gl_entries = frappe.db.sql("""select account, debit, credit
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
order by account asc, debit asc""", si.name, as_dict=1)
self.assertTrue(gl_entries)
stock_in_hand = frappe.db.get_value("Account", {"master_name": "_Test Warehouse - _TC"})
expected_gl_entries = sorted([
[si.debit_to, 630.0, 0.0],
[pos[1]["income_account"], 0.0, 500.0],
[pos[2]["account_head"], 0.0, 80.0],
[pos[3]["account_head"], 0.0, 50.0],
[pos["entries"][0]["income_account"], 0.0, 500.0],
[pos["other_charges"][0]["account_head"], 0.0, 80.0],
[pos["other_charges"][1]["account_head"], 0.0, 50.0],
[stock_in_hand, 0.0, 75.0],
[pos[1]["expense_account"], 75.0, 0.0],
[pos["entries"][0]["expense_account"], 75.0, 0.0],
[si.debit_to, 0.0, 600.0],
["_Test Account Bank Account - _TC", 600.0, 0.0]
])
@ -480,57 +468,57 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEquals(expected_gl_entries[i][0], gle.account)
self.assertEquals(expected_gl_entries[i][1], gle.debit)
self.assertEquals(expected_gl_entries[i][2], gle.credit)
si.cancel()
gle = frappe.db.sql("""select * from `tabGL Entry`
gle = frappe.db.sql("""select * from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)
self.assertFalse(gle)
self.assertFalse(get_stock_and_account_difference([stock_in_hand]))
set_perpetual_inventory(0)
def test_si_gl_entry_with_aii_and_update_stock_with_warehouse_but_no_account(self):
self.clear_stock_account_balance()
set_perpetual_inventory()
frappe.delete_doc("Account", "_Test Warehouse No Account - _TC")
# insert purchase receipt
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import test_records \
as pr_test_records
pr = frappe.copy_doc(pr_test_records[0])
pr.naming_series = "_T-Purchase Receipt-"
pr.get("entries")[0].warehouse = "_Test Warehouse No Account - _TC"
pr.get("purchase_receipt_details")[0].warehouse = "_Test Warehouse No Account - _TC"
pr.insert()
pr.submit()
si_doc = frappe.copy_doc(test_records[1])
si_doc = copy.deepcopy(test_records[1])
si_doc["update_stock"] = 1
si_doc["posting_time"] = "12:05"
si_doc.get("entries")["warehouse"] = "_Test Warehouse No Account - _TC"
si_doc.get("entries")[0]["warehouse"] = "_Test Warehouse No Account - _TC"
si = frappe.copy_doc(si_doc)
si.insert()
si.submit()
# check stock ledger entries
sle = frappe.db.sql("""select * from `tabStock Ledger Entry`
where voucher_type = 'Sales Invoice' and voucher_no = %s""",
sle = frappe.db.sql("""select * from `tabStock Ledger Entry`
where voucher_type = 'Sales Invoice' and voucher_no = %s""",
si.name, as_dict=1)[0]
self.assertTrue(sle)
self.assertEquals([sle.item_code, sle.warehouse, sle.actual_qty],
self.assertEquals([sle.item_code, sle.warehouse, sle.actual_qty],
["_Test Item", "_Test Warehouse No Account - _TC", -1.0])
# check gl entries
gl_entries = frappe.db.sql("""select account, debit, credit
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
order by account asc, debit asc""", si.name, as_dict=1)
self.assertTrue(gl_entries)
expected_gl_entries = sorted([
[si.debit_to, 630.0, 0.0],
[si_doc.get("entries")["income_account"], 0.0, 500.0],
[si_doc.get("entries")[0]["income_account"], 0.0, 500.0],
[si_doc.get("other_charges")[0]["account_head"], 0.0, 80.0],
[si_doc.get("other_charges")[1]["account_head"], 0.0, 50.0],
])
@ -538,69 +526,67 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEquals(expected_gl_entries[i][0], gle.account)
self.assertEquals(expected_gl_entries[i][1], gle.debit)
self.assertEquals(expected_gl_entries[i][2], gle.credit)
si.cancel()
gle = frappe.db.sql("""select * from `tabGL Entry`
gle = frappe.db.sql("""select * from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)
self.assertFalse(gle)
set_perpetual_inventory(0)
def test_sales_invoice_gl_entry_with_aii_no_item_code(self):
self.clear_stock_account_balance()
set_perpetual_inventory()
si_copy = frappe.copy_doc(test_records[1])
si_copy[1]["item_code"] = None
si = frappe.get_doc(si_copy)
si = frappe.get_doc(test_records[1])
si.get("entries")[0].item_code = None
si.insert()
si.submit()
gl_entries = frappe.db.sql("""select account, debit, credit
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
order by account asc""", si.name, as_dict=1)
self.assertTrue(gl_entries)
expected_values = sorted([
[si.debit_to, 630.0, 0.0],
[test_records[1][1]["income_account"], 0.0, 500.0],
[test_records[1][2]["account_head"], 0.0, 80.0],
[test_records[1][3]["account_head"], 0.0, 50.0],
[test_records[1]["entries"][0]["income_account"], 0.0, 500.0],
[test_records[1]["other_charges"][0]["account_head"], 0.0, 80.0],
[test_records[1]["other_charges"][1]["account_head"], 0.0, 50.0],
])
for i, gle in enumerate(gl_entries):
self.assertEquals(expected_values[i][0], gle.account)
self.assertEquals(expected_values[i][1], gle.debit)
self.assertEquals(expected_values[i][2], gle.credit)
set_perpetual_inventory(0)
def test_sales_invoice_gl_entry_with_aii_non_stock_item(self):
self.clear_stock_account_balance()
set_perpetual_inventory()
si_copy = frappe.copy_doc(test_records[1])
si_copy[1]["item_code"] = "_Test Non Stock Item"
si = frappe.get_doc(si_copy)
si = frappe.get_doc(test_records[1])
si.get("entries")[0].item_code = "_Test Non Stock Item"
si.insert()
si.submit()
gl_entries = frappe.db.sql("""select account, debit, credit
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
order by account asc""", si.name, as_dict=1)
self.assertTrue(gl_entries)
expected_values = sorted([
[si.debit_to, 630.0, 0.0],
[test_records[1][1]["income_account"], 0.0, 500.0],
[test_records[1][2]["account_head"], 0.0, 80.0],
[test_records[1][3]["account_head"], 0.0, 50.0],
[test_records[1]["entries"][0]["income_account"], 0.0, 500.0],
[test_records[1]["other_charges"][0]["account_head"], 0.0, 80.0],
[test_records[1]["other_charges"][1]["account_head"], 0.0, 50.0],
])
for i, gle in enumerate(gl_entries):
self.assertEquals(expected_values[i][0], gle.account)
self.assertEquals(expected_values[i][1], gle.debit)
self.assertEquals(expected_values[i][2], gle.credit)
set_perpetual_inventory(0)
def _insert_purchase_receipt(self):
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import test_records \
as pr_test_records
@ -608,7 +594,7 @@ class TestSalesInvoice(unittest.TestCase):
pr.naming_series = "_T-Purchase Receipt-"
pr.insert()
pr.submit()
def _insert_delivery_note(self):
from erpnext.stock.doctype.delivery_note.test_delivery_note import test_records \
as dn_test_records
@ -617,23 +603,23 @@ class TestSalesInvoice(unittest.TestCase):
dn.insert()
dn.submit()
return dn
def _insert_pos_settings(self):
from erpnext.accounts.doctype.pos_setting.test_pos_setting \
import test_records as pos_setting_test_records
frappe.db.sql("""delete from `tabPOS Setting`""")
ps = frappe.copy_doc(pos_setting_test_records[0])
ps.insert()
def test_sales_invoice_with_advance(self):
from erpnext.accounts.doctype.journal_voucher.test_journal_voucher \
import test_records as jv_test_records
jv = frappe.copy_doc(jv_test_records[0])
jv.insert()
jv.submit()
si = frappe.copy_doc(test_records[0])
si.append("advance_adjustment_details", {
"doctype": "Sales Invoice Advance",
@ -646,20 +632,20 @@ class TestSalesInvoice(unittest.TestCase):
si.insert()
si.submit()
si.load_from_db()
self.assertTrue(frappe.db.sql("""select name from `tabJournal Voucher Detail`
where against_invoice=%s""", si.name))
self.assertTrue(frappe.db.sql("""select name from `tabJournal Voucher Detail`
where against_invoice=%s and credit=300""", si.name))
self.assertEqual(si.outstanding_amount, 261.8)
si.cancel()
self.assertTrue(not frappe.db.sql("""select name from `tabJournal Voucher Detail`
where against_invoice=%s""", si.name))
def test_recurring_invoice(self):
from frappe.utils import get_first_day, get_last_day, add_to_date, nowdate, getdate
from erpnext.accounts.utils import get_fiscal_year
@ -675,13 +661,13 @@ class TestSalesInvoice(unittest.TestCase):
"invoice_period_from_date": get_first_day(today),
"invoice_period_to_date": get_last_day(today)
})
# monthly
si1 = frappe.copy_doc(base_si)
si1.insert()
si1.submit()
self._test_recurring_invoice(si1, True)
# monthly without a first and last day period
si2 = frappe.copy_doc(base_si)
si2.update({
@ -691,7 +677,7 @@ class TestSalesInvoice(unittest.TestCase):
si2.insert()
si2.submit()
self._test_recurring_invoice(si2, False)
# quarterly
si3 = frappe.copy_doc(base_si)
si3.update({
@ -702,7 +688,7 @@ class TestSalesInvoice(unittest.TestCase):
si3.insert()
si3.submit()
self._test_recurring_invoice(si3, True)
# quarterly without a first and last day period
si4 = frappe.copy_doc(base_si)
si4.update({
@ -713,7 +699,7 @@ class TestSalesInvoice(unittest.TestCase):
si4.insert()
si4.submit()
self._test_recurring_invoice(si4, False)
# yearly
si5 = frappe.copy_doc(base_si)
si5.update({
@ -724,7 +710,7 @@ class TestSalesInvoice(unittest.TestCase):
si5.insert()
si5.submit()
self._test_recurring_invoice(si5, True)
# yearly without a first and last day period
si6 = frappe.copy_doc(base_si)
si6.update({
@ -735,7 +721,7 @@ class TestSalesInvoice(unittest.TestCase):
si6.insert()
si6.submit()
self._test_recurring_invoice(si6, False)
# change posting date but keep recuring day to be today
si7 = frappe.copy_doc(base_si)
si7.update({
@ -743,7 +729,7 @@ class TestSalesInvoice(unittest.TestCase):
})
si7.insert()
si7.submit()
# setting so that _test function works
si7.posting_date = today
self._test_recurring_invoice(si7, True)
@ -752,52 +738,52 @@ class TestSalesInvoice(unittest.TestCase):
from frappe.utils import add_months, get_last_day
from erpnext.accounts.doctype.sales_invoice.sales_invoice \
import manage_recurring_invoices, get_next_date
no_of_months = ({"Monthly": 1, "Quarterly": 3, "Yearly": 12})[base_si.recurring_type]
def _test(i):
self.assertEquals(i+1, frappe.db.sql("""select count(*) from `tabSales Invoice`
where recurring_id=%s and docstatus=1""", base_si.recurring_id)[0][0])
next_date = get_next_date(base_si.posting_date, no_of_months,
next_date = get_next_date(base_si.posting_date, no_of_months,
base_si.repeat_on_day_of_month)
manage_recurring_invoices(next_date=next_date, commit=False)
recurred_invoices = frappe.db.sql("""select name from `tabSales Invoice`
where recurring_id=%s and docstatus=1 order by name desc""",
base_si.recurring_id)
self.assertEquals(i+2, len(recurred_invoices))
new_si = frappe.get_doc("Sales Invoice", recurred_invoices[0][0])
for fieldname in ["convert_into_recurring_invoice", "recurring_type",
"repeat_on_day_of_month", "notification_email_address"]:
self.assertEquals(base_si.get(fieldname),
new_si.get(fieldname))
self.assertEquals(new_si.posting_date, unicode(next_date))
self.assertEquals(new_si.invoice_period_from_date,
unicode(add_months(base_si.invoice_period_from_date, no_of_months)))
if first_and_last_day:
self.assertEquals(new_si.invoice_period_to_date,
self.assertEquals(new_si.invoice_period_to_date,
unicode(get_last_day(add_months(base_si.invoice_period_to_date,
no_of_months))))
else:
self.assertEquals(new_si.invoice_period_to_date,
self.assertEquals(new_si.invoice_period_to_date,
unicode(add_months(base_si.invoice_period_to_date, no_of_months)))
return new_si
# if yearly, test 1 repetition, else test 5 repetitions
count = 1 if (no_of_months == 12) else 5
for i in xrange(count):
base_si = _test(i)
def clear_stock_account_balance(self):
frappe.db.sql("delete from `tabStock Ledger Entry`")
frappe.db.sql("delete from tabBin")
@ -806,10 +792,10 @@ class TestSalesInvoice(unittest.TestCase):
def test_serialized(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
se = make_serialized_item()
serial_nos = get_serial_nos(se.get("entries")[0].serial_no)
serial_nos = get_serial_nos(se.get("mtn_details")[0].serial_no)
si = frappe.copy_doc(test_records[0])
si.update_stock = 1
si.get("entries")[0].item_code = "_Test Serialized Item With Series"
@ -817,14 +803,14 @@ class TestSalesInvoice(unittest.TestCase):
si.get("entries")[0].serial_no = serial_nos[0]
si.insert()
si.submit()
self.assertEquals(frappe.db.get_value("Serial No", serial_nos[0], "status"), "Delivered")
self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0], "warehouse"))
self.assertEquals(frappe.db.get_value("Serial No", serial_nos[0],
self.assertEquals(frappe.db.get_value("Serial No", serial_nos[0],
"delivery_document_no"), si.name)
return si
def test_serialized_cancel(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
si = self.test_serialized()
@ -834,20 +820,20 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEquals(frappe.db.get_value("Serial No", serial_nos[0], "status"), "Available")
self.assertEquals(frappe.db.get_value("Serial No", serial_nos[0], "warehouse"), "_Test Warehouse - _TC")
self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0],
self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0],
"delivery_document_no"))
def test_serialize_status(self):
from erpnext.stock.doctype.serial_no.serial_no import SerialNoStatusError, get_serial_nos
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
se = make_serialized_item()
serial_nos = get_serial_nos(se.get("entries")[0].serial_no)
serial_nos = get_serial_nos(se.get("mtn_details")[0].serial_no)
sr = frappe.get_doc("Serial No", serial_nos[0])
sr.status = "Not Available"
sr.save()
si = frappe.copy_doc(test_records[0])
si.update_stock = 1
si.get("entries")[0].item_code = "_Test Serialized Item With Series"
@ -858,5 +844,4 @@ class TestSalesInvoice(unittest.TestCase):
self.assertRaises(SerialNoStatusError, si.submit)
test_dependencies = ["Journal Voucher", "POS Setting", "Contact", "Address"]
test_records = frappe.get_test_records('Sales Invoice')
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 erpnext.controllers.buying_controller import BuyingController
class PurchaseOrder(BuyingController):
tname = 'Purchase Order Item'
@ -24,15 +24,15 @@ class PurchaseOrder(BuyingController):
'source_field': 'qty',
'percent_join_field': 'prevdoc_docname',
}]
def validate(self):
super(PurchaseOrder, self).validate()
if not self.status:
self.status = "Draft"
from erpnext.utilities import validate_status
validate_status(self.status, ["Draft", "Submitted", "Stopped",
validate_status(self.status, ["Draft", "Submitted", "Stopped",
"Cancelled"])
pc_obj = frappe.get_doc('Purchase Common')
@ -45,7 +45,7 @@ class PurchaseOrder(BuyingController):
self.validate_with_previous_doc()
self.validate_for_subcontracting()
self.update_raw_materials_supplied("po_raw_material_details")
def validate_with_previous_doc(self):
super(PurchaseOrder, self).validate_with_previous_doc(self.tname, {
"Supplier Quotation": {
@ -54,7 +54,7 @@ class PurchaseOrder(BuyingController):
},
"Supplier Quotation Item": {
"ref_dn_field": "supplier_quotation_item",
"compare_fields": [["rate", "="], ["project_name", "="], ["item_code", "="],
"compare_fields": [["rate", "="], ["project_name", "="], ["item_code", "="],
["uom", "="]],
"is_child_table": True
}
@ -65,11 +65,11 @@ class PurchaseOrder(BuyingController):
if d.prevdoc_detail_docname and not d.schedule_date:
d.schedule_date = frappe.db.get_value("Material Request Item",
d.prevdoc_detail_docname, "schedule_date")
def get_last_purchase_rate(self):
frappe.get_doc('Purchase Common').get_last_purchase_rate(self)
# Check for Stopped status
# Check for Stopped status
def check_for_stopped_status(self, pc_obj):
check_list =[]
for d in self.get('po_details'):
@ -77,7 +77,7 @@ class PurchaseOrder(BuyingController):
check_list.append(d.prevdoc_docname)
pc_obj.check_for_stopped_status( d.prevdoc_doctype, d.prevdoc_docname)
def update_bin(self, is_submit, is_stopped = 0):
from erpnext.stock.utils import update_bin
pc_obj = frappe.get_doc('Purchase Common')
@ -87,29 +87,29 @@ class PurchaseOrder(BuyingController):
# this happens when item is changed from non-stock to stock item
if not d.warehouse:
continue
ind_qty, po_qty = 0, flt(d.qty) * flt(d.conversion_factor)
if is_stopped:
po_qty = flt(d.qty) > flt(d.received_qty) and \
flt( flt(flt(d.qty) - flt(d.received_qty))*flt(d.conversion_factor)) or 0
flt( flt(flt(d.qty) - flt(d.received_qty))*flt(d.conversion_factor)) or 0
# No updates in Material Request on Stop / Unstop
if cstr(d.prevdoc_doctype) == 'Material Request' and not is_stopped:
# get qty and pending_qty of prevdoc
# get qty and pending_qty of prevdoc
curr_ref_qty = pc_obj.get_qty(d.doctype, 'prevdoc_detail_docname',
d.prevdoc_detail_docname, 'Material Request Item',
d.prevdoc_detail_docname, 'Material Request Item',
'Material Request - Purchase Order', self.name)
max_qty, qty, curr_qty = flt(curr_ref_qty.split('~~~')[1]), \
flt(curr_ref_qty.split('~~~')[0]), 0
if flt(qty) + flt(po_qty) > flt(max_qty):
curr_qty = flt(max_qty) - flt(qty)
# special case as there is no restriction
# for Material Request - Purchase Order
# special case as there is no restriction
# for Material Request - Purchase Order
curr_qty = curr_qty > 0 and curr_qty or 0
else:
curr_qty = flt(po_qty)
ind_qty = -flt(curr_qty)
# Update ordered_qty and indented_qty in bin
@ -121,12 +121,12 @@ class PurchaseOrder(BuyingController):
"posting_date": self.transaction_date
}
update_bin(args)
def check_modified_date(self):
mod_db = frappe.db.sql("select modified from `tabPurchase Order` where name = %s",
mod_db = frappe.db.sql("select modified from `tabPurchase Order` where name = %s",
self.name)
date_diff = frappe.db.sql("select TIMEDIFF('%s', '%s')" % ( mod_db[0][0],cstr(self.modified)))
if date_diff and date_diff[0][0]:
msgprint(cstr(self.doctype) +" => "+ cstr(self.name) +" has been modified. Please Refresh. ")
raise Exception
@ -144,28 +144,28 @@ class PurchaseOrder(BuyingController):
def on_submit(self):
purchase_controller = frappe.get_doc("Purchase Common")
self.update_prevdoc_status()
self.update_bin(is_submit = 1, is_stopped = 0)
frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype,
frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype,
self.company, self.grand_total)
purchase_controller.update_last_purchase_rate(self, is_submit = 1)
frappe.db.set(self,'status','Submitted')
def on_cancel(self):
pc_obj = frappe.get_doc(dt = 'Purchase Common')
pc_obj = frappe.get_doc(dt = 'Purchase Common')
self.check_for_stopped_status(pc_obj)
# Check if Purchase Receipt has been submitted against current Purchase Order
pc_obj.check_docstatus(check = 'Next', doctype = 'Purchase Receipt', docname = self.name, detail_doctype = 'Purchase Receipt Item')
# Check if Purchase Invoice has been submitted against current Purchase Order
submitted = frappe.db.sql("""select t1.name
from `tabPurchase Invoice` t1,`tabPurchase Invoice Item` t2
where t1.name = t2.parent and t2.purchase_order = %s and t1.docstatus = 1""",
submitted = frappe.db.sql("""select t1.name
from `tabPurchase Invoice` t1,`tabPurchase Invoice Item` t2
where t1.name = t2.parent and t2.purchase_order = %s and t1.docstatus = 1""",
self.name)
if submitted:
msgprint("Purchase Invoice : " + cstr(submitted[0][0]) + " has already been submitted !")
@ -175,14 +175,14 @@ class PurchaseOrder(BuyingController):
self.update_prevdoc_status()
self.update_bin( is_submit = 0, is_stopped = 0)
pc_obj.update_last_purchase_rate(self, is_submit = 0)
def on_update(self):
pass
@frappe.whitelist()
def make_purchase_receipt(source_name, target_doc=None):
from frappe.model.mapper import get_mapped_doc
def set_missing_values(source, target):
doc = frappe.get_doc(target)
doc.run_method("set_missing_values")
@ -193,35 +193,35 @@ def make_purchase_receipt(source_name, target_doc=None):
target.amount = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.rate)
target.base_amount = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.base_rate)
doclist = get_mapped_doc("Purchase Order", source_name, {
doc = get_mapped_doc("Purchase Order", source_name, {
"Purchase Order": {
"doctype": "Purchase Receipt",
"doctype": "Purchase Receipt",
"validation": {
"docstatus": ["=", 1],
}
},
},
"Purchase Order Item": {
"doctype": "Purchase Receipt Item",
"doctype": "Purchase Receipt Item",
"field_map": {
"name": "prevdoc_detail_docname",
"parent": "prevdoc_docname",
"parenttype": "prevdoc_doctype",
"name": "prevdoc_detail_docname",
"parent": "prevdoc_docname",
"parenttype": "prevdoc_doctype",
},
"postprocess": update_item,
"condition": lambda doc: doc.received_qty < doc.qty
},
},
"Purchase Taxes and Charges": {
"doctype": "Purchase Taxes and Charges",
"doctype": "Purchase Taxes and Charges",
"add_if_empty": True
}
}, target_doc, set_missing_values)
return doclist.as_dict()
return doc
@frappe.whitelist()
def make_purchase_invoice(source_name, target_doc=None):
from frappe.model.mapper import get_mapped_doc
def set_missing_values(source, target):
doc = frappe.get_doc(target)
doc.run_method("set_missing_values")
@ -232,26 +232,26 @@ def make_purchase_invoice(source_name, target_doc=None):
if flt(obj.base_rate):
target.qty = target.base_amount / flt(obj.base_rate)
doclist = get_mapped_doc("Purchase Order", source_name, {
doc = get_mapped_doc("Purchase Order", source_name, {
"Purchase Order": {
"doctype": "Purchase Invoice",
"doctype": "Purchase Invoice",
"validation": {
"docstatus": ["=", 1],
}
},
},
"Purchase Order Item": {
"doctype": "Purchase Invoice Item",
"doctype": "Purchase Invoice Item",
"field_map": {
"name": "po_detail",
"parent": "purchase_order",
"name": "po_detail",
"parent": "purchase_order",
},
"postprocess": update_item,
"condition": lambda doc: doc.base_amount==0 or doc.billed_amt < doc.amount
},
"condition": lambda doc: doc.base_amount==0 or doc.billed_amt < doc.amount
},
"Purchase Taxes and Charges": {
"doctype": "Purchase Taxes and Charges",
"doctype": "Purchase Taxes and Charges",
"add_if_empty": True
}
}, target_doc, set_missing_values)
return doclist.as_dict()
return doc

View File

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

View File

@ -13,22 +13,22 @@ class TestPurchaseOrder(unittest.TestCase):
sq = frappe.copy_doc(test_records[0]).insert()
self.assertRaises(frappe.ValidationError, make_purchase_order,
self.assertRaises(frappe.ValidationError, make_purchase_order,
sq.name)
sq = frappe.get_doc("Supplier Quotation", sq.name)
sq.submit()
po = make_purchase_order(sq.name)
self.assertEquals(po[0]["doctype"], "Purchase Order")
self.assertEquals(len(po), len(sq))
po[0]["naming_series"] = "_T-Purchase Order-"
for doc in po:
self.assertEquals(po.doctype, "Purchase Order")
self.assertEquals(len(po.get("po_details")), len(sq.get("quotation_items")))
po.naming_series = "_T-Purchase Order-"
for doc in po.get("po_details"):
if doc.get("item_code"):
doc["schedule_date"] = "2013-04-12"
doc.set("schedule_date", "2013-04-12")
frappe.get_doc(po).insert()
test_records = frappe.get_test_records('Supplier Quotation')
po.insert()
test_records = frappe.get_test_records('Supplier Quotation')

View File

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

View File

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

View File

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

View File

@ -12,19 +12,19 @@ class TestBOM(unittest.TestCase):
def test_get_items(self):
from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
items_dict = get_bom_items_as_dict(bom="BOM/_Test FG Item 2/001", qty=1, fetch_exploded=0)
self.assertTrue(test_records[2][1]["item_code"] in items_dict)
self.assertTrue(test_records[2][2]["item_code"] in items_dict)
self.assertTrue(test_records[2]["bom_materials"][0]["item_code"] in items_dict)
self.assertTrue(test_records[2]["bom_materials"][1]["item_code"] in items_dict)
self.assertEquals(len(items_dict.values()), 2)
def test_get_items_exploded(self):
from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
items_dict = get_bom_items_as_dict(bom="BOM/_Test FG Item 2/001", qty=1, fetch_exploded=1)
self.assertTrue(test_records[2][1]["item_code"] in items_dict)
self.assertFalse(test_records[2][2]["item_code"] in items_dict)
self.assertTrue(test_records[0][1]["item_code"] in items_dict)
self.assertTrue(test_records[0][2]["item_code"] in items_dict)
self.assertTrue(test_records[2]["bom_materials"][0]["item_code"] in items_dict)
self.assertFalse(test_records[2]["bom_materials"][1]["item_code"] in items_dict)
self.assertTrue(test_records[0]["bom_materials"][0]["item_code"] in items_dict)
self.assertTrue(test_records[0]["bom_materials"][1]["item_code"] in items_dict)
self.assertEquals(len(items_dict.values()), 3)
def test_get_items_list(self):
from erpnext.manufacturing.doctype.bom.bom import get_bom_items
self.assertEquals(len(get_bom_items(bom="BOM/_Test FG Item 2/001", qty=1, fetch_exploded=1)), 3)
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 `tabBin`""")
frappe.db.sql("""delete from `tabGL Entry`""")
pro_doc = frappe.copy_doc(test_records[0])
pro_doc.insert()
pro_doc.submit()
from erpnext.stock.doctype.stock_entry.test_stock_entry import test_records as se_test_records
mr1 = frappe.copy_doc(se_test_records[0])
mr1.insert()
mr1.submit()
mr2 = frappe.copy_doc(se_test_records[0])
mr2.get("mtn_details")[0].item_code = "_Test Item Home Desktop 100"
mr2.insert()
mr2.submit()
stock_entry = make_stock_entry(pro_doc.name, "Manufacture/Repack")
stock_entry = frappe.get_doc(stock_entry)
stock_entry.fiscal_year = "_Test Fiscal Year 2013"
stock_entry.fg_completed_qty = 4
stock_entry.posting_date = "2013-05-12"
stock_entry.fiscal_year = "_Test Fiscal Year 2013"
stock_entry.set("mtn_details", [])
stock_entry.run_method("get_items")
stock_entry.submit()
self.assertEqual(frappe.db.get_value("Production Order", pro_doc.name,
self.assertEqual(frappe.db.get_value("Production Order", pro_doc.name,
"produced_qty"), 4)
self.assertEqual(frappe.db.get_value("Bin", {"item_code": "_Test FG Item",
self.assertEqual(frappe.db.get_value("Bin", {"item_code": "_Test FG Item",
"warehouse": "_Test Warehouse 1 - _TC"}, "planned_qty"), 6)
return pro_doc.name
def test_over_production(self):
from erpnext.stock.doctype.stock_entry.stock_entry import StockOverProductionError
pro_order = self.test_planned_qty()
stock_entry = make_stock_entry(pro_order, "Manufacture/Repack")
stock_entry = frappe.get_doc(stock_entry)
stock_entry.posting_date = "2013-05-12"
stock_entry.fiscal_year = "_Test Fiscal Year 2013"
stock_entry.fg_completed_qty = 15
stock_entry.set("mtn_details", [])
stock_entry.run_method("get_items")
stock_entry.insert()
self.assertRaises(StockOverProductionError, stock_entry.submit)
test_records = frappe.get_test_records('Production Order')
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
class TestTimeLog(unittest.TestCase):
def test_duplication(self):
def test_duplication(self):
ts = frappe.get_doc(frappe.copy_doc(test_records[0]))
self.assertRaises(OverlapError, ts.insert)
test_records = frappe.get_test_records('Time Log')
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.submit()
self.assertEquals(frappe.db.get_value("Time Log", time_log.name, "status"), "Submitted")
tlb = frappe.copy_doc(test_records[0])
tlb["time_log_batch_details"][0].time_log = time_log.name
tlb.get("time_log_batch_details")[0].time_log = time_log.name
tlb.insert()
tlb.submit()
@ -25,4 +25,6 @@ class TimeLogBatchTest(unittest.TestCase):
tlb.cancel()
self.assertEquals(frappe.db.get_value("Time Log", time_log.name, "status"), "Submitted")
test_records = frappe.get_test_records('Time Log Batch')
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:
frappe.msgprint(_("Time Log must have status 'Submitted'") + \
" :" + tl.name + " (" + _(tl.status) + ")", raise_exception=True)
def set_status(self):
self.status = {
"0": "Draft",
"1": "Submitted",
"2": "Cancelled"
}[str(self.docstatus or 0)]
if self.sales_invoice:
self.status = "Billed"
def on_submit(self):
self.update_status(self.name)
@ -57,4 +57,4 @@ class TimeLogBatch(Document):
tl = frappe.get_doc("Time Log", d.time_log)
tl.time_log_batch = time_log_batch
tl.sales_invoice = self.sales_invoice
tl.update_after_submit()
tl.save()

View File

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

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)
return doclist.as_dict()
return doclist
@frappe.whitelist()
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
customer = make_customer("_T-Lead-00001")
self.assertEquals(customer[0]["doctype"], "Customer")
self.assertEquals(customer[0]["lead_name"], "_T-Lead-00001")
self.assertEquals(customer.doctype, "Customer")
self.assertEquals(customer.lead_name, "_T-Lead-00001")
customer[0]["company"] = "_Test Company"
customer[0]["customer_group"] = "_Test Customer Group"
frappe.get_doc(customer).insert()
customer.company = "_Test Company"
customer.customer_group = "_Test Customer Group"
customer.insert()

View File

@ -161,4 +161,4 @@ def make_quotation(source_name, target_doc=None):
}
}, 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
return doclist.as_dict()
return doclist
def _make_customer(source_name, ignore_permissions=False):
quotation = frappe.db.get_value("Quotation", source_name, ["lead", "order_type"])

View File

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

View File

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

View File

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

View File

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

View File

@ -245,7 +245,7 @@ def make_purchase_order(source_name, target_doc=None):
}
}, target_doc, set_missing_values)
return doclist.as_dict()
return doclist
@frappe.whitelist()
def make_purchase_order_based_on_supplier(source_name, target_doc=None):
@ -325,7 +325,7 @@ def make_supplier_quotation(source_name, target_doc=None):
}
}, target_doc, set_missing_values)
return doclist.as_dict()
return doclist
@frappe.whitelist()
def make_stock_entry(source_name, target_doc=None):
@ -361,4 +361,4 @@ def make_stock_entry(source_name, target_doc=None):
}
}, target_doc, set_missing_values)
return doclist.as_dict()
return doclist

View File

@ -323,4 +323,4 @@ def make_purchase_invoice(source_name, target_doc=None):
}
}, 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):
"""validation: finished good quantity should be same as manufacturing quantity"""
import json
for d in self.get('mtn_details'):
if d.bom_no and flt(d.transfer_qty) != flt(self.fg_completed_qty):
msgprint(_("Row #") + " %s: " % d.idx

View File

@ -59,4 +59,4 @@ def make_maintenance_visit(source_name, target_doc=None):
}
}, 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)
return doclist.as_dict()
return doclist