Fixed Test Cases frappe/frappe#478
This commit is contained in:
parent
cd71e1d8ab
commit
d29465029d
@ -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
|
||||
|
@ -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')
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
|
@ -88,4 +88,4 @@ def make_purchase_order(source_name, target_doc=None):
|
||||
},
|
||||
}, target_doc, set_missing_values)
|
||||
|
||||
return doclist.as_dict()
|
||||
return doclist
|
@ -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')
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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')
|
||||
|
@ -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"]
|
||||
|
@ -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"]
|
||||
|
@ -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()
|
||||
|
@ -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))
|
||||
|
@ -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):
|
||||
|
@ -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()
|
||||
|
@ -161,4 +161,4 @@ def make_quotation(source_name, target_doc=None):
|
||||
}
|
||||
}, target_doc, set_missing_values)
|
||||
|
||||
return doclist.as_dict()
|
||||
return doclist
|
@ -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"])
|
||||
|
@ -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')
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -323,4 +323,4 @@ def make_purchase_invoice(source_name, target_doc=None):
|
||||
}
|
||||
}, target_doc, set_missing_values)
|
||||
|
||||
return doclist.as_dict()
|
||||
return doclist
|
@ -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
|
||||
|
@ -59,4 +59,4 @@ def make_maintenance_visit(source_name, target_doc=None):
|
||||
}
|
||||
}, target_doc)
|
||||
|
||||
return doclist.as_dict()
|
||||
return doclist
|
@ -295,4 +295,4 @@ def make_maintenance_visit(source_name, target_doc=None):
|
||||
}
|
||||
}, target_doc)
|
||||
|
||||
return doclist.as_dict()
|
||||
return doclist
|
Loading…
x
Reference in New Issue
Block a user