Merge branch 'develop' of github.com:frappe/erpnext into feature-pick-list
This commit is contained in:
commit
da2a623e38
erpnext
__init__.py
accounts
doctype
accounting_dimension
bank_transaction
payment_entry
payment_reconciliation
sales_invoice
page/bank_reconciliation
buying/doctype/purchase_order
change_log/v12
config
controllers
hr
doctype/leave_ledger_entry
report/employee_leave_balance
manufacturing
doctype
page/bom_comparison_tool
patches/v12_0
public/js
selling/doctype/quotation
stock/doctype/stock_entry
templates
@ -5,7 +5,7 @@ import frappe
|
||||
from erpnext.hooks import regional_overrides
|
||||
from frappe.utils import getdate
|
||||
|
||||
__version__ = '11.1.39'
|
||||
__version__ = '12.0.7'
|
||||
|
||||
def get_default_company(user=None):
|
||||
'''Get default company for user'''
|
||||
|
@ -40,9 +40,16 @@ frappe.ui.form.on('Accounting Dimension', {
|
||||
},
|
||||
|
||||
document_type: function(frm) {
|
||||
|
||||
frm.set_value('label', frm.doc.document_type);
|
||||
frm.set_value('fieldname', frappe.model.scrub(frm.doc.document_type));
|
||||
|
||||
if (frm.is_new()){
|
||||
let row = frappe.model.add_child(frm.doc, "Accounting Dimension Detail", "dimension_defaults");
|
||||
row.reference_document = frm.doc.document_type;
|
||||
frm.refresh_fields("dimension_defaults");
|
||||
}
|
||||
|
||||
frappe.db.get_value('Accounting Dimension', {'document_type': frm.doc.document_type}, 'document_type', (r) => {
|
||||
if (r && r.document_type) {
|
||||
frm.set_df_property('document_type', 'description', "Document type is already set as dimension");
|
||||
|
@ -45,7 +45,7 @@ class BankTransaction(StatusUpdater):
|
||||
def clear_linked_payment_entries(self):
|
||||
for payment_entry in self.payment_entries:
|
||||
allocated_amount = get_total_allocated_amount(payment_entry)
|
||||
paid_amount = get_paid_amount(payment_entry)
|
||||
paid_amount = get_paid_amount(payment_entry, self.currency)
|
||||
|
||||
if paid_amount and allocated_amount:
|
||||
if flt(allocated_amount[0]["allocated_amount"]) > flt(paid_amount):
|
||||
@ -80,9 +80,17 @@ def get_total_allocated_amount(payment_entry):
|
||||
AND
|
||||
bt.docstatus = 1""", (payment_entry.payment_document, payment_entry.payment_entry), as_dict=True)
|
||||
|
||||
def get_paid_amount(payment_entry):
|
||||
def get_paid_amount(payment_entry, currency):
|
||||
if payment_entry.payment_document in ["Payment Entry", "Sales Invoice", "Purchase Invoice"]:
|
||||
return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "paid_amount")
|
||||
|
||||
paid_amount_field = "paid_amount"
|
||||
if payment_entry.payment_document == 'Payment Entry':
|
||||
doc = frappe.get_doc("Payment Entry", payment_entry.payment_entry)
|
||||
paid_amount_field = ("base_paid_amount"
|
||||
if doc.paid_to_account_currency == currency else "paid_amount")
|
||||
|
||||
return frappe.db.get_value(payment_entry.payment_document,
|
||||
payment_entry.payment_entry, paid_amount_field)
|
||||
|
||||
elif payment_entry.payment_document == "Journal Entry":
|
||||
return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "total_credit")
|
||||
|
@ -624,8 +624,8 @@ def get_outstanding_reference_documents(args):
|
||||
data = negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed
|
||||
|
||||
if not data:
|
||||
frappe.msgprint(_("No outstanding invoices found for the {0} <b>{1}</b> which qualify the filters you have specified.")
|
||||
.format(args.get("party_type").lower(), args.get("party")))
|
||||
frappe.msgprint(_("No outstanding invoices found for the {0} {1} which qualify the filters you have specified.")
|
||||
.format(args.get("party_type").lower(), frappe.bold(args.get("party"))))
|
||||
|
||||
return data
|
||||
|
||||
|
@ -93,7 +93,7 @@ class PaymentReconciliation(Document):
|
||||
and `tab{doc}`.is_return = 1 and `tabGL Entry`.against_voucher_type = %(voucher_type)s
|
||||
and `tab{doc}`.docstatus = 1 and `tabGL Entry`.party = %(party)s
|
||||
and `tabGL Entry`.party_type = %(party_type)s and `tabGL Entry`.account = %(account)s
|
||||
GROUP BY `tabSales Invoice`.name
|
||||
GROUP BY `tab{doc}`.name
|
||||
Having
|
||||
amount > 0
|
||||
""".format(doc=voucher_type, dr_or_cr=dr_or_cr, reconciled_dr_or_cr=reconciled_dr_or_cr), {
|
||||
|
@ -78,6 +78,7 @@ class SalesInvoice(SellingController):
|
||||
self.so_dn_required()
|
||||
|
||||
self.validate_proj_cust()
|
||||
self.validate_pos_return()
|
||||
self.validate_with_previous_doc()
|
||||
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
||||
self.validate_uom_is_integer("uom", "qty")
|
||||
@ -199,6 +200,16 @@ class SalesInvoice(SellingController):
|
||||
if "Healthcare" in active_domains:
|
||||
manage_invoice_submit_cancel(self, "on_submit")
|
||||
|
||||
def validate_pos_return(self):
|
||||
|
||||
if self.is_pos and self.is_return:
|
||||
total_amount_in_payments = 0
|
||||
for payment in self.payments:
|
||||
total_amount_in_payments += payment.amount
|
||||
|
||||
if total_amount_in_payments < self.rounded_total:
|
||||
frappe.throw(_("Total payments amount can't be greater than {}".format(-self.rounded_total)))
|
||||
|
||||
def validate_pos_paid_amount(self):
|
||||
if len(self.payments) == 0 and self.is_pos:
|
||||
frappe.throw(_("At least one mode of payment is required for POS invoice."))
|
||||
|
@ -124,8 +124,6 @@ def check_matching_amount(bank_account, company, transaction):
|
||||
'txt': '%%%s%%' % amount
|
||||
}, as_dict=True)
|
||||
|
||||
frappe.errprint(journal_entries)
|
||||
|
||||
if transaction.credit > 0:
|
||||
sales_invoices = frappe.db.sql("""
|
||||
SELECT
|
||||
|
@ -483,7 +483,7 @@ def make_rm_stock_entry(purchase_order, rm_items):
|
||||
'from_warehouse': rm_item_data["warehouse"],
|
||||
'stock_uom': rm_item_data["stock_uom"],
|
||||
'main_item_code': rm_item_data["item_code"],
|
||||
'allow_alternative_item': item_wh[rm_item_code].get('allow_alternative_item')
|
||||
'allow_alternative_item': item_wh.get(rm_item_code, {}).get('allow_alternative_item')
|
||||
}
|
||||
}
|
||||
stock_entry.add_to_stock_entry_detail(items_dict)
|
||||
|
41
erpnext/change_log/v12/v12_0_0.md
Normal file
41
erpnext/change_log/v12/v12_0_0.md
Normal file
@ -0,0 +1,41 @@
|
||||
# Version 12 Release Notes
|
||||
|
||||
### Accounting
|
||||
1. [Accounting Dimensions](https://erpnext.com/docs/user/manual/en/accounts/accounting-dimensions)
|
||||
1. [Chart of Accounts Importer](https://erpnext.com/docs/user/manual/en/setting-up/chart-of-accounts-importer)
|
||||
1. [Invoice Discounting](https://erpnext.com/docs/user/manual/en/accounts/invoice_discounting)
|
||||
1. [Tally Migrator](https://github.com/frappe/erpnext/pull/17405)
|
||||
|
||||
### Stock
|
||||
1. [Serialized & Batched Item Reconciliation](https://erpnext.com/docs/user/manual/en/setting-up/stock-reconciliation#12-for-serialized-items)
|
||||
1. [Auto Fetch Serialized Items](https://erpnext.com/version-12/release-notes/features#new-upload-dialog)
|
||||
1. [Item Tax Templates](https://erpnext.com/docs/user/manual/en/accounts/item-tax-template)
|
||||
|
||||
### HR
|
||||
1. [Auto Attendance](https://erpnext.com/docs/user/manual/en/human-resources/auto-attendance)
|
||||
1. [Employee Skill Map](https://erpnext.com/docs/user/manual/en/human-resources/employee_skill_map)
|
||||
1. [Encrypted Salary Slips](https://erpnext.com/docs/user/manual/en/human-resources/hr-settings#24-encrypt-salary-slips-in-emails)
|
||||
1. [Leave Ledger](https://erpnext.com/docs/user/manual/en/human-resources/leave-ledger-entry)
|
||||
1. [Staffing Plan](https://erpnext.com/docs/user/manual/en/human-resources/staffing-plan)
|
||||
|
||||
### CRM
|
||||
1. [Promotional Scheme](https://erpnext.com/docs/user/manual/en/accounts/promotional-schemes)
|
||||
1. [SLA](https://erpnext.com/docs/user/manual/en/support/service-level-agreement)
|
||||
1. [Exotel Call Integration](https://erpnext.com/docs/user/manual/en/erpnext_integration/exotel_integration)
|
||||
1. [Email Campaign](https://erpnext.com/docs/user/manual/en/CRM/email-campaign)
|
||||
|
||||
### Domain Specific Features
|
||||
1. [Learning Management System](https://erpnext.com/docs/user/manual/en/education/setting-up-lms)
|
||||
1. [Quality Management System](https://erpnext.com/docs/user/manual/en/quality-management)
|
||||
1. [Production Planning Enhancements](https://erpnext.com/docs/user/manual/en/manufacturing/production-plan/planning-for-material-requests)
|
||||
1. [Project Template](https://erpnext.com/docs/user/manual/en/projects/project-template)
|
||||
|
||||
### New Reports
|
||||
1. [Bank Remittance](https://erpnext.com/docs/user/manual/en/human-resources/human-resources-reports#bank-remittance-report)
|
||||
1. [BOM Explorer](https://erpnext.com/docs/user/manual/en/stock/articles/bom_explorer)
|
||||
1. [Billing Summary Report](https://erpnext.com/docs/user/manual/en/projects/reports/billing_summary_reports)
|
||||
1. [Procurement Tracker Report](docs/user/manual/en/buying/articles/procurement-tracker-report)
|
||||
1. [Loan Repayment](https://erpnext.com/docs/user/manual/en/human-resources/human-resources-reports#loan-repayment-report)
|
||||
1. [GSTR-3B](https://erpnext.com/docs/user/manual/en/regional/india/gst-3b-report)
|
||||
1. [Sales Partner](https://erpnext.com/docs/user/manual/en/selling/sales-partner#sales-partner-reports)
|
||||
1. [Sales Partner Target Variance based on Item Group](https://erpnext.com/docs/user/manual/en/selling/sales-partner#sales-partner-target-variance-based-on-item-group)
|
@ -166,6 +166,10 @@ def get_data():
|
||||
"name": "Salary Slip",
|
||||
"onboard": 1,
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Payroll Period",
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Salary Component",
|
||||
|
@ -94,6 +94,13 @@ def get_data():
|
||||
"name": "BOM Update Tool",
|
||||
"description": _("Replace BOM and update latest price in all BOMs"),
|
||||
},
|
||||
{
|
||||
"type": "page",
|
||||
"label": _("BOM Comparison Tool"),
|
||||
"name": "bom-comparison-tool",
|
||||
"description": _("Compare BOMs for changes in Raw Materials and Operations"),
|
||||
"data_doctype": "BOM"
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -727,7 +727,7 @@ def get_items_from_bom(item_code, bom, exploded_item=1):
|
||||
where
|
||||
t2.parent = t1.name and t1.item = %s
|
||||
and t1.docstatus = 1 and t1.is_active = 1 and t1.name = %s
|
||||
and t2.item_code = t3.name and t3.is_stock_item = 1""".format(doctype),
|
||||
and t2.item_code = t3.name""".format(doctype),
|
||||
(item_code, bom), as_dict=1)
|
||||
|
||||
if not bom_items:
|
||||
|
@ -21,42 +21,45 @@ def get_list_context(context=None):
|
||||
|
||||
def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified"):
|
||||
user = frappe.session.user
|
||||
key = None
|
||||
ignore_permissions = False
|
||||
|
||||
if not filters: filters = []
|
||||
|
||||
if doctype == 'Supplier Quotation':
|
||||
filters.append((doctype, "docstatus", "<", 2))
|
||||
filters.append((doctype, 'docstatus', '<', 2))
|
||||
else:
|
||||
filters.append((doctype, "docstatus", "=", 1))
|
||||
filters.append((doctype, 'docstatus', '=', 1))
|
||||
|
||||
if (user != "Guest" and is_website_user()) or doctype == 'Request for Quotation':
|
||||
if (user != 'Guest' and is_website_user()) or doctype == 'Request for Quotation':
|
||||
parties_doctype = 'Request for Quotation Supplier' if doctype == 'Request for Quotation' else doctype
|
||||
# find party for this contact
|
||||
customers, suppliers = get_customers_suppliers(parties_doctype, user)
|
||||
|
||||
if not customers and not suppliers: return []
|
||||
|
||||
key, parties = get_party_details(customers, suppliers)
|
||||
|
||||
if doctype == 'Request for Quotation':
|
||||
return rfq_transaction_list(parties_doctype, doctype, parties, limit_start, limit_page_length)
|
||||
|
||||
filters.append((doctype, key, "in", parties))
|
||||
|
||||
if key:
|
||||
return post_process(doctype, get_list_for_transactions(doctype, txt,
|
||||
filters=filters, fields="name",limit_start=limit_start,
|
||||
limit_page_length=limit_page_length,ignore_permissions=True,
|
||||
order_by="modified desc"))
|
||||
if customers:
|
||||
if doctype == 'Quotation':
|
||||
filters.append(('quotation_to', '=', 'Customer'))
|
||||
filters.append(('party_name', 'in', customers))
|
||||
else:
|
||||
filters.append(('customer', 'in', customers))
|
||||
elif suppliers:
|
||||
filters.append(('supplier', 'in', suppliers))
|
||||
else:
|
||||
return []
|
||||
|
||||
return post_process(doctype, get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length,
|
||||
fields="name", order_by="modified desc"))
|
||||
if doctype == 'Request for Quotation':
|
||||
parties = customers or suppliers
|
||||
return rfq_transaction_list(parties_doctype, doctype, parties, limit_start, limit_page_length)
|
||||
|
||||
# Since customers and supplier do not have direct access to internal doctypes
|
||||
ignore_permissions = True
|
||||
|
||||
transactions = get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length,
|
||||
fields='name', ignore_permissions=ignore_permissions, order_by='modified desc')
|
||||
|
||||
return post_process(doctype, transactions)
|
||||
|
||||
def get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length=20,
|
||||
ignore_permissions=False,fields=None, order_by=None):
|
||||
ignore_permissions=False, fields=None, order_by=None):
|
||||
""" Get List of transactions like Invoices, Orders """
|
||||
from frappe.www.list import get_list
|
||||
meta = frappe.get_meta(doctype)
|
||||
@ -83,16 +86,6 @@ def get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_len
|
||||
|
||||
return data
|
||||
|
||||
def get_party_details(customers, suppliers):
|
||||
if customers:
|
||||
key, parties = "customer", customers
|
||||
elif suppliers:
|
||||
key, parties = "supplier", suppliers
|
||||
else:
|
||||
key, parties = "customer", []
|
||||
|
||||
return key, parties
|
||||
|
||||
def rfq_transaction_list(parties_doctype, doctype, parties, limit_start, limit_page_length):
|
||||
data = frappe.db.sql("""select distinct parent as name, supplier from `tab{doctype}`
|
||||
where supplier = '{supplier}' and docstatus=1 order by modified desc limit {start}, {len}""".
|
||||
@ -130,6 +123,11 @@ def get_customers_suppliers(doctype, user):
|
||||
suppliers = []
|
||||
meta = frappe.get_meta(doctype)
|
||||
|
||||
customer_field_name = get_customer_field_name(doctype)
|
||||
|
||||
has_customer_field = meta.has_field(customer_field_name)
|
||||
has_supplier_field = meta.has_field('supplier')
|
||||
|
||||
if has_common(["Supplier", "Customer"], frappe.get_roles(user)):
|
||||
contacts = frappe.db.sql("""
|
||||
select
|
||||
@ -141,27 +139,40 @@ def get_customers_suppliers(doctype, user):
|
||||
where
|
||||
`tabContact`.name=`tabDynamic Link`.parent and `tabContact`.email_id =%s
|
||||
""", user, as_dict=1)
|
||||
customers = [c.link_name for c in contacts if c.link_doctype == 'Customer'] \
|
||||
if meta.get_field("customer") else None
|
||||
suppliers = [c.link_name for c in contacts if c.link_doctype == 'Supplier'] \
|
||||
if meta.get_field("supplier") else None
|
||||
customers = [c.link_name for c in contacts if c.link_doctype == 'Customer']
|
||||
suppliers = [c.link_name for c in contacts if c.link_doctype == 'Supplier']
|
||||
elif frappe.has_permission(doctype, 'read', user=user):
|
||||
customers = [customer.name for customer in frappe.get_list("Customer")] \
|
||||
if meta.get_field("customer") else None
|
||||
suppliers = [supplier.name for supplier in frappe.get_list("Customer")] \
|
||||
if meta.get_field("supplier") else None
|
||||
customer_list = frappe.get_list("Customer")
|
||||
customers = suppliers = [customer.name for customer in customer_list]
|
||||
|
||||
return customers, suppliers
|
||||
return customers if has_customer_field else None, \
|
||||
suppliers if has_supplier_field else None
|
||||
|
||||
def has_website_permission(doc, ptype, user, verbose=False):
|
||||
doctype = doc.doctype
|
||||
customers, suppliers = get_customers_suppliers(doctype, user)
|
||||
if customers:
|
||||
return frappe.get_all(doctype, filters=[(doctype, "customer", "in", customers),
|
||||
(doctype, "name", "=", doc.name)]) and True or False
|
||||
return frappe.db.exists(doctype, get_customer_filter(doc, customers))
|
||||
elif suppliers:
|
||||
fieldname = 'suppliers' if doctype == 'Request for Quotation' else 'supplier'
|
||||
return frappe.get_all(doctype, filters=[(doctype, fieldname, "in", suppliers),
|
||||
(doctype, "name", "=", doc.name)]) and True or False
|
||||
return frappe.db.exists(doctype, filters={
|
||||
'name': doc.name,
|
||||
fieldname: ["in", suppliers]
|
||||
})
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_customer_filter(doc, customers):
|
||||
doctype = doc.doctype
|
||||
filters = frappe._dict()
|
||||
filters.name = doc.name
|
||||
filters[get_customer_field_name(doctype)] = ['in', customers]
|
||||
if doctype == 'Quotation':
|
||||
filters.quotation_to = 'Customer'
|
||||
return filters
|
||||
|
||||
def get_customer_field_name(doctype):
|
||||
if doctype == 'Quotation':
|
||||
return 'party_name'
|
||||
else:
|
||||
return 'customer'
|
@ -27,6 +27,7 @@
|
||||
"options": "Employee"
|
||||
},
|
||||
{
|
||||
"fetch_from": "employee.employee_name",
|
||||
"fieldname": "employee_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Employee Name"
|
||||
@ -101,7 +102,7 @@
|
||||
],
|
||||
"in_create": 1,
|
||||
"is_submittable": 1,
|
||||
"modified": "2019-06-21 00:37:07.782810",
|
||||
"modified": "2019-08-20 14:40:04.130799",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Leave Ledger Entry",
|
||||
|
@ -97,16 +97,15 @@ def get_approvers(department):
|
||||
|
||||
def get_total_allocated_leaves(employee, leave_type, from_date, to_date):
|
||||
''' Returns leave allocation between from date and to date '''
|
||||
filters= {
|
||||
'from_date': ['between', (from_date, to_date)],
|
||||
'to_date': ['between', (from_date, to_date)],
|
||||
leave_allocation_records = frappe.db.get_all('Leave Ledger Entry', filters={
|
||||
'docstatus': 1,
|
||||
'is_expired': 0,
|
||||
'leave_type': leave_type,
|
||||
'employee': employee,
|
||||
'transaction_type': 'Leave Allocation'
|
||||
}
|
||||
|
||||
leave_allocation_records = frappe.db.get_all('Leave Ledger Entry', filters=filters, fields=['SUM(leaves) as leaves'])
|
||||
}, or_filters={
|
||||
'from_date': ['between', (from_date, to_date)],
|
||||
'to_date': ['between', (from_date, to_date)]
|
||||
}, fields=['SUM(leaves) as leaves'])
|
||||
|
||||
return flt(leave_allocation_records[0].get('leaves')) if leave_allocation_records else flt(0)
|
File diff suppressed because it is too large
Load Diff
@ -9,6 +9,7 @@ from erpnext.setup.utils import get_exchange_rate
|
||||
from frappe.website.website_generator import WebsiteGenerator
|
||||
from erpnext.stock.get_item_details import get_conversion_factor
|
||||
from erpnext.stock.get_item_details import get_price_list_rate
|
||||
from frappe.core.doctype.version.version import get_diff
|
||||
|
||||
import functools
|
||||
|
||||
@ -763,3 +764,52 @@ def add_additional_cost(stock_entry, work_order):
|
||||
'description': name[0],
|
||||
'amount': items.get(name[0])
|
||||
})
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_bom_diff(bom1, bom2):
|
||||
from frappe.model import table_fields
|
||||
|
||||
doc1 = frappe.get_doc('BOM', bom1)
|
||||
doc2 = frappe.get_doc('BOM', bom2)
|
||||
|
||||
out = get_diff(doc1, doc2)
|
||||
out.row_changed = []
|
||||
out.added = []
|
||||
out.removed = []
|
||||
|
||||
meta = doc1.meta
|
||||
|
||||
identifiers = {
|
||||
'operations': 'operation',
|
||||
'items': 'item_code',
|
||||
'scrap_items': 'item_code',
|
||||
'exploded_items': 'item_code'
|
||||
}
|
||||
|
||||
for df in meta.fields:
|
||||
old_value, new_value = doc1.get(df.fieldname), doc2.get(df.fieldname)
|
||||
|
||||
if df.fieldtype in table_fields:
|
||||
identifier = identifiers[df.fieldname]
|
||||
# make maps
|
||||
old_row_by_identifier, new_row_by_identifier = {}, {}
|
||||
for d in old_value:
|
||||
old_row_by_identifier[d.get(identifier)] = d
|
||||
for d in new_value:
|
||||
new_row_by_identifier[d.get(identifier)] = d
|
||||
|
||||
# check rows for additions, changes
|
||||
for i, d in enumerate(new_value):
|
||||
if d.get(identifier) in old_row_by_identifier:
|
||||
diff = get_diff(old_row_by_identifier[d.get(identifier)], d, for_child=True)
|
||||
if diff and diff.changed:
|
||||
out.row_changed.append((df.fieldname, i, d.get(identifier), diff.changed))
|
||||
else:
|
||||
out.added.append([df.fieldname, d.as_dict()])
|
||||
|
||||
# check for deletions
|
||||
for d in old_value:
|
||||
if not d.get(identifier) in new_row_by_identifier:
|
||||
out.removed.append([df.fieldname, d.as_dict()])
|
||||
|
||||
return out
|
||||
|
@ -320,7 +320,8 @@ class ProductionPlan(Document):
|
||||
'qty': data.get("stock_qty") * item.get("qty"),
|
||||
'production_plan': self.name,
|
||||
'company': self.company,
|
||||
'fg_warehouse': item.get("fg_warehouse")
|
||||
'fg_warehouse': item.get("fg_warehouse"),
|
||||
'update_consumed_material_cost_in_project': 0
|
||||
})
|
||||
|
||||
work_order = self.create_work_order(data)
|
||||
|
@ -4,6 +4,7 @@
|
||||
"creation": "2013-01-10 16:34:16",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"item",
|
||||
"naming_series",
|
||||
@ -12,9 +13,6 @@
|
||||
"item_name",
|
||||
"image",
|
||||
"bom_no",
|
||||
"allow_alternative_item",
|
||||
"use_multi_level_bom",
|
||||
"skip_transfer",
|
||||
"column_break1",
|
||||
"company",
|
||||
"qty",
|
||||
@ -22,7 +20,13 @@
|
||||
"produced_qty",
|
||||
"sales_order",
|
||||
"project",
|
||||
"settings_section",
|
||||
"allow_alternative_item",
|
||||
"use_multi_level_bom",
|
||||
"column_break_18",
|
||||
"skip_transfer",
|
||||
"from_wip_warehouse",
|
||||
"update_consumed_material_cost_in_project",
|
||||
"warehouses",
|
||||
"wip_warehouse",
|
||||
"fg_warehouse",
|
||||
@ -442,13 +446,28 @@
|
||||
"oldfieldtype": "Data",
|
||||
"options": "Work Order",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_18",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "update_consumed_material_cost_in_project",
|
||||
"fieldtype": "Check",
|
||||
"label": "Update Consumed Material Cost In Project"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cogs",
|
||||
"idx": 1,
|
||||
"image_field": "image",
|
||||
"is_submittable": 1,
|
||||
"modified": "2019-05-27 09:36:16.707719",
|
||||
"modified": "2019-07-31 00:13:38.218277",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Work Order",
|
||||
@ -477,8 +496,9 @@
|
||||
"role": "Stock User"
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"title_field": "production_item",
|
||||
"track_changes": 1,
|
||||
"track_seen": 1
|
||||
}
|
||||
}
|
@ -0,0 +1,213 @@
|
||||
frappe.pages['bom-comparison-tool'].on_page_load = function(wrapper) {
|
||||
var page = frappe.ui.make_app_page({
|
||||
parent: wrapper,
|
||||
title: __('BOM Comparison Tool'),
|
||||
single_column: true
|
||||
});
|
||||
|
||||
new erpnext.BOMComparisonTool(page);
|
||||
}
|
||||
|
||||
erpnext.BOMComparisonTool = class BOMComparisonTool {
|
||||
constructor(page) {
|
||||
this.page = page;
|
||||
this.make_form();
|
||||
}
|
||||
|
||||
make_form() {
|
||||
this.form = new frappe.ui.FieldGroup({
|
||||
fields: [
|
||||
{
|
||||
label: __('BOM 1'),
|
||||
fieldname: 'name1',
|
||||
fieldtype: 'Link',
|
||||
options: 'BOM',
|
||||
change: () => this.fetch_and_render()
|
||||
},
|
||||
{
|
||||
fieldtype: 'Column Break'
|
||||
},
|
||||
{
|
||||
label: __('BOM 2'),
|
||||
fieldname: 'name2',
|
||||
fieldtype: 'Link',
|
||||
options: 'BOM',
|
||||
change: () => this.fetch_and_render()
|
||||
},
|
||||
{
|
||||
fieldtype: 'Section Break'
|
||||
},
|
||||
{
|
||||
fieldtype: 'HTML',
|
||||
fieldname: 'preview'
|
||||
}
|
||||
],
|
||||
body: this.page.body
|
||||
});
|
||||
this.form.make();
|
||||
}
|
||||
|
||||
fetch_and_render() {
|
||||
let { name1, name2 } = this.form.get_values();
|
||||
if (!(name1 && name2)) {
|
||||
this.form.get_field('preview').html('');
|
||||
return;
|
||||
}
|
||||
|
||||
// set working state
|
||||
this.form.get_field('preview').html(`
|
||||
<div class="text-muted margin-top">
|
||||
${__("Fetching...")}
|
||||
</div>
|
||||
`);
|
||||
|
||||
frappe.call('erpnext.manufacturing.doctype.bom.bom.get_bom_diff', {
|
||||
bom1: name1,
|
||||
bom2: name2
|
||||
}).then(r => {
|
||||
let diff = r.message;
|
||||
frappe.model.with_doctype('BOM', () => {
|
||||
this.render('BOM', name1, name2, diff);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render(doctype, name1, name2, diff) {
|
||||
|
||||
let change_html = (title, doctype, changed) => {
|
||||
let values_changed = this.get_changed_values(doctype, changed)
|
||||
.map(change => {
|
||||
let [fieldname, value1, value2] = change;
|
||||
return `
|
||||
<tr>
|
||||
<td>${frappe.meta.get_label(doctype, fieldname)}</td>
|
||||
<td>${value1}</td>
|
||||
<td>${value2}</td>
|
||||
</tr>
|
||||
`;
|
||||
})
|
||||
.join('');
|
||||
|
||||
return `
|
||||
<h4 class="margin-top">${title}</h4>
|
||||
<div>
|
||||
<table class="table table-bordered">
|
||||
<tr>
|
||||
<th width="33%">${__('Field')}</th>
|
||||
<th width="33%">${name1}</th>
|
||||
<th width="33%">${name2}</th>
|
||||
</tr>
|
||||
${values_changed}
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
let value_changes = change_html(__('Values Changed'), doctype, diff.changed);
|
||||
|
||||
let row_changes_by_fieldname = group_items(diff.row_changed, change => change[0]);
|
||||
|
||||
let table_changes = Object.keys(row_changes_by_fieldname).map(fieldname => {
|
||||
let changes = row_changes_by_fieldname[fieldname];
|
||||
let df = frappe.meta.get_docfield(doctype, fieldname);
|
||||
|
||||
let html = changes.map(change => {
|
||||
let [fieldname,, item_code, changes] = change;
|
||||
let df = frappe.meta.get_docfield(doctype, fieldname);
|
||||
let child_doctype = df.options;
|
||||
let values_changed = this.get_changed_values(child_doctype, changes);
|
||||
|
||||
return values_changed.map((change, i) => {
|
||||
let [fieldname, value1, value2] = change;
|
||||
let th = i === 0
|
||||
? `<th rowspan="${values_changed.length}">${item_code}</th>`
|
||||
: '';
|
||||
return `
|
||||
<tr>
|
||||
${th}
|
||||
<td>${frappe.meta.get_label(child_doctype, fieldname)}</td>
|
||||
<td>${value1}</td>
|
||||
<td>${value2}</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
}).join('');
|
||||
|
||||
return `
|
||||
<h4 class="margin-top">${__('Changes in {0}', [df.label])}</h4>
|
||||
<table class="table table-bordered">
|
||||
<tr>
|
||||
<th width="25%">${__('Item Code')}</th>
|
||||
<th width="25%">${__('Field')}</th>
|
||||
<th width="25%">${name1}</th>
|
||||
<th width="25%">${name2}</th>
|
||||
</tr>
|
||||
${html}
|
||||
</table>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
let get_added_removed_html = (title, grouped_items) => {
|
||||
return Object.keys(grouped_items).map(fieldname => {
|
||||
let rows = grouped_items[fieldname];
|
||||
let df = frappe.meta.get_docfield(doctype, fieldname);
|
||||
let fields = frappe.meta.get_docfields(df.options)
|
||||
.filter(df => df.in_list_view);
|
||||
|
||||
let html = rows.map(row => {
|
||||
let [, doc] = row;
|
||||
let cells = fields
|
||||
.map(df => `<td>${doc[df.fieldname]}</td>`)
|
||||
.join('');
|
||||
return `<tr>${cells}</tr>`;
|
||||
}).join('');
|
||||
|
||||
let header = fields.map(df => `<th>${df.label}</th>`).join('');
|
||||
return `
|
||||
<h4 class="margin-top">${$.format(title, [df.label])}</h4>
|
||||
<table class="table table-bordered">
|
||||
<tr>${header}</tr>
|
||||
${html}
|
||||
</table>
|
||||
`;
|
||||
}).join('');
|
||||
};
|
||||
|
||||
let added_by_fieldname = group_items(diff.added, change => change[0]);
|
||||
let removed_by_fieldname = group_items(diff.removed, change => change[0]);
|
||||
|
||||
let added_html = get_added_removed_html(__('Rows Added in {0}'), added_by_fieldname);
|
||||
let removed_html = get_added_removed_html(__('Rows Removed in {0}'), removed_by_fieldname);
|
||||
|
||||
let html = `
|
||||
${value_changes}
|
||||
${table_changes}
|
||||
${added_html}
|
||||
${removed_html}
|
||||
`;
|
||||
|
||||
this.form.get_field('preview').html(html);
|
||||
}
|
||||
|
||||
get_changed_values(doctype, changed) {
|
||||
return changed.filter(change => {
|
||||
let [fieldname, value1, value2] = change;
|
||||
if (!value1) value1 = '';
|
||||
if (!value2) value2 = '';
|
||||
if (value1 === value2) return false;
|
||||
let df = frappe.meta.get_docfield(doctype, fieldname);
|
||||
if (!df) return false;
|
||||
if (df.hidden) return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function group_items(array, fn) {
|
||||
return array.reduce((acc, item) => {
|
||||
let key = fn(item);
|
||||
acc[key] = acc[key] || [];
|
||||
acc[key].push(item);
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
{
|
||||
"content": null,
|
||||
"creation": "2019-07-29 13:24:38.201981",
|
||||
"docstatus": 0,
|
||||
"doctype": "Page",
|
||||
"idx": 0,
|
||||
"modified": "2019-07-29 13:24:38.201981",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "bom-comparison-tool",
|
||||
"owner": "Administrator",
|
||||
"page_name": "BOM Comparison Tool",
|
||||
"restrict_to_domain": "Manufacturing",
|
||||
"roles": [
|
||||
{
|
||||
"role": "System Manager"
|
||||
},
|
||||
{
|
||||
"role": "Manufacturing User"
|
||||
},
|
||||
{
|
||||
"role": "Manufacturing Manager"
|
||||
}
|
||||
],
|
||||
"script": null,
|
||||
"standard": "Yes",
|
||||
"style": null,
|
||||
"system_page": 0,
|
||||
"title": "BOM Comparison Tool"
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils import getdate
|
||||
from frappe.utils import getdate, today
|
||||
|
||||
def execute():
|
||||
""" Generates leave ledger entries for leave allocation/application/encashment
|
||||
@ -66,6 +66,7 @@ def generate_expiry_allocation_ledger_entries():
|
||||
if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Allocation', 'transaction_name': allocation.name, 'is_expired': 1}):
|
||||
allocation.update(dict(doctype="Leave Allocation"))
|
||||
allocation_obj = frappe.get_doc(allocation)
|
||||
if allocation_obj.to_date <= getdate(today()):
|
||||
expire_allocation(allocation_obj)
|
||||
|
||||
def get_allocation_records():
|
||||
|
@ -60,7 +60,15 @@ $.extend(erpnext, {
|
||||
|
||||
var me = this;
|
||||
$btn.on("click", function() {
|
||||
me.show_serial_batch_selector(grid_row.frm, grid_row.doc);
|
||||
let callback = '';
|
||||
let on_close = '';
|
||||
|
||||
if (grid_row.doc.serial_no) {
|
||||
grid_row.doc.has_serial_no = true;
|
||||
}
|
||||
|
||||
me.show_serial_batch_selector(grid_row.frm, grid_row.doc,
|
||||
callback, on_close, true);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -68,6 +68,28 @@ erpnext.child_docs.forEach((doctype) => {
|
||||
});
|
||||
},
|
||||
|
||||
accounts_add: function(frm, cdt, cdn) {
|
||||
erpnext.dimension_filters.forEach((dimension) => {
|
||||
var row = frappe.get_doc(cdt, cdn);
|
||||
frm.script_manager.copy_from_first_row("accounts", row, [dimension['fieldname']]);
|
||||
});
|
||||
},
|
||||
|
||||
company: function(frm) {
|
||||
if(frm.doc.company) {
|
||||
erpnext.dimension_filters.forEach((dimension) => {
|
||||
frm.set_value(dimension['fieldname'], erpnext.default_dimensions[frm.doc.company][dimension['document_type']]);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
items_add: function(frm, cdt, cdn) {
|
||||
erpnext.dimension_filters.forEach((dimension) => {
|
||||
var row = frappe.get_doc(cdt, cdn);
|
||||
frm.script_manager.copy_from_first_row("items", row, [dimension['fieldname']]);
|
||||
});
|
||||
},
|
||||
|
||||
accounts_add: function(frm, cdt, cdn) {
|
||||
erpnext.dimension_filters.forEach((dimension) => {
|
||||
var row = frappe.get_doc(cdt, cdn);
|
||||
|
@ -34,7 +34,7 @@ class Quotation(SellingController):
|
||||
self.with_items = 1
|
||||
|
||||
def validate_valid_till(self):
|
||||
if self.valid_till and self.valid_till < self.transaction_date:
|
||||
if self.valid_till and getdate(self.valid_till) < getdate(self.transaction_date):
|
||||
frappe.throw(_("Valid till date cannot be before transaction date"))
|
||||
|
||||
def has_sales_order(self):
|
||||
|
@ -145,6 +145,10 @@ class StockEntry(StockController):
|
||||
self.precision("transfer_qty", item))
|
||||
|
||||
def update_cost_in_project(self):
|
||||
if (self.work_order and not frappe.db.get_value("Work Order",
|
||||
self.work_order, "update_consumed_material_cost_in_project")):
|
||||
return
|
||||
|
||||
if self.project:
|
||||
amount = frappe.db.sql(""" select ifnull(sum(sed.amount), 0)
|
||||
from
|
||||
|
@ -32,7 +32,7 @@ frappe.ready(function() {
|
||||
if(r.message.product_info.in_stock===0) {
|
||||
$(".item-stock").html("<div style='color: red'> <i class='fa fa-close'></i> {{ _("Not in stock") }}</div>");
|
||||
}
|
||||
else if(r.message.product_info.in_stock===1) {
|
||||
else if(r.message.product_info.in_stock===1 && r.message.cart_settings.show_stock_availability) {
|
||||
var qty_display = "{{ _("In stock") }}";
|
||||
if (r.message.product_info.show_stock_qty) {
|
||||
qty_display += " ("+r.message.product_info.stock_qty+")";
|
||||
|
@ -5,8 +5,7 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import formatdate
|
||||
from erpnext.controllers.website_list_for_contact import (get_customers_suppliers,
|
||||
get_party_details)
|
||||
from erpnext.controllers.website_list_for_contact import get_customers_suppliers
|
||||
|
||||
def get_context(context):
|
||||
context.no_cache = 1
|
||||
@ -23,8 +22,8 @@ def get_supplier():
|
||||
doctype = frappe.form_dict.doctype
|
||||
parties_doctype = 'Request for Quotation Supplier' if doctype == 'Request for Quotation' else doctype
|
||||
customers, suppliers = get_customers_suppliers(parties_doctype, frappe.session.user)
|
||||
key, parties = get_party_details(customers, suppliers)
|
||||
return parties[0] if key == 'supplier' else ''
|
||||
|
||||
return suppliers[0] if suppliers else ''
|
||||
|
||||
def check_supplier_has_docname_access(supplier):
|
||||
status = True
|
||||
|
Loading…
Reference in New Issue
Block a user