Merge branch 'develop'

This commit is contained in:
Anand Doshi 2015-08-26 16:55:08 +05:30
commit f326c63588
55 changed files with 2330 additions and 574 deletions

View File

@ -1,2 +1,2 @@
from __future__ import unicode_literals
__version__ = '5.7.7'
__version__ = '5.8.0'

View File

@ -32,7 +32,6 @@ class JournalEntry(AccountsController):
self.set_against_account()
self.create_remarks()
self.set_print_format_fields()
self.check_due_date()
self.validate_expense_claim()
self.validate_credit_debit_note()
self.validate_empty_accounts_table()
@ -83,20 +82,6 @@ class JournalEntry(AccountsController):
for customer in customers:
check_credit_limit(customer, self.company)
def check_due_date(self):
if self.cheque_date:
for d in self.get("accounts"):
if d.party_type and d.party and d.get("credit" if d.party_type=="Customer" else "debit") > 0:
due_date = None
if d.reference_type in ("Sales Invoice", "Purchase Invoice"):
due_date = frappe.db.get_value(d.reference_type, d.reference_name, "due_date")
if due_date and getdate(self.cheque_date) > getdate(due_date):
diff = date_diff(self.cheque_date, due_date)
if diff > 0:
msgprint(_("Note: Reference Date exceeds invoice due date by {0} days for {1} {2}")
.format(diff, d.party_type, d.party))
def validate_cheque_info(self):
if self.voucher_type in ['Bank Entry']:
if not self.cheque_no or not self.cheque_date:

View File

@ -127,7 +127,7 @@ class PurchaseInvoice(BuyingController):
}
})
if cint(frappe.db.get_single_value('Buying Settings', 'maintain_same_rate')):
if cint(frappe.db.get_single_value('Buying Settings', 'maintain_same_rate')) and not self.is_return:
self.validate_rate_with_reference_doc([
["Purchase Order", "purchase_order", "po_detail"],
["Purchase Receipt", "purchase_receipt", "pr_detail"]

View File

@ -7,7 +7,7 @@ frappe.listview_settings['Purchase Invoice'] = {
"currency", "is_return"],
get_indicator: function(doc) {
if(cint(doc.is_return)==1) {
return [__("Return"), "darkgrey", "is_return,=,1"];
return [__("Return"), "darkgrey", "is_return,=,Yes"];
} else if(flt(doc.outstanding_amount) > 0 && doc.docstatus==1) {
if(frappe.datetime.get_diff(doc.due_date) < 0) {
return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<,Today"];

View File

@ -64,6 +64,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
if(doc.outstanding_amount!=0 && !cint(doc.is_return)) {
cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_bank_entry).addClass("btn-primary");
}
}
// Show buttons only when pos view is active

File diff suppressed because it is too large Load Diff

View File

@ -81,9 +81,14 @@ class SalesInvoice(SellingController):
self.check_prev_docstatus()
if not self.is_return:
if self.is_return:
# NOTE status updating bypassed for is_return
self.status_updater = []
self.update_status_updater_args()
self.update_prevdoc_status()
if not self.is_return:
self.update_billing_status_for_zero_amount_refdoc("Sales Order")
self.check_credit_limit()
@ -107,9 +112,14 @@ class SalesInvoice(SellingController):
from erpnext.accounts.utils import remove_against_link_from_jv
remove_against_link_from_jv(self.doctype, self.name)
if not self.is_return:
if self.is_return:
# NOTE status updating bypassed for is_return
self.status_updater = []
self.update_status_updater_args()
self.update_prevdoc_status()
if not self.is_return:
self.update_billing_status_for_zero_amount_refdoc("Sales Order")
self.validate_c_form_on_cancel()
@ -118,7 +128,7 @@ class SalesInvoice(SellingController):
def update_status_updater_args(self):
if cint(self.update_stock):
self.status_updater.append({
self.status_updater.extend([{
'source_dt':'Sales Invoice Item',
'target_dt':'Sales Order Item',
'target_parent_dt':'Sales Order',
@ -136,7 +146,21 @@ class SalesInvoice(SellingController):
'overflow_type': 'delivery',
'extra_cond': """ and exists(select name from `tabSales Invoice`
where name=`tabSales Invoice Item`.parent and ifnull(update_stock, 0) = 1)"""
})
},
{
'source_dt': 'Sales Invoice Item',
'target_dt': 'Sales Order Item',
'join_field': 'so_detail',
'target_field': 'returned_qty',
'target_parent_dt': 'Sales Order',
# 'target_parent_field': 'per_delivered',
# 'target_ref_field': 'qty',
'source_field': '-1 * qty',
# 'percent_join_field': 'sales_order',
# 'overflow_type': 'delivery',
'extra_cond': """ and exists (select name from `tabSales Invoice` where name=`tabSales Invoice Item`.parent and update_stock=1 and is_return=1)"""
}
])
def set_missing_values(self, for_validate=False):
pos = self.set_pos_fields(for_validate)
@ -273,7 +297,7 @@ class SalesInvoice(SellingController):
},
})
if cint(frappe.db.get_single_value('Selling Settings', 'maintain_same_sales_rate')):
if cint(frappe.db.get_single_value('Selling Settings', 'maintain_same_sales_rate')) and not self.is_return:
self.validate_rate_with_reference_doc([
["Sales Order", "sales_order", "so_detail"],
["Delivery Note", "delivery_note", "dn_detail"]
@ -294,8 +318,6 @@ class SalesInvoice(SellingController):
def so_dn_required(self):
"""check in manage account if sales order / delivery note required or not."""
if self.is_return:
return
dic = {'Sales Order':'so_required','Delivery Note':'dn_required'}
for i in dic:
if frappe.db.get_value('Selling Settings', None, dic[i]) == 'Yes':
@ -408,36 +430,12 @@ class SalesInvoice(SellingController):
def check_prev_docstatus(self):
for d in self.get('items'):
if d.sales_order:
submitted = frappe.db.sql("""select name from `tabSales Order`
where docstatus = 1 and name = %s""", d.sales_order)
if not submitted:
if d.sales_order and frappe.db.get_value("Sales Order", d.sales_order, "docstatus") != 1:
frappe.throw(_("Sales Order {0} is not submitted").format(d.sales_order))
if d.delivery_note:
submitted = frappe.db.sql("""select name from `tabDelivery Note`
where docstatus = 1 and name = %s""", d.delivery_note)
if not submitted:
if d.delivery_note and frappe.db.get_value("Delivery Note", d.delivery_note, "docstatus") != 1:
throw(_("Delivery Note {0} is not submitted").format(d.delivery_note))
def update_stock_ledger(self):
sl_entries = []
for d in self.get_item_list():
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1 and d.warehouse and flt(d['qty']):
self.update_reserved_qty(d)
incoming_rate = 0
if cint(self.is_return) and self.return_against and self.docstatus==1:
incoming_rate = self.get_incoming_rate_for_sales_return(d.item_code,
self.return_against)
sl_entries.append(self.get_sl_entries(d, {
"actual_qty": -1*flt(d.qty),
"stock_uom": frappe.db.get_value("Item", d.item_code, "stock_uom"),
"incoming_rate": incoming_rate
}))
self.make_sl_entries(sl_entries)
def make_gl_entries(self, repost_future_gle=True):
gl_entries = self.get_gl_entries()

View File

@ -7,7 +7,7 @@ frappe.listview_settings['Sales Invoice'] = {
"currency", "is_return"],
get_indicator: function(doc) {
if(cint(doc.is_return)==1) {
return [__("Return"), "darkgrey", "is_return,=,1"];
return [__("Return"), "darkgrey", "is_return,=,Yes"];
} else if(flt(doc.outstanding_amount)==0) {
return [__("Paid"), "green", "outstanding_amount,=,0"]
} else if (flt(doc.outstanding_amount) > 0 && doc.due_date > frappe.datetime.get_today()) {

View File

@ -1,17 +0,0 @@
{
"creation": "2015-07-22 17:45:22.220567",
"custom_format": 1,
"disabled": 0,
"doc_type": "Sales Invoice",
"docstatus": 0,
"doctype": "Print Format",
"font": "Default",
"html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Monospace;\n\t\tline-height: 200%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 6in;\n\t\t}\n\t}\n</style>\n\n<p class=\"text-center\">\n\t{{ doc.company }}<br>\n\t{{ doc.select_print_heading or _(\"Credit Note\") }}<br>\n</p>\n\n<hr>\n\n{%- for label, value in (\n (_(\"Receipt No\"), doc.name),\n (_(\"Date\"), doc.get_formatted(\"posting_date\")),\n\t(_(\"Customer\"), doc.customer_name),\n (_(\"Amount\"), \"<strong>\" + doc.get_formatted(\"grand_total\", absolute_value=True) + \"</strong><br>\" + (doc.in_words or \"\")),\n\t(_(\"Against\"), doc.return_against),\n (_(\"Remarks\"), doc.remarks)\n) -%}\n\n\t\t<div class=\"row\">\n\t\t <div class=\"col-xs-4\"><label class=\"text-right\">{{ label }}</label></div>\n\t\t <div class=\"col-xs-8\">{{ value }}</div>\n\t\t</div>\n{%- endfor -%}\n\n<hr>\n<br>\n<p class=\"strong\">\n {{ _(\"For\") }} {{ doc.company }},<br>\n <br>\n <br>\n <br>\n {{ _(\"Authorized Signatory\") }}\n</p>",
"modified": "2015-07-22 17:45:22.220567",
"modified_by": "Administrator",
"name": "Credit Note - Negative Invoice",
"owner": "Administrator",
"print_format_builder": 0,
"print_format_type": "Server",
"standard": "Yes"
}

View File

@ -61,7 +61,7 @@ def get_result(filters, account_details):
return result
def get_gl_entries(filters):
group_by_condition = "group by voucher_type, voucher_no, account" \
group_by_condition = "group by voucher_type, voucher_no, account, cost_center" \
if filters.get("group_by_voucher") else "group by name"
gl_entries = frappe.db.sql("""select posting_date, account, party_type, party,

View File

@ -9,6 +9,7 @@ from frappe import msgprint, _, throw
from frappe.model.mapper import get_mapped_doc
from erpnext.controllers.buying_controller import BuyingController
from erpnext.stock.doctype.item.item import get_last_purchase_details
from erpnext.stock.stock_balance import update_bin_qty, get_ordered_qty
form_grid_templates = {
@ -136,20 +137,6 @@ class PurchaseOrder(BuyingController):
def update_ordered_qty(self, po_item_rows=None):
"""update requested qty (before ordered_qty is updated)"""
from erpnext.stock.utils import get_bin
def _update_ordered_qty(item_code, warehouse):
ordered_qty = frappe.db.sql("""
select sum((po_item.qty - ifnull(po_item.received_qty, 0))*po_item.conversion_factor)
from `tabPurchase Order Item` po_item, `tabPurchase Order` po
where po_item.item_code=%s and po_item.warehouse=%s
and po_item.qty > ifnull(po_item.received_qty, 0) and po_item.parent=po.name
and po.status!='Stopped' and po.docstatus=1""", (item_code, warehouse))
bin_doc = get_bin(item_code, warehouse)
bin_doc.ordered_qty = flt(ordered_qty[0][0]) if ordered_qty else 0
bin_doc.save()
item_wh_list = []
for d in self.get("items"):
if (not po_item_rows or d.name in po_item_rows) and [d.item_code, d.warehouse] not in item_wh_list \
@ -157,7 +144,9 @@ class PurchaseOrder(BuyingController):
item_wh_list.append([d.item_code, d.warehouse])
for item_code, warehouse in item_wh_list:
_update_ordered_qty(item_code, warehouse)
update_bin_qty(item_code, warehouse, {
"ordered_qty": get_ordered_qty(item_code, warehouse)
})
def check_modified_date(self):
mod_db = frappe.db.sql("select modified from `tabPurchase Order` where name = %s",

View File

@ -1002,6 +1002,27 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"depends_on": "returned_qty",
"fieldname": "returned_qty",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Returned Qty",
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"fieldname": "billed_amt",
@ -1074,7 +1095,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"modified": "2015-08-19 12:46:32.384796",
"modified": "2015-08-25 06:42:00.898647",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",

View File

@ -0,0 +1,5 @@
- Fixes in Sales and Purchase Return
- Update Delivered, Ordered and Returned Quantity based on Return transaction
- Allow different Rate in Return transaction
- Set default Finished Goods and Work-in-Progress Warehouse in Manufacturing Settings
- Fixed range validation for numeric attributes in Item Variants

View File

@ -205,10 +205,10 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""select `tabProject`.name from `tabProject`
where `tabProject`.status not in ("Completed", "Cancelled")
and %(cond)s `tabProject`.name like "%(txt)s" %(mcond)s
and {cond} `tabProject`.name like %s {match_cond}
order by `tabProject`.name asc
limit %(start)s, %(page_len)s """ % {'cond': cond,'txt': "%%%s%%" % frappe.db.escape(txt),
'mcond':get_match_cond(doctype),'start': start, 'page_len': page_len})
limit {start}, {page_len}""".format(cond=cond, match_cond=get_match_cond(doctype),
start=start, page_len=page_len), "%{0}%".format(txt))
def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""select `tabDelivery Note`.name, `tabDelivery Note`.customer_name

View File

@ -83,9 +83,6 @@ def validate_returned_items(doc):
elif abs(d.qty) > max_return_qty:
frappe.throw(_("Row # {0}: Cannot return more than {1} for Item {2}")
.format(d.idx, ref.qty, d.item_code), StockOverReturnError)
elif ref.rate and flt(d.rate) != ref.rate:
frappe.throw(_("Row # {0}: Rate must be same as {1} {2}")
.format(d.idx, doc.doctype, doc.return_against))
elif ref.batch_no and d.batch_no != ref.batch_no:
frappe.throw(_("Row # {0}: Batch No must be same as {1} {2}")
.format(d.idx, doc.doctype, doc.return_against))
@ -127,6 +124,14 @@ def make_return_doc(doctype, source_name, target_doc=None):
if doctype == "Sales Invoice":
doc.is_pos = 0
# look for Print Heading "Credit Note"
if not doc.select_print_heading:
doc.select_print_heading = frappe.db.get_value("Print Heading", _("Credit Note"))
elif doctype == "Purchase Invoice":
# look for Print Heading "Debit Note"
doc.select_print_heading = frappe.db.get_value("Print Heading", _("Debit Note"))
for tax in doc.get("taxes"):
if tax.charge_type == "Actual":
tax.tax_amount = -1 * tax.tax_amount
@ -137,9 +142,26 @@ def make_return_doc(doctype, source_name, target_doc=None):
target_doc.qty = -1* source_doc.qty
if doctype == "Purchase Receipt":
target_doc.received_qty = -1* source_doc.qty
target_doc.prevdoc_doctype = source_doc.prevdoc_doctype
target_doc.prevdoc_docname = source_doc.prevdoc_docname
target_doc.prevdoc_detail_docname = source_doc.prevdoc_detail_docname
elif doctype == "Purchase Invoice":
target_doc.purchase_order = source_doc.purchase_order
target_doc.purchase_receipt = source_doc.purchase_receipt
target_doc.po_detail = source_doc.po_detail
target_doc.pr_detail = source_doc.pr_detail
elif doctype == "Delivery Note":
target_doc.against_sales_order = source_doc.against_sales_order
target_doc.against_sales_invoice = source_doc.against_sales_invoice
target_doc.so_detail = source_doc.so_detail
target_doc.si_detail = source_doc.si_detail
target_doc.expense_account = source_doc.expense_account
elif doctype == "Sales Invoice":
target_doc.sales_order = source_doc.sales_order
target_doc.delivery_note = source_doc.delivery_note
target_doc.so_detail = source_doc.so_detail
target_doc.dn_detail = source_doc.dn_detail
target_doc.expense_account = source_doc.expense_account
doclist = get_mapped_doc(doctype, source_name, {
doctype: {
@ -152,8 +174,6 @@ def make_return_doc(doctype, source_name, target_doc=None):
doctype +" Item": {
"doctype": doctype + " Item",
"field_map": {
"purchase_order": "purchase_order",
"purchase_receipt": "purchase_receipt",
"serial_no": "serial_no",
"batch_no": "batch_no"
},

View File

@ -163,42 +163,17 @@ class SellingController(StockController):
def get_item_list(self):
il = []
for d in self.get("items"):
reserved_warehouse = ""
reserved_qty_for_main_item = 0
if d.qty is None:
frappe.throw(_("Row {0}: Qty is mandatory").format(d.idx))
if self.doctype == "Sales Order":
reserved_warehouse = d.warehouse
if flt(d.qty) > flt(d.delivered_qty):
reserved_qty_for_main_item = flt(d.qty) - flt(d.delivered_qty)
elif (((self.doctype == "Delivery Note" and d.against_sales_order)
or (self.doctype == "Sales Invoice" and d.sales_order and self.update_stock))
and not self.is_return):
# 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,
d.against_sales_order if self.doctype=="Delivery Note" else d.sales_order, d.so_detail)
so_qty, reserved_warehouse = self.get_so_qty_and_warehouse(d.so_detail)
if already_delivered_qty + d.qty > so_qty:
reserved_qty_for_main_item = -(so_qty - already_delivered_qty)
else:
reserved_qty_for_main_item = -flt(d.qty)
if self.has_product_bundle(d.item_code):
for p in self.get("packed_items"):
if p.parent_detail_docname == d.name and p.parent_item == d.item_code:
# the packing details table's qty is already multiplied with parent's qty
il.append(frappe._dict({
'warehouse': p.warehouse,
'reserved_warehouse': reserved_warehouse,
'item_code': p.item_code,
'qty': flt(p.qty),
'reserved_qty': (flt(p.qty)/flt(d.qty)) * reserved_qty_for_main_item,
'uom': p.uom,
'batch_no': cstr(p.batch_no).strip(),
'serial_no': cstr(p.serial_no).strip(),
@ -207,10 +182,8 @@ class SellingController(StockController):
else:
il.append(frappe._dict({
'warehouse': d.warehouse,
'reserved_warehouse': reserved_warehouse,
'item_code': d.item_code,
'qty': d.qty,
'reserved_qty': reserved_qty_for_main_item,
'uom': d.stock_uom,
'stock_uom': d.stock_uom,
'batch_no': cstr(d.get("batch_no")).strip(),

View File

@ -90,6 +90,10 @@ class StatusUpdater(Document):
self.global_tolerance = None
for args in self.status_updater:
if "target_ref_field" not in args:
# if target_ref_field is not specified, the programmer does not want to validate qty / amount
continue
# get unique transactions to update
for d in self.get_all_children():
if d.doctype == args['source_dt'] and d.get(args["join_field"]):
@ -140,8 +144,9 @@ class StatusUpdater(Document):
.format(_(item["target_ref_field"].title()), item["reduce_by"]))
def update_qty(self, change_modified=True):
"""
Updates qty at row level
"""Updates qty or amount at row level
:param change_modified: If true, updates `modified` and `modified_by` for target parent doc
"""
for args in self.status_updater:
# condition to include current record (if submit or no if cancel)
@ -150,13 +155,22 @@ class StatusUpdater(Document):
else:
args['cond'] = ' and parent!="%s"' % self.name.replace('"', '\"')
args['modified_cond'] = ''
args['set_modified'] = ''
if change_modified:
args['modified_cond'] = ', modified = now()'
args['set_modified'] = ', modified = now(), modified_by = "{0}"'\
.format(frappe.db.escape(frappe.session.user))
# update quantities in child table
self._update_children(args)
if "percent_join_field" in args:
self._update_percent_field(args)
def _update_children(self, args):
"""Update quantities or amount in child table"""
for d in self.get_all_children():
if d.doctype == args['source_dt']:
if d.doctype != args['source_dt']:
continue
# updates qty in the child table
args['detail_id'] = d.get(args['join_field'])
@ -180,9 +194,14 @@ class StatusUpdater(Document):
and (docstatus=1 %(cond)s) %(extra_cond)s) %(second_source_condition)s
where name='%(detail_id)s'""" % args)
# get unique transactions to update
for name in set([d.get(args['percent_join_field']) for d in self.get_all_children(args['source_dt'])]):
if name:
def _update_percent_field(self, args):
"""Update percent field in parent transaction"""
unique_transactions = set([d.get(args['percent_join_field']) for d in self.get_all_children(args['source_dt'])])
for name in unique_transactions:
if not name:
continue
args['name'] = name
# update percent complete in the parent table
@ -191,7 +210,7 @@ class StatusUpdater(Document):
set %(target_parent_field)s = (select sum(if(%(target_ref_field)s >
ifnull(%(target_field)s, 0), %(target_field)s,
%(target_ref_field)s))/sum(%(target_ref_field)s)*100
from `tab%(target_dt)s` where parent="%(name)s") %(modified_cond)s
from `tab%(target_dt)s` where parent="%(name)s") %(set_modified)s
where name='%(name)s'""" % args)
# update field
@ -202,6 +221,8 @@ class StatusUpdater(Document):
'Fully %(keyword)s', 'Partly %(keyword)s'))
where name='%(name)s'""" % args)
if args.get("set_modified"):
frappe.get_doc(args["target_parent_dt"], name).notify_modified()
def update_billing_status_for_zero_amount_refdoc(self, ref_dt):
ref_fieldname = ref_dt.lower().replace(" ", "_")

View File

@ -6,11 +6,9 @@ import frappe
from frappe.utils import cint, flt, cstr
from frappe import msgprint, _
import frappe.defaults
from erpnext.accounts.general_ledger import make_gl_entries, delete_gl_entries, process_gl_map
from erpnext.controllers.accounts_controller import AccountsController
from erpnext.accounts.general_ledger import make_gl_entries, delete_gl_entries, process_gl_map
from erpnext.stock.utils import update_bin
class StockController(AccountsController):
def make_gl_entries(self, repost_future_gle=True):
@ -230,22 +228,43 @@ class StockController(AccountsController):
return incoming_rate
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(_("Delivery Warehouse is missing in Sales Order"))
def update_reserved_qty(self):
so_map = {}
for d in self.get("items"):
if d.so_detail:
if self.doctype == "Delivery Note" and d.against_sales_order:
so_map.setdefault(d.against_sales_order, []).append(d.so_detail)
elif self.doctype == "Sales Invoice" and d.sales_order and self.update_stock:
so_map.setdefault(d.sales_order, []).append(d.so_detail)
args = {
"item_code": d['item_code'],
"warehouse": d["reserved_warehouse"],
"voucher_type": self.doctype,
"voucher_no": self.name,
"reserved_qty": (self.docstatus==1 and 1 or -1)*flt(d['reserved_qty']),
"posting_date": self.posting_date,
"is_amended": self.amended_from and 'Yes' or 'No'
}
update_bin(args)
for so, so_item_rows in so_map.items():
if so and so_item_rows:
sales_order = frappe.get_doc("Sales Order", so)
if sales_order.status in ["Stopped", "Cancelled"]:
frappe.throw(_("Sales Order {0} is cancelled or stopped").format(so), frappe.InvalidStatusError)
sales_order.update_reserved_qty(so_item_rows)
def update_stock_ledger(self):
self.update_reserved_qty()
sl_entries = []
for d in self.get_item_list():
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1 \
and d.warehouse and flt(d['qty']):
incoming_rate = 0
if cint(self.is_return) and self.return_against and self.docstatus==1:
incoming_rate = self.get_incoming_rate_for_sales_return(d.item_code, self.return_against)
sl_entries.append(self.get_sl_entries(d, {
"actual_qty": -1*flt(d['qty']),
"stock_uom": frappe.db.get_value("Item", d.item_code, "stock_uom"),
"incoming_rate": incoming_rate
}))
self.make_sl_entries(sl_entries)
def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
warehouse_account=None):

View File

@ -27,7 +27,7 @@ blogs.
"""
app_icon = "icon-th"
app_color = "#e74c3c"
app_version = "5.7.7"
app_version = "5.8.0"
github_link = "https://github.com/frappe/erpnext"
error_report_email = "support@erpnext.com"

View File

@ -6,79 +6,275 @@
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Master",
"document_type": "Document",
"fields": [
{
"allow_on_submit": 0,
"fieldname": "capacity_planning",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Capacity Planning",
"no_copy": 0,
"permlevel": 0,
"precision": ""
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"description": "Disables creation of time logs against Production Orders.\nOperations shall not be tracked against Production Order",
"fieldname": "disable_capacity_planning",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Disable Capacity Planning and Time Tracking",
"no_copy": 0,
"permlevel": 0,
"precision": ""
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"description": "Plan time logs outside Workstation Working Hours.",
"fieldname": "allow_overtime",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Allow Overtime",
"no_copy": 0,
"permlevel": 0,
"precision": ""
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"default": "",
"fieldname": "allow_production_on_holidays",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Allow Production on Holidays",
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": ""
},
{
"default": "BOM",
"fieldname": "backflush_raw_materials_based_on",
"fieldtype": "Select",
"label": "Backflush Raw Materials Based On",
"options": "BOM\nMaterial Transferred for Manufacture",
"permlevel": 0,
"precision": ""
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0,
"precision": ""
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"default": "30",
"description": "Try planning operations for X days in advance.",
"fieldname": "capacity_planning_for_days",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Capacity Planning For (Days)",
"no_copy": 0,
"permlevel": 0,
"precision": ""
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"description": "Default 10 mins",
"fieldname": "mins_between_operations",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Time Between Operations (in mins)",
"no_copy": 0,
"permlevel": 0,
"precision": ""
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"fieldname": "section_break_6",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"fieldname": "over_production_allowance_percentage",
"fieldtype": "Percent",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Over Production Allowance Percentage",
"no_copy": 0,
"permlevel": 0,
"precision": ""
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"default": "BOM",
"fieldname": "backflush_raw_materials_based_on",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Backflush Raw Materials Based On",
"no_copy": 0,
"options": "BOM\nMaterial Transferred for Manufacture",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"fieldname": "column_break_11",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"fieldname": "default_wip_warehouse",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Default Work In Progress Warehouse",
"no_copy": 0,
"options": "Warehouse",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"fieldname": "default_fg_warehouse",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Default Finished Goods Warehouse",
"no_copy": 0,
"options": "Warehouse",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
@ -89,7 +285,7 @@
"is_submittable": 0,
"issingle": 1,
"istable": 0,
"modified": "2015-08-12 08:12:33.889753",
"modified": "2015-08-21 08:27:03.794133",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Manufacturing Settings",
@ -104,6 +300,7 @@
"delete": 0,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 0,

View File

@ -12,6 +12,7 @@ frappe.ui.form.on("Production Order", "onload", function(frm) {
"actual_start_date": "",
"actual_end_date": ""
});
erpnext.production_order.set_default_warehouse(frm);
}
erpnext.production_order.set_custom_buttons(frm);
@ -138,10 +139,22 @@ erpnext.production_order = {
}
} else msgprint(__("Please enter Production Item first"));
});
},
set_default_warehouse: function(frm) {
frappe.call({
method: "erpnext.manufacturing.doctype.production_order.production_order.get_default_warehouse",
callback: function(r) {
if(!r.exe) {
frm.set_value("wip_warehouse", r.message.wip_warehouse);
frm.set_value("fg_warehouse", r.message.fg_warehouse)
}
}
});
}
}
$.extend(cur_frm.cscript, {
before_submit: function() {
cur_frm.toggle_reqd(["fg_warehouse", "wip_warehouse"], true);

View File

@ -422,3 +422,9 @@ def make_time_log(name, operation, from_time=None, to_time=None, qty=None, proj
if from_time and to_time :
time_log.calculate_total_hours()
return time_log
@frappe.whitelist()
def get_default_warehouse():
wip_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_wip_warehouse")
fg_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_fg_warehouse")
return {"wip_warehouse": wip_warehouse, "fg_warehouse": fg_warehouse}

View File

@ -200,15 +200,19 @@ class ProductionPlanningTool(Document):
def create_production_order(self, items):
"""Create production order. Called from Production Planning Tool"""
from erpnext.manufacturing.doctype.production_order.production_order import OverProductionError
from erpnext.manufacturing.doctype.production_order.production_order import OverProductionError, get_default_warehouse
warehouse = get_default_warehouse()
pro_list = []
for key in items:
pro = frappe.new_doc("Production Order")
pro.update(items[key])
pro.set_production_order_operations()
if warehouse:
pro.wip_warehouse = warehouse.get('wip_warehouse')
if not pro.fg_warehouse:
pro.fg_warehouse = warehouse.get('fg_warehouse')
frappe.flags.mute_messages = True
try:
pro.insert()
pro_list.append(pro.name)
@ -216,7 +220,6 @@ class ProductionPlanningTool(Document):
pass
frappe.flags.mute_messages = False
return pro_list
def download_raw_materials(self):

View File

@ -191,5 +191,10 @@ erpnext.patches.v5_4.stock_entry_additional_costs
erpnext.patches.v5_4.cleanup_journal_entry #2015-08-14
execute:frappe.db.sql("update `tabProduction Order` pro set description = (select description from tabItem where name=pro.production_item) where ifnull(description, '') = ''")
erpnext.patches.v5_7.item_template_attributes
execute:frappe.delete_doc_if_exists("DocType", "Manage Variants")
execute:frappe.delete_doc_if_exists("DocType", "Manage Variants Item")
erpnext.patches.v4_2.repost_reserved_qty #2015-08-20
erpnext.patches.v5_4.update_purchase_cost_against_project
erpnext.patches.v5_8.update_order_reference_in_return_entries
erpnext.patches.v5_8.add_credit_note_print_heading
execute:frappe.delete_doc_if_exists("Print Format", "Credit Note - Negative Invoice")

View File

@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe
def execute():
from erpnext.utilities.repost_stock import set_stock_balance_as_per_serial_no
from erpnext.stock.stock_balance import set_stock_balance_as_per_serial_no
frappe.db.auto_commit_on_many_writes = 1
set_stock_balance_as_per_serial_no()

View File

@ -6,7 +6,7 @@ import frappe
from frappe.utils import flt
def execute():
from erpnext.utilities.repost_stock import repost
from erpnext.stock.stock_balance import repost
repost(allow_zero_rate=True, only_actual=True)
frappe.reload_doctype("Account")

View File

@ -3,7 +3,7 @@
from __future__ import unicode_literals
import frappe
from erpnext.utilities.repost_stock import update_bin_qty, get_reserved_qty
from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty
def execute():
repost_for = frappe.db.sql("""

View File

@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe
def execute():
from erpnext.utilities.repost_stock import update_bin_qty, get_indented_qty, get_ordered_qty
from erpnext.stock.stock_balance import update_bin_qty, get_indented_qty, get_ordered_qty
count=0
for item_code, warehouse in frappe.db.sql("""select distinct item_code, warehouse from

View File

@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe
def execute():
from erpnext.utilities.repost_stock import update_bin_qty, get_indented_qty
from erpnext.stock.stock_balance import update_bin_qty, get_indented_qty
count=0
for item_code, warehouse in frappe.db.sql("""select distinct item_code, warehouse

View File

@ -3,7 +3,7 @@
from __future__ import unicode_literals
import frappe
from erpnext.utilities.repost_stock import repost_actual_qty
from erpnext.stock.stock_balance import repost_actual_qty
def execute():
cancelled_invoices = frappe.db.sql_list("""select name from `tabSales Invoice`

View File

@ -10,12 +10,6 @@ def execute():
frappe.reload_doctype("Stock Entry Detail")
frappe.reload_doctype("Landed Cost Taxes and Charges")
frappe.db.sql("""update `tabStock Entry Detail` sed, `tabStock Entry` se
set sed.valuation_rate=sed.incoming_rate, sed.basic_rate=sed.incoming_rate, sed.basic_amount=sed.amount
where sed.parent = se.name
and (se.purpose not in ('Manufacture', 'Repack') or ifnull(additional_operating_cost, 0)=0)
""")
stock_entry_db_columns = frappe.db.get_table_columns("Stock Entry")
if "additional_operating_cost" in stock_entry_db_columns:
operating_cost_fieldname = "additional_operating_cost"
@ -25,6 +19,13 @@ def execute():
return
frappe.db.sql("""update `tabStock Entry Detail` sed, `tabStock Entry` se
set sed.valuation_rate=sed.incoming_rate, sed.basic_rate=sed.incoming_rate, sed.basic_amount=sed.amount
where sed.parent = se.name
and (se.purpose not in ('Manufacture', 'Repack') or ifnull({0}, 0)=0)
""".format(operating_cost_fieldname))
stock_entries = frappe.db.sql_list("""select name from `tabStock Entry`
where purpose in ('Manufacture', 'Repack') and ifnull({0}, 0)!=0
and docstatus < 2""".format(operating_cost_fieldname))

View File

@ -55,9 +55,6 @@ def migrate_manage_variants():
template.set('attributes', attributes)
template.save()
frappe.delete_doc("DocType", "Manage Variants")
frappe.delete_doc("DocType", "Manage Variants Item")
# patch old style
def migrate_item_variants():
for item in frappe.get_all("Item", filters={"has_variants": 1}):

View File

@ -0,0 +1 @@
from __future__ import unicode_literals

View File

@ -0,0 +1,14 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
def execute():
for print_heading in (_("Credit Note"), _("Debit Note")):
if not frappe.db.exists("Print Heading", print_heading):
frappe.get_doc({
"doctype": "Print Heading",
"print_heading": print_heading
}).insert()

View File

@ -0,0 +1,85 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
# sales return
return_entries = list(frappe.db.sql("""
select dn.name as name, dn_item.name as row_id, dn.return_against,
dn_item.item_code, "Delivery Note" as doctype
from `tabDelivery Note Item` dn_item, `tabDelivery Note` dn
where dn_item.parent=dn.name and dn.is_return=1 and dn.docstatus < 2
""", as_dict=1))
return_entries += list(frappe.db.sql("""
select si.name as name, si_item.name as row_id, si.return_against,
si_item.item_code, "Sales Invoice" as doctype, update_stock
from `tabSales Invoice Item` si_item, `tabSales Invoice` si
where si_item.parent=si.name and si.is_return=1 and si.docstatus < 2
""", as_dict=1))
for d in return_entries:
ref_field = "against_sales_order" if d.doctype == "Delivery Note" else "sales_order"
order_details = frappe.db.sql("""
select {ref_field} as sales_order, so_detail,
(select transaction_date from `tabSales Order` where name=item.{ref_field}) as sales_order_date
from `tab{doctype} Item` item
where
parent=%s
and item_code=%s
and ifnull(so_detail, '') !=''
order by sales_order_date DESC limit 1
""".format(ref_field=ref_field, doctype=d.doctype), (d.return_against, d.item_code), as_dict=1)
if order_details:
frappe.db.sql("""
update `tab{doctype} Item`
set {ref_field}=%s, so_detail=%s
where name=%s
""".format(doctype=d.doctype, ref_field=ref_field),
(order_details[0].sales_order, order_details[0].so_detail, d.row_id))
if (d.doctype=="Sales Invoice" and d.update_stock) or d.doctype=="Delivery Note":
doc = frappe.get_doc(d.doctype, d.name)
doc.update_reserved_qty()
if d.doctype=="Sales Invoice":
doc.status_updater = []
doc.update_status_updater_args()
doc.update_prevdoc_status()
#--------------------------
# purchase return
return_entries = frappe.db.sql("""
select pr.name as name, pr_item.name as row_id, pr.return_against, pr_item.item_code
from `tabPurchase Receipt Item` pr_item, `tabPurchase Receipt` pr
where pr_item.parent=pr.name and pr.is_return=1 and pr.docstatus < 2
""", as_dict=1)
for d in return_entries:
order_details = frappe.db.sql("""
select prevdoc_docname as purchase_order, prevdoc_detail_docname as po_detail,
(select transaction_date from `tabPurchase Order` where name=item.prevdoc_detail_docname) as purchase_order_date
from `tabPurchase Receipt Item` item
where
parent=%s
and item_code=%s
and ifnull(prevdoc_detail_docname, '') !=''
and ifnull(prevdoc_doctype, '') = 'Purchase Order' and ifnull(prevdoc_detail_docname, '') != ''
order by purchase_order_date DESC limit 1
""", (d.return_against, d.item_code), as_dict=1)
if order_details:
frappe.db.sql("""
update `tabPurchase Receipt Item`
set prevdoc_doctype='Purchase Order', prevdoc_docname=%s, prevdoc_detail_docname=%s
where name=%s
""", (order_details[0].purchase_order, order_details[0].po_detail, d.row_id))
pr = frappe.get_doc("Purchase Receipt", d.name)
pr.update_ordered_qty()
pr.update_prevdoc_status()

View File

@ -88,6 +88,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
this.set_dynamic_labels();
erpnext.pos.make_pos_btn(this.frm);
this.setup_sms();
this.make_show_payments_btn();
},
apply_default_taxes: function() {
@ -123,6 +124,22 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
var sms_man = new SMSManager(this.frm.doc);
},
make_show_payments_btn: function() {
var me = this;
if (in_list(["Purchase Invoice", "Sales Invoice"], this.frm.doctype)) {
if(this.frm.doc.outstanding_amount !== this.frm.doc.base_grand_total) {
this.frm.add_custom_button(__("Show Payments"), function() {
frappe.route_options = {
"Journal Entry Account.reference_type": me.frm.doc.doctype,
"Journal Entry Account.reference_name": me.frm.doc.name
};
frappe.set_route("List", "Journal Entry");
});
}
}
},
hide_currency_and_price_list: function() {
if(this.frm.doc.conversion_rate == 1 && this.frm.doc.docstatus > 0) {
hide_field("currency_and_price_list");

View File

@ -8,6 +8,7 @@ import frappe.utils
from frappe.utils import cstr, flt, getdate, comma_and
from frappe import _
from frappe.model.mapper import get_mapped_doc
from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty
from erpnext.controllers.selling_controller import SellingController
@ -151,7 +152,7 @@ class SalesOrder(SellingController):
super(SalesOrder, self).on_submit()
self.check_credit_limit()
self.update_stock_ledger(update_stock = 1)
self.update_reserved_qty()
frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.base_grand_total, self)
@ -164,7 +165,7 @@ class SalesOrder(SellingController):
frappe.throw(_("Stopped order cannot be cancelled. Unstop to cancel."))
self.check_nextdoc_docstatus()
self.update_stock_ledger(update_stock = -1)
self.update_reserved_qty()
self.update_prevdoc_status('cancel')
@ -213,32 +214,38 @@ class SalesOrder(SellingController):
def stop_sales_order(self):
self.check_modified_date()
self.update_stock_ledger(-1)
frappe.db.set(self, 'status', 'Stopped')
self.update_reserved_qty()
frappe.msgprint(_("{0} {1} status is Stopped").format(self.doctype, self.name))
self.notify_modified()
def unstop_sales_order(self):
self.check_modified_date()
self.update_stock_ledger(1)
frappe.db.set(self, 'status', 'Submitted')
self.update_reserved_qty()
frappe.msgprint(_("{0} {1} status is Unstopped").format(self.doctype, self.name))
def update_reserved_qty(self, so_item_rows=None):
"""update requested qty (before ordered_qty is updated)"""
item_wh_list = []
def _valid_for_reserve(item_code, warehouse):
if item_code and warehouse and [item_code, warehouse] not in item_wh_list \
and frappe.db.get_value("Item", item_code, "is_stock_item"):
item_wh_list.append([item_code, warehouse])
def update_stock_ledger(self, update_stock):
from erpnext.stock.utils import update_bin
for d in self.get_item_list():
if frappe.db.get_value("Item", d['item_code'], "is_stock_item")==1:
args = {
"item_code": d['item_code'],
"warehouse": d['reserved_warehouse'],
"reserved_qty": flt(update_stock) * flt(d['reserved_qty']),
"posting_date": self.transaction_date,
"voucher_type": self.doctype,
"voucher_no": self.name,
"is_amended": self.amended_from and 'Yes' or 'No'
}
update_bin(args)
for d in self.get("items"):
if (not so_item_rows or d.name in so_item_rows):
_valid_for_reserve(d.item_code, d.warehouse)
if self.has_product_bundle(d.item_code):
for p in self.get("packed_items"):
if p.parent_detail_docname == d.name and p.parent_item == d.item_code:
_valid_for_reserve(p.item_code, p.warehouse)
for item_code, warehouse in item_wh_list:
update_bin_qty(item_code, warehouse, {
"reserved_qty": get_reserved_qty(item_code, warehouse)
})
def on_update(self):
pass

View File

@ -840,6 +840,27 @@
"unique": 0,
"width": "100px"
},
{
"allow_on_submit": 0,
"depends_on": "returned_qty",
"fieldname": "returned_qty",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Returned Qty",
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"fieldname": "billed_amt",
@ -960,7 +981,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"modified": "2015-08-19 12:46:32.930498",
"modified": "2015-08-25 06:42:11.062909",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order Item",

View File

@ -173,6 +173,8 @@ def install(country=None):
{"doctype": "Offer Term", "offer_term": _("Notice Period")},
{"doctype": "Offer Term", "offer_term": _("Incentives")},
{'doctype': "Print Heading", 'print_heading': _("Credit Note")},
{'doctype': "Print Heading", 'print_heading': _("Debit Note")}
]
from erpnext.setup.page.setup_wizard.fixtures import industry_type

View File

@ -7,7 +7,7 @@
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Transaction",
"document_type": "Document",
"fields": [
{
"allow_on_submit": 0,
@ -1080,7 +1080,7 @@
"options": "\nGrand Total\nNet Total",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide": 1,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
@ -2179,7 +2179,7 @@
"is_submittable": 1,
"issingle": 0,
"istable": 0,
"modified": "2015-08-04 11:56:59.782517",
"modified": "2015-08-24 09:09:59.298127",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",

View File

@ -47,6 +47,19 @@ class DeliveryNote(SellingController):
'source_field': 'qty',
'percent_join_field': 'against_sales_invoice',
'overflow_type': 'delivery'
},
{
'source_dt': 'Delivery Note Item',
'target_dt': 'Sales Order Item',
'join_field': 'so_detail',
'target_field': 'returned_qty',
'target_parent_dt': 'Sales Order',
# 'target_parent_field': 'per_delivered',
# 'target_ref_field': 'qty',
'source_field': '-1 * qty',
# 'percent_join_field': 'against_sales_order',
# 'overflow_type': 'delivery',
'extra_cond': """ and exists (select name from `tabDelivery Note` where name=`tabDelivery Note Item`.parent and is_return=1)"""
}]
def onload(self):
@ -66,7 +79,7 @@ class DeliveryNote(SellingController):
item_meta = frappe.get_meta("Delivery Note Item")
print_hide_fields = {
"parent": ["grand_total", "rounded_total", "in_words", "currency", "net_total"],
"parent": ["grand_total", "rounded_total", "in_words", "currency", "total", "taxes"],
"items": ["rate", "amount", "price_list_rate", "discount_percentage"]
}
@ -83,7 +96,7 @@ class DeliveryNote(SellingController):
def so_required(self):
"""check in manage account if sales order required or not"""
if not self.is_return and frappe.db.get_value("Selling Settings", None, 'so_required') == 'Yes':
if frappe.db.get_value("Selling Settings", None, 'so_required') == 'Yes':
for d in self.get('items'):
if not d.against_sales_order:
frappe.throw(_("Sales Order required for Item {0}").format(d.item_code))
@ -118,7 +131,7 @@ class DeliveryNote(SellingController):
},
})
if cint(frappe.db.get_single_value('Selling Settings', 'maintain_same_sales_rate')):
if cint(frappe.db.get_single_value('Selling Settings', 'maintain_same_sales_rate')) and not self.is_return:
self.validate_rate_with_reference_doc([["Sales Order", "sales_order", "so_detail"],
["Sales Invoice", "sales_invoice", "si_detail"]])
@ -174,10 +187,10 @@ class DeliveryNote(SellingController):
# Check for Approving Authority
frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.company, self.base_grand_total, self)
if not self.is_return:
# update delivered qty in sales order
self.update_prevdoc_status()
if not self.is_return:
self.check_credit_limit()
self.update_stock_ledger()
@ -190,7 +203,6 @@ class DeliveryNote(SellingController):
self.check_stop_sales_order("against_sales_order")
self.check_next_docstatus()
if not self.is_return:
self.update_prevdoc_status()
self.update_stock_ledger()
@ -242,25 +254,6 @@ class DeliveryNote(SellingController):
ps.cancel()
frappe.msgprint(_("Packing Slip(s) cancelled"))
def update_stock_ledger(self):
sl_entries = []
for d in self.get_item_list():
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1 \
and d.warehouse and flt(d['qty']):
self.update_reserved_qty(d)
incoming_rate = 0
if cint(self.is_return) and self.return_against and self.docstatus==1:
incoming_rate = self.get_incoming_rate_for_sales_return(d.item_code, self.return_against)
sl_entries.append(self.get_sl_entries(d, {
"actual_qty": -1*flt(d['qty']),
"incoming_rate": incoming_rate
}))
self.make_sl_entries(sl_entries)
def get_list_context(context=None):
from erpnext.controllers.website_list_for_contact import get_list_context
list_context = get_list_context(context)

View File

@ -3,7 +3,7 @@ frappe.listview_settings['Delivery Note'] = {
"transporter_name", "grand_total", "is_return"],
get_indicator: function(doc) {
if(cint(doc.is_return)==1) {
return [__("Return"), "darkgrey", "is_return,=,1"];
return [__("Return"), "darkgrey", "is_return,=,Yes"];
}
}
};

View File

@ -912,7 +912,7 @@
"in_filter": 0,
"in_list_view": 1,
"label": "Against Sales Order",
"no_copy": 0,
"no_copy": 1,
"options": "Sales Order",
"permlevel": 0,
"print_hide": 1,
@ -932,7 +932,7 @@
"in_filter": 0,
"in_list_view": 0,
"label": "Against Sales Invoice",
"no_copy": 0,
"no_copy": 1,
"options": "Sales Invoice",
"permlevel": 0,
"print_hide": 1,
@ -1039,7 +1039,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"modified": "2015-08-19 12:46:31.447022",
"modified": "2015-08-25 07:15:19.811365",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note Item",

View File

@ -269,7 +269,7 @@ class Item(WebsiteGenerator):
frappe.db.set_value("Item", newdn, "last_purchase_rate", last_purchase_rate)
def recalculate_bin_qty(self, newdn):
from erpnext.utilities.repost_stock import repost_stock
from erpnext.stock.stock_balance import repost_stock
frappe.db.auto_commit_on_many_writes = 1
existing_allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock")
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
@ -513,11 +513,9 @@ def validate_item_variant_attributes(item, args):
filters={"parent": ["in", args.keys()]}):
(attribute_values.setdefault(t.parent, [])).append(t.attribute_value)
numeric_attributes = frappe._dict((t.name, t) for t in frappe.get_list("Item Attribute", filters={"numeric_values":1,
"name": ["in", args.keys()]}, fields=["name", "from_range", "to_range", "increment"]))
template_item = frappe.get_doc("Item", item)
template_item_attributes = frappe._dict((d.attribute, d) for d in template_item.attributes)
numeric_attributes = frappe._dict((t.attribute, t) for t in \
frappe.db.sql("""select attribute, from_range, to_range, increment from `tabItem Variant Attribute`
where parent = %s and numeric_values=1""", (item), as_dict=1))
for attribute, value in args.items():
@ -532,9 +530,16 @@ def validate_item_variant_attributes(item, args):
# defensive validation to prevent ZeroDivisionError
frappe.throw(_("Increment for Attribute {0} cannot be 0").format(attribute))
is_in_range = from_range <= flt(value) <= to_range
precision = len(cstr(increment).split(".")[-1].rstrip("0"))
#avoid precision error by rounding the remainder
remainder = flt((flt(value) - from_range) % increment, precision)
if not ( (from_range <= flt(value) <= to_range) and (flt(value) - from_range) % increment == 0 ):
frappe.throw(_("Value for Attribute {0} must be within the range of {1} to {2} in the increments of {3}").format(attribute, from_range, to_range, increment), InvalidItemAttributeValueError)
is_incremental = remainder==0 or remainder==0 or remainder==increment
if not (is_in_range and is_incremental):
frappe.throw(_("Value for Attribute {0} must be within the range of {1} to {2} in the increments of {3}")\
.format(attribute, from_range, to_range, increment), InvalidItemAttributeValueError)
elif value not in attribute_values[attribute]:
frappe.throw(_("Value {0} for Attribute {1} does not exist in the list of valid Item Attribute Values").format(
@ -567,7 +572,7 @@ def find_variant(item, args):
for attribute, value in args.items():
for row in variant.attributes:
if row.attribute==attribute and row.attribute_value==value:
if row.attribute==attribute and row.attribute_value== cstr(value):
# this row matches
match_count += 1
break

View File

@ -6,9 +6,8 @@ import unittest
import frappe
from frappe.test_runner import make_test_records
from erpnext.stock.doctype.item.item import (WarehouseNotSet, ItemTemplateCannotHaveStock, create_variant,
from erpnext.stock.doctype.item.item import (WarehouseNotSet, create_variant,
ItemVariantExistsError, InvalidItemAttributeValueError)
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
test_ignore = ["BOM"]
test_dependencies = ["Warehouse"]
@ -129,8 +128,16 @@ class TestItem(unittest.TestCase):
# make template item
make_item("_Test Numeric Template Item", {
"attributes": [
{"attribute": "Test Size"},
{"attribute": "Test Item Length"}
{
"attribute": "Test Size"
},
{
"attribute": "Test Item Length",
"numeric_values": 1,
"from_range": 0.0,
"to_range": 100.0,
"increment": 0.5
}
],
"default_warehouse": "_Test Warehouse - _TC"
})

View File

@ -10,9 +10,11 @@ import frappe
from frappe.utils import cstr, flt, getdate
from frappe import _
from frappe.model.mapper import get_mapped_doc
from erpnext.stock.stock_balance import update_bin_qty, get_indented_qty
from erpnext.controllers.buying_controller import BuyingController
form_grid_templates = {
"items": "templates/form_grid/material_request_grid.html"
}
@ -136,19 +138,6 @@ class MaterialRequest(BuyingController):
def update_requested_qty(self, mr_item_rows=None):
"""update requested qty (before ordered_qty is updated)"""
from erpnext.stock.utils import get_bin
def _update_requested_qty(item_code, warehouse):
requested_qty = frappe.db.sql("""select sum(mr_item.qty - ifnull(mr_item.ordered_qty, 0))
from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr
where mr_item.item_code=%s and mr_item.warehouse=%s
and mr_item.qty > ifnull(mr_item.ordered_qty, 0) and mr_item.parent=mr.name
and mr.status!='Stopped' and mr.docstatus=1""", (item_code, warehouse))
bin_doc = get_bin(item_code, warehouse)
bin_doc.indented_qty = flt(requested_qty[0][0]) if requested_qty else 0
bin_doc.save()
item_wh_list = []
for d in self.get("items"):
if (not mr_item_rows or d.name in mr_item_rows) and [d.item_code, d.warehouse] not in item_wh_list \
@ -156,7 +145,9 @@ class MaterialRequest(BuyingController):
item_wh_list.append([d.item_code, d.warehouse])
for item_code, warehouse in item_wh_list:
_update_requested_qty(item_code, warehouse)
update_bin_qty(item_code, warehouse, {
"indented_qty": get_indented_qty(item_code, warehouse)
})
def update_completed_and_requested_qty(stock_entry, method):
if stock_entry.doctype == "Stock Entry":

View File

@ -29,6 +29,19 @@ class PurchaseReceipt(BuyingController):
'source_field': 'qty',
'percent_join_field': 'prevdoc_docname',
'overflow_type': 'receipt'
},
{
'source_dt': 'Purchase Receipt Item',
'target_dt': 'Purchase Order Item',
'join_field': 'prevdoc_detail_docname',
'target_field': 'returned_qty',
'target_parent_dt': 'Purchase Order',
# 'target_parent_field': 'per_received',
# 'target_ref_field': 'qty',
'source_field': '-1 * qty',
# 'percent_join_field': 'prevdoc_docname',
# 'overflow_type': 'receipt',
'extra_cond': """ and exists (select name from `tabPurchase Receipt` where name=`tabPurchase Receipt Item`.parent and is_return=1)"""
}]
def onload(self):
@ -113,11 +126,11 @@ class PurchaseReceipt(BuyingController):
}
})
if cint(frappe.db.get_single_value('Buying Settings', 'maintain_same_rate')):
if cint(frappe.db.get_single_value('Buying Settings', 'maintain_same_rate')) and not self.is_return:
self.validate_rate_with_reference_doc([["Purchase Order", "prevdoc_docname", "prevdoc_detail_docname"]])
def po_required(self):
if not self.is_return and frappe.db.get_value("Buying Settings", None, "po_required") == 'Yes':
if frappe.db.get_value("Buying Settings", None, "po_required") == 'Yes':
for d in self.get('items'):
if not d.prevdoc_docname:
frappe.throw(_("Purchase Order number required for Item {0}").format(d.item_code))
@ -221,9 +234,10 @@ class PurchaseReceipt(BuyingController):
# Set status as Submitted
frappe.db.set(self, 'status', 'Submitted')
if not self.is_return:
self.update_prevdoc_status()
self.update_ordered_qty()
if not self.is_return:
purchase_controller.update_last_purchase_rate(self, 1)
self.update_stock_ledger()
@ -257,12 +271,11 @@ class PurchaseReceipt(BuyingController):
self.update_stock_ledger()
if not self.is_return:
self.update_prevdoc_status()
# Must be called after updating received qty in PO
self.update_ordered_qty()
if not self.is_return:
pc_obj.update_last_purchase_rate(self, 0)
self.make_gl_entries_on_cancel()

View File

@ -3,7 +3,7 @@ frappe.listview_settings['Purchase Receipt'] = {
"transporter_name", "is_return"],
get_indicator: function(doc) {
if(cint(doc.is_return)==1) {
return [__("Return"), "darkgrey", "is_return,=,1"];
return [__("Return"), "darkgrey", "is_return,=,Yes"];
}
}
};

View File

@ -209,7 +209,7 @@ def validate_serial_no(sle, item_det):
frappe.throw(_("Serial No {0} quantity {1} cannot be a fraction").format(sle.item_code, sle.actual_qty))
if len(serial_nos) and len(serial_nos) != abs(cint(sle.actual_qty)):
frappe.throw(_("{0} Serial Numbers required for Item {0}. Only {0} provided.").format(sle.actual_qty, sle.item_code, len(serial_nos)),
frappe.throw(_("{0} Serial Numbers required for Item {1}. You have provided {2}.").format(sle.actual_qty, sle.item_code, len(serial_nos)),
SerialNoQtyError)
if len(serial_nos) != len(set(serial_nos)):

View File

@ -140,7 +140,7 @@ class Warehouse(Document):
self.recalculate_bin_qty(newdn)
def recalculate_bin_qty(self, newdn):
from erpnext.utilities.repost_stock import repost_stock
from erpnext.stock.stock_balance import repost_stock
frappe.db.auto_commit_on_many_writes = 1
existing_allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock")
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)

View File

@ -257,6 +257,10 @@ def validate_price_list(args):
def validate_conversion_rate(args, meta):
from erpnext.controllers.accounts_controller import validate_conversion_rate
if (not args.conversion_rate
and args.currency==frappe.db.get_value("Company", args.company, "default_currency")):
args.conversion_rate = 1.0
# validate currency conversion rate
validate_conversion_rate(args.currency, args.conversion_rate,
meta.get_label("conversion_rate"), args.company)

View File

@ -21,3 +21,5 @@ def send_message(subject="Website Query", message="", sender="", status="Open"):
"sent_or_received": "Received"
})
comm.insert(ignore_permissions=True)
return "okay"

View File

@ -1,6 +1,6 @@
from setuptools import setup, find_packages
version = "5.7.7"
version = "5.8.0"
with open("requirements.txt", "r") as f:
install_requires = f.readlines()