Merge branch 'develop' into supplier_items
This commit is contained in:
commit
98b86ecbc2
@ -5,7 +5,7 @@ import frappe
|
||||
from erpnext.hooks import regional_overrides
|
||||
from frappe.utils import getdate
|
||||
|
||||
__version__ = '11.1.39'
|
||||
__version__ = '12.0.8'
|
||||
|
||||
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,12 +45,12 @@ 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):
|
||||
frappe.throw(_("The total allocated amount ({0}) is greated than the paid amount ({1}).".format(flt(allocated_amount[0]["allocated_amount"]), flt(paid_amount))))
|
||||
elif flt(allocated_amount[0]["allocated_amount"]) == flt(paid_amount):
|
||||
else:
|
||||
if payment_entry.payment_document in ["Payment Entry", "Journal Entry", "Purchase Invoice", "Expense Claim"]:
|
||||
self.clear_simple_entry(payment_entry)
|
||||
|
||||
@ -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")
|
||||
|
@ -8,7 +8,8 @@
|
||||
"customer",
|
||||
"column_break_3",
|
||||
"posting_date",
|
||||
"outstanding_amount"
|
||||
"outstanding_amount",
|
||||
"debit_to"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -48,10 +49,18 @@
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "sales_invoice.debit_to",
|
||||
"fieldname": "debit_to",
|
||||
"fieldtype": "Link",
|
||||
"label": "Debit to",
|
||||
"options": "Account",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"modified": "2019-05-30 19:27:29.436153",
|
||||
"modified": "2019-08-07 15:13:55.808349",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Discounted Invoice",
|
||||
|
@ -13,41 +13,57 @@ frappe.ui.form.on('Invoice Discounting', {
|
||||
};
|
||||
});
|
||||
|
||||
frm.events.filter_accounts("bank_account", frm, {"account_type": "Bank"});
|
||||
frm.events.filter_accounts("bank_charges_account", frm, {"root_type": "Expense"});
|
||||
frm.events.filter_accounts("short_term_loan", frm, {"root_type": "Liability"});
|
||||
frm.events.filter_accounts("accounts_receivable_credit", frm, {"account_type": "Receivable"});
|
||||
frm.events.filter_accounts("accounts_receivable_discounted", frm, {"account_type": "Receivable"});
|
||||
frm.events.filter_accounts("accounts_receivable_unpaid", frm, {"account_type": "Receivable"});
|
||||
|
||||
frm.events.filter_accounts("bank_account", frm, [["account_type", "=", "Bank"]]);
|
||||
frm.events.filter_accounts("bank_charges_account", frm, [["root_type", "=", "Expense"]]);
|
||||
frm.events.filter_accounts("short_term_loan", frm, [["root_type", "=", "Liability"]]);
|
||||
frm.events.filter_accounts("accounts_receivable_discounted", frm, [["account_type", "=", "Receivable"]]);
|
||||
frm.events.filter_accounts("accounts_receivable_credit", frm, [["account_type", "=", "Receivable"]]);
|
||||
frm.events.filter_accounts("accounts_receivable_unpaid", frm, [["account_type", "=", "Receivable"]]);
|
||||
|
||||
},
|
||||
|
||||
filter_accounts: (fieldname, frm, addl_filters) => {
|
||||
let filters = {
|
||||
"company": frm.doc.company,
|
||||
"is_group": 0
|
||||
};
|
||||
if(addl_filters) Object.assign(filters, addl_filters);
|
||||
let filters = [
|
||||
["company", "=", frm.doc.company],
|
||||
["is_group", "=", 0]
|
||||
];
|
||||
if(addl_filters){
|
||||
filters = $.merge(filters , addl_filters);
|
||||
}
|
||||
|
||||
frm.set_query(fieldname, () => { return { "filters": filters }; });
|
||||
},
|
||||
|
||||
refresh_filters: (frm) =>{
|
||||
let invoice_accounts = Object.keys(frm.doc.invoices).map(function(key) {
|
||||
return frm.doc.invoices[key].debit_to;
|
||||
});
|
||||
let filters = [
|
||||
["account_type", "=", "Receivable"],
|
||||
["name", "not in", invoice_accounts]
|
||||
];
|
||||
frm.events.filter_accounts("accounts_receivable_credit", frm, filters);
|
||||
frm.events.filter_accounts("accounts_receivable_discounted", frm, filters);
|
||||
frm.events.filter_accounts("accounts_receivable_unpaid", frm, filters);
|
||||
},
|
||||
|
||||
refresh: (frm) => {
|
||||
frm.events.show_general_ledger(frm);
|
||||
|
||||
if(frm.doc.docstatus === 0) {
|
||||
if (frm.doc.docstatus === 0) {
|
||||
frm.add_custom_button(__('Get Invoices'), function() {
|
||||
frm.events.get_invoices(frm);
|
||||
});
|
||||
}
|
||||
|
||||
if(frm.doc.docstatus === 1 && frm.doc.status !== "Settled") {
|
||||
if(frm.doc.status == "Sanctioned") {
|
||||
if (frm.doc.docstatus === 1 && frm.doc.status !== "Settled") {
|
||||
if (frm.doc.status == "Sanctioned") {
|
||||
frm.add_custom_button(__('Disburse Loan'), function() {
|
||||
frm.events.create_disbursement_entry(frm);
|
||||
}).addClass("btn-primary");
|
||||
}
|
||||
if(frm.doc.status == "Disbursed") {
|
||||
if (frm.doc.status == "Disbursed") {
|
||||
frm.add_custom_button(__('Close Loan'), function() {
|
||||
frm.events.close_loan(frm);
|
||||
}).addClass("btn-primary");
|
||||
@ -64,7 +80,7 @@ frappe.ui.form.on('Invoice Discounting', {
|
||||
},
|
||||
|
||||
set_end_date: (frm) => {
|
||||
if(frm.doc.loan_start_date && frm.doc.loan_period) {
|
||||
if (frm.doc.loan_start_date && frm.doc.loan_period) {
|
||||
let end_date = frappe.datetime.add_days(frm.doc.loan_start_date, frm.doc.loan_period);
|
||||
frm.set_value("loan_end_date", end_date);
|
||||
}
|
||||
@ -132,6 +148,7 @@ frappe.ui.form.on('Invoice Discounting', {
|
||||
frm.doc.invoices = frm.doc.invoices.filter(row => row.sales_invoice);
|
||||
let row = frm.add_child("invoices");
|
||||
$.extend(row, v);
|
||||
frm.events.refresh_filters(frm);
|
||||
});
|
||||
refresh_field("invoices");
|
||||
}
|
||||
@ -190,8 +207,10 @@ frappe.ui.form.on('Invoice Discounting', {
|
||||
frappe.ui.form.on('Discounted Invoice', {
|
||||
sales_invoice: (frm) => {
|
||||
frm.events.calculate_total_amount(frm);
|
||||
frm.events.refresh_filters(frm);
|
||||
},
|
||||
invoices_remove: (frm) => {
|
||||
frm.events.calculate_total_amount(frm);
|
||||
frm.events.refresh_filters(frm);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -12,6 +12,7 @@ from erpnext.accounts.general_ledger import make_gl_entries
|
||||
class InvoiceDiscounting(AccountsController):
|
||||
def validate(self):
|
||||
self.validate_mandatory()
|
||||
self.validate_invoices()
|
||||
self.calculate_total_amount()
|
||||
self.set_status()
|
||||
self.set_end_date()
|
||||
@ -24,6 +25,15 @@ class InvoiceDiscounting(AccountsController):
|
||||
if self.docstatus == 1 and not (self.loan_start_date and self.loan_period):
|
||||
frappe.throw(_("Loan Start Date and Loan Period are mandatory to save the Invoice Discounting"))
|
||||
|
||||
def validate_invoices(self):
|
||||
discounted_invoices = [record.sales_invoice for record in
|
||||
frappe.get_all("Discounted Invoice",fields = ["sales_invoice"], filters= {"docstatus":1})]
|
||||
|
||||
for record in self.invoices:
|
||||
if record.sales_invoice in discounted_invoices:
|
||||
frappe.throw("Row({0}): {1} is already discounted in {2}"
|
||||
.format(record.idx, frappe.bold(record.sales_invoice), frappe.bold(record.parent)))
|
||||
|
||||
def calculate_total_amount(self):
|
||||
self.total_amount = sum([flt(d.outstanding_amount) for d in self.invoices])
|
||||
|
||||
@ -212,7 +222,8 @@ def get_invoices(filters):
|
||||
name as sales_invoice,
|
||||
customer,
|
||||
posting_date,
|
||||
outstanding_amount
|
||||
outstanding_amount,
|
||||
debit_to
|
||||
from `tabSales Invoice` si
|
||||
where
|
||||
docstatus = 1
|
||||
|
@ -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
|
||||
|
||||
|
@ -66,6 +66,7 @@ frappe.ui.form.on('Payment Order', {
|
||||
get_query_filters: {
|
||||
bank: frm.doc.bank,
|
||||
docstatus: 1,
|
||||
payment_type: ("!=", "Receive"),
|
||||
bank_account: frm.doc.company_bank_account,
|
||||
paid_from: frm.doc.account,
|
||||
payment_order_status: ["=", "Initiated"],
|
||||
|
@ -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), {
|
||||
@ -257,11 +257,8 @@ def reconcile_dr_cr_note(dr_cr_notes):
|
||||
voucher_type = ('Credit Note'
|
||||
if d.voucher_type == 'Sales Invoice' else 'Debit Note')
|
||||
|
||||
dr_or_cr = ('credit_in_account_currency'
|
||||
if d.reference_type == 'Sales Invoice' else 'debit_in_account_currency')
|
||||
|
||||
reconcile_dr_or_cr = ('debit_in_account_currency'
|
||||
if dr_or_cr == 'credit_in_account_currency' else 'credit_in_account_currency')
|
||||
if d.dr_or_cr == 'credit_in_account_currency' else 'credit_in_account_currency')
|
||||
|
||||
jv = frappe.get_doc({
|
||||
"doctype": "Journal Entry",
|
||||
@ -272,8 +269,7 @@ def reconcile_dr_cr_note(dr_cr_notes):
|
||||
'account': d.account,
|
||||
'party': d.party,
|
||||
'party_type': d.party_type,
|
||||
reconcile_dr_or_cr: (abs(d.allocated_amount)
|
||||
if abs(d.unadjusted_amount) > abs(d.allocated_amount) else abs(d.unadjusted_amount)),
|
||||
d.dr_or_cr: abs(d.allocated_amount),
|
||||
'reference_type': d.against_voucher_type,
|
||||
'reference_name': d.against_voucher
|
||||
},
|
||||
@ -281,7 +277,8 @@ def reconcile_dr_cr_note(dr_cr_notes):
|
||||
'account': d.account,
|
||||
'party': d.party,
|
||||
'party_type': d.party_type,
|
||||
dr_or_cr: abs(d.allocated_amount),
|
||||
reconcile_dr_or_cr: (abs(d.allocated_amount)
|
||||
if abs(d.unadjusted_amount) > abs(d.allocated_amount) else abs(d.unadjusted_amount)),
|
||||
'reference_type': d.voucher_type,
|
||||
'reference_name': d.voucher_no
|
||||
}
|
||||
|
@ -307,7 +307,7 @@ def get_item_tax_data():
|
||||
# example: {'Consulting Services': {'Excise 12 - TS': '12.000'}}
|
||||
|
||||
itemwise_tax = {}
|
||||
taxes = frappe.db.sql(""" select parent, tax_type, tax_rate from `tabItem Tax`""", as_dict=1)
|
||||
taxes = frappe.db.sql(""" select parent, tax_type, tax_rate from `tabItem Tax Template Detail`""", as_dict=1)
|
||||
|
||||
for tax in taxes:
|
||||
if tax.parent not in itemwise_tax:
|
||||
@ -432,7 +432,6 @@ def get_customer_id(doc, customer=None):
|
||||
|
||||
return cust_id
|
||||
|
||||
|
||||
def make_customer_and_address(customers):
|
||||
customers_list = []
|
||||
for customer, data in iteritems(customers):
|
||||
@ -449,7 +448,6 @@ def make_customer_and_address(customers):
|
||||
frappe.db.commit()
|
||||
return customers_list
|
||||
|
||||
|
||||
def add_customer(data):
|
||||
customer = data.get('full_name') or data.get('customer')
|
||||
if frappe.db.exists("Customer", customer.strip()):
|
||||
@ -466,21 +464,18 @@ def add_customer(data):
|
||||
frappe.db.commit()
|
||||
return customer_doc.name
|
||||
|
||||
|
||||
def get_territory(data):
|
||||
if data.get('territory'):
|
||||
return data.get('territory')
|
||||
|
||||
return frappe.db.get_single_value('Selling Settings','territory') or _('All Territories')
|
||||
|
||||
|
||||
def get_customer_group(data):
|
||||
if data.get('customer_group'):
|
||||
return data.get('customer_group')
|
||||
|
||||
return frappe.db.get_single_value('Selling Settings', 'customer_group') or frappe.db.get_value('Customer Group', {'is_group': 0}, 'name')
|
||||
|
||||
|
||||
def make_contact(args, customer):
|
||||
if args.get('email_id') or args.get('phone'):
|
||||
name = frappe.db.get_value('Dynamic Link',
|
||||
@ -506,7 +501,6 @@ def make_contact(args, customer):
|
||||
doc.flags.ignore_mandatory = True
|
||||
doc.save(ignore_permissions=True)
|
||||
|
||||
|
||||
def make_address(args, customer):
|
||||
if not args.get('address_line1'):
|
||||
return
|
||||
@ -521,7 +515,10 @@ def make_address(args, customer):
|
||||
address = frappe.get_doc('Address', name)
|
||||
else:
|
||||
address = frappe.new_doc('Address')
|
||||
address.country = frappe.get_cached_value('Company', args.get('company'), 'country')
|
||||
if args.get('company'):
|
||||
address.country = frappe.get_cached_value('Company',
|
||||
args.get('company'), 'country')
|
||||
|
||||
address.append('links', {
|
||||
'link_doctype': 'Customer',
|
||||
'link_name': customer
|
||||
@ -533,7 +530,6 @@ def make_address(args, customer):
|
||||
address.flags.ignore_mandatory = True
|
||||
address.save(ignore_permissions=True)
|
||||
|
||||
|
||||
def make_email_queue(email_queue):
|
||||
name_list = []
|
||||
for key, data in iteritems(email_queue):
|
||||
@ -550,7 +546,6 @@ def make_email_queue(email_queue):
|
||||
|
||||
return name_list
|
||||
|
||||
|
||||
def validate_item(doc):
|
||||
for item in doc.get('items'):
|
||||
if not frappe.db.exists('Item', item.get('item_code')):
|
||||
@ -569,7 +564,6 @@ def validate_item(doc):
|
||||
item_doc.save(ignore_permissions=True)
|
||||
frappe.db.commit()
|
||||
|
||||
|
||||
def submit_invoice(si_doc, name, doc, name_list):
|
||||
try:
|
||||
si_doc.insert()
|
||||
@ -585,7 +579,6 @@ def submit_invoice(si_doc, name, doc, name_list):
|
||||
|
||||
return name_list
|
||||
|
||||
|
||||
def save_invoice(doc, name, name_list):
|
||||
try:
|
||||
if not frappe.db.exists('Sales Invoice', {'offline_pos_name': name}):
|
||||
|
@ -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
|
||||
invoice_total = self.rounded_total or self.grand_total
|
||||
if total_amount_in_payments < invoice_total:
|
||||
frappe.throw(_("Total payments amount can't be greater than {}".format(-invoice_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."))
|
||||
@ -1499,4 +1510,4 @@ def create_invoice_discounting(source_name, target_doc=None):
|
||||
"outstanding_amount": invoice.outstanding_amount
|
||||
})
|
||||
|
||||
return invoice_discounting
|
||||
return invoice_discounting
|
||||
|
@ -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
|
||||
|
@ -1762,18 +1762,11 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
|
||||
this.si_docs = this.get_submitted_invoice() || [];
|
||||
this.email_queue_list = this.get_email_queue() || {};
|
||||
this.customers_list = this.get_customers_details() || {};
|
||||
if(this.customer_doc) {
|
||||
this.freeze = this.customer_doc.display
|
||||
}
|
||||
|
||||
freeze_screen = this.freeze_screen || false;
|
||||
|
||||
if ((this.si_docs.length || this.email_queue_list || this.customers_list) && !this.freeze) {
|
||||
this.freeze = true;
|
||||
|
||||
if (this.si_docs.length || this.email_queue_list || this.customers_list) {
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.sales_invoice.pos.make_invoice",
|
||||
freeze: freeze_screen,
|
||||
freeze: true,
|
||||
args: {
|
||||
doc_list: me.si_docs,
|
||||
email_queue_list: me.email_queue_list,
|
||||
|
@ -197,8 +197,10 @@ class ReceivablePayableReport(object):
|
||||
if self.filters.based_on_payment_terms and gl_entries_data:
|
||||
self.payment_term_map = self.get_payment_term_detail(voucher_nos)
|
||||
|
||||
self.gle_inclusion_map = {}
|
||||
for gle in gl_entries_data:
|
||||
if self.is_receivable_or_payable(gle, self.dr_or_cr, future_vouchers, return_entries):
|
||||
self.gle_inclusion_map[gle.name] = True
|
||||
outstanding_amount, credit_note_amount, payment_amount = self.get_outstanding_amount(
|
||||
gle,self.filters.report_date, self.dr_or_cr, return_entries)
|
||||
temp_outstanding_amt = outstanding_amount
|
||||
@ -409,7 +411,9 @@ class ReceivablePayableReport(object):
|
||||
for e in self.get_gl_entries_for(gle.party, gle.party_type, gle.voucher_type, gle.voucher_no):
|
||||
if getdate(e.posting_date) <= report_date \
|
||||
and (e.name!=gle.name or (e.voucher_no in return_entries and not return_entries.get(e.voucher_no))):
|
||||
|
||||
if e.name!=gle.name and self.gle_inclusion_map.get(e.name):
|
||||
continue
|
||||
self.gle_inclusion_map[e.name] = True
|
||||
amount = flt(e.get(reverse_dr_or_cr), self.currency_precision) - flt(e.get(dr_or_cr), self.currency_precision)
|
||||
if e.voucher_no not in return_entries:
|
||||
payment_amount += amount
|
||||
|
@ -119,19 +119,11 @@ def get_gl_entries(filters):
|
||||
select_fields = """, debit, credit, debit_in_account_currency,
|
||||
credit_in_account_currency """
|
||||
|
||||
group_by_statement = ''
|
||||
order_by_statement = "order by posting_date, account"
|
||||
|
||||
if filters.get("group_by") == _("Group by Voucher"):
|
||||
order_by_statement = "order by posting_date, voucher_type, voucher_no"
|
||||
|
||||
if filters.get("group_by") == _("Group by Voucher (Consolidated)"):
|
||||
group_by_statement = "group by voucher_type, voucher_no, account, cost_center"
|
||||
|
||||
select_fields = """, sum(debit) as debit, sum(credit) as credit,
|
||||
sum(debit_in_account_currency) as debit_in_account_currency,
|
||||
sum(credit_in_account_currency) as credit_in_account_currency"""
|
||||
|
||||
if filters.get("include_default_book_entries"):
|
||||
filters['company_fb'] = frappe.db.get_value("Company",
|
||||
filters.get("company"), 'default_finance_book')
|
||||
@ -144,11 +136,10 @@ def get_gl_entries(filters):
|
||||
against_voucher_type, against_voucher, account_currency,
|
||||
remarks, against, is_opening {select_fields}
|
||||
from `tabGL Entry`
|
||||
where company=%(company)s {conditions} {group_by_statement}
|
||||
where company=%(company)s {conditions}
|
||||
{order_by_statement}
|
||||
""".format(
|
||||
select_fields=select_fields, conditions=get_conditions(filters),
|
||||
group_by_statement=group_by_statement,
|
||||
order_by_statement=order_by_statement
|
||||
),
|
||||
filters, as_dict=1)
|
||||
@ -185,7 +176,8 @@ def get_conditions(filters):
|
||||
if not (filters.get("account") or filters.get("party") or
|
||||
filters.get("group_by") in ["Group by Account", "Group by Party"]):
|
||||
conditions.append("posting_date >=%(from_date)s")
|
||||
conditions.append("posting_date <=%(to_date)s")
|
||||
|
||||
conditions.append("(posting_date <=%(to_date)s or is_opening = 'Yes')")
|
||||
|
||||
if filters.get("project"):
|
||||
conditions.append("project in %(project)s")
|
||||
@ -286,6 +278,7 @@ def initialize_gle_map(gl_entries, filters):
|
||||
def get_accountwise_gle(filters, gl_entries, gle_map):
|
||||
totals = get_totals_dict()
|
||||
entries = []
|
||||
consolidated_gle = OrderedDict()
|
||||
group_by = group_by_field(filters.get('group_by'))
|
||||
|
||||
def update_value_in_dict(data, key, gle):
|
||||
@ -310,12 +303,20 @@ def get_accountwise_gle(filters, gl_entries, gle_map):
|
||||
update_value_in_dict(totals, 'total', gle)
|
||||
if filters.get("group_by") != _('Group by Voucher (Consolidated)'):
|
||||
gle_map[gle.get(group_by)].entries.append(gle)
|
||||
else:
|
||||
entries.append(gle)
|
||||
elif filters.get("group_by") == _('Group by Voucher (Consolidated)'):
|
||||
key = (gle.get("voucher_type"), gle.get("voucher_no"),
|
||||
gle.get("account"), gle.get("cost_center"))
|
||||
if key not in consolidated_gle:
|
||||
consolidated_gle.setdefault(key, gle)
|
||||
else:
|
||||
update_value_in_dict(consolidated_gle, key, gle)
|
||||
|
||||
update_value_in_dict(gle_map[gle.get(group_by)].totals, 'closing', gle)
|
||||
update_value_in_dict(totals, 'closing', gle)
|
||||
|
||||
for key, value in consolidated_gle.items():
|
||||
entries.append(value)
|
||||
|
||||
return totals, entries
|
||||
|
||||
def get_result_as_list(data, filters):
|
||||
|
@ -303,14 +303,17 @@ frappe.ui.form.on('Asset', {
|
||||
},
|
||||
|
||||
set_depreciation_rate: function(frm, row) {
|
||||
if (row.total_number_of_depreciations && row.frequency_of_depreciation) {
|
||||
if (row.total_number_of_depreciations && row.frequency_of_depreciation
|
||||
&& row.expected_value_after_useful_life) {
|
||||
frappe.call({
|
||||
method: "get_depreciation_rate",
|
||||
doc: frm.doc,
|
||||
args: row,
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frappe.model.set_value(row.doctype, row.name, "rate_of_depreciation", r.message);
|
||||
frappe.flags.dont_change_rate = true;
|
||||
frappe.model.set_value(row.doctype, row.name,
|
||||
"rate_of_depreciation", flt(r.message, precision("rate_of_depreciation", row)));
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -338,6 +341,14 @@ frappe.ui.form.on('Asset Finance Book', {
|
||||
total_number_of_depreciations: function(frm, cdt, cdn) {
|
||||
const row = locals[cdt][cdn];
|
||||
frm.events.set_depreciation_rate(frm, row);
|
||||
},
|
||||
|
||||
rate_of_depreciation: function(frm, cdt, cdn) {
|
||||
if(!frappe.flags.dont_change_rate) {
|
||||
frappe.model.set_value(cdt, cdn, "expected_value_after_useful_life", 0);
|
||||
}
|
||||
|
||||
frappe.flags.dont_change_rate = false;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -6,7 +6,7 @@ from __future__ import unicode_literals
|
||||
import frappe, erpnext, math, json
|
||||
from frappe import _
|
||||
from six import string_types
|
||||
from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff
|
||||
from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff, add_days
|
||||
from frappe.model.document import Document
|
||||
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
||||
from erpnext.assets.doctype.asset.depreciation \
|
||||
@ -101,97 +101,88 @@ class Asset(AccountsController):
|
||||
|
||||
def set_depreciation_rate(self):
|
||||
for d in self.get("finance_books"):
|
||||
d.rate_of_depreciation = self.get_depreciation_rate(d, on_validate=True)
|
||||
d.rate_of_depreciation = flt(self.get_depreciation_rate(d, on_validate=True),
|
||||
d.precision("rate_of_depreciation"))
|
||||
|
||||
def make_depreciation_schedule(self):
|
||||
depreciation_method = [d.depreciation_method for d in self.finance_books]
|
||||
|
||||
if 'Manual' not in depreciation_method:
|
||||
if 'Manual' not in [d.depreciation_method for d in self.finance_books]:
|
||||
self.schedules = []
|
||||
|
||||
if not self.get("schedules") and self.available_for_use_date:
|
||||
total_depreciations = sum([d.total_number_of_depreciations for d in self.get('finance_books')])
|
||||
if self.get("schedules") or not self.available_for_use_date:
|
||||
return
|
||||
|
||||
for d in self.get('finance_books'):
|
||||
self.validate_asset_finance_books(d)
|
||||
for d in self.get('finance_books'):
|
||||
self.validate_asset_finance_books(d)
|
||||
|
||||
value_after_depreciation = (flt(self.gross_purchase_amount) -
|
||||
flt(self.opening_accumulated_depreciation))
|
||||
value_after_depreciation = (flt(self.gross_purchase_amount) -
|
||||
flt(self.opening_accumulated_depreciation))
|
||||
|
||||
d.value_after_depreciation = value_after_depreciation
|
||||
d.value_after_depreciation = value_after_depreciation
|
||||
|
||||
no_of_depreciations = cint(d.total_number_of_depreciations - 1) - cint(self.number_of_depreciations_booked)
|
||||
end_date = add_months(d.depreciation_start_date,
|
||||
no_of_depreciations * cint(d.frequency_of_depreciation))
|
||||
number_of_pending_depreciations = cint(d.total_number_of_depreciations) - \
|
||||
cint(self.number_of_depreciations_booked)
|
||||
|
||||
total_days = date_diff(end_date, self.available_for_use_date)
|
||||
rate_per_day = (value_after_depreciation - d.get("expected_value_after_useful_life")) / total_days
|
||||
has_pro_rata = self.check_is_pro_rata(d)
|
||||
|
||||
number_of_pending_depreciations = cint(d.total_number_of_depreciations) - \
|
||||
cint(self.number_of_depreciations_booked)
|
||||
if has_pro_rata:
|
||||
number_of_pending_depreciations += 1
|
||||
|
||||
from_date = self.available_for_use_date
|
||||
if number_of_pending_depreciations:
|
||||
next_depr_date = getdate(add_months(self.available_for_use_date,
|
||||
number_of_pending_depreciations * 12))
|
||||
if (cint(frappe.db.get_value("Asset Settings", None, "schedule_based_on_fiscal_year")) == 1
|
||||
and getdate(d.depreciation_start_date) < next_depr_date):
|
||||
skip_row = False
|
||||
for n in range(number_of_pending_depreciations):
|
||||
# If depreciation is already completed (for double declining balance)
|
||||
if skip_row: continue
|
||||
|
||||
number_of_pending_depreciations += 1
|
||||
for n in range(number_of_pending_depreciations):
|
||||
if n == list(range(number_of_pending_depreciations))[-1]:
|
||||
schedule_date = add_months(self.available_for_use_date, n * 12)
|
||||
previous_scheduled_date = add_months(d.depreciation_start_date, (n-1) * 12)
|
||||
depreciation_amount = \
|
||||
self.get_depreciation_amount_prorata_temporis(value_after_depreciation,
|
||||
d, previous_scheduled_date, schedule_date)
|
||||
depreciation_amount = self.get_depreciation_amount(value_after_depreciation,
|
||||
d.total_number_of_depreciations, d)
|
||||
|
||||
elif n == list(range(number_of_pending_depreciations))[0]:
|
||||
schedule_date = d.depreciation_start_date
|
||||
depreciation_amount = \
|
||||
self.get_depreciation_amount_prorata_temporis(value_after_depreciation,
|
||||
d, self.available_for_use_date, schedule_date)
|
||||
if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
|
||||
schedule_date = add_months(d.depreciation_start_date,
|
||||
n * cint(d.frequency_of_depreciation))
|
||||
|
||||
else:
|
||||
schedule_date = add_months(d.depreciation_start_date, n * 12)
|
||||
depreciation_amount = \
|
||||
self.get_depreciation_amount_prorata_temporis(value_after_depreciation, d)
|
||||
# For first row
|
||||
if has_pro_rata and n==0:
|
||||
depreciation_amount, days = get_pro_rata_amt(d, depreciation_amount,
|
||||
self.available_for_use_date, d.depreciation_start_date)
|
||||
# For last row
|
||||
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
|
||||
to_date = add_months(self.available_for_use_date,
|
||||
n * cint(d.frequency_of_depreciation))
|
||||
|
||||
if value_after_depreciation != 0:
|
||||
value_after_depreciation -= flt(depreciation_amount)
|
||||
depreciation_amount, days = get_pro_rata_amt(d,
|
||||
depreciation_amount, schedule_date, to_date)
|
||||
|
||||
self.append("schedules", {
|
||||
"schedule_date": schedule_date,
|
||||
"depreciation_amount": depreciation_amount,
|
||||
"depreciation_method": d.depreciation_method,
|
||||
"finance_book": d.finance_book,
|
||||
"finance_book_id": d.idx
|
||||
})
|
||||
else:
|
||||
for n in range(number_of_pending_depreciations):
|
||||
schedule_date = add_months(d.depreciation_start_date,
|
||||
n * cint(d.frequency_of_depreciation))
|
||||
schedule_date = add_days(schedule_date, days)
|
||||
|
||||
if d.depreciation_method in ("Straight Line", "Manual"):
|
||||
days = date_diff(schedule_date, from_date)
|
||||
if n == 0: days += 1
|
||||
if not depreciation_amount: continue
|
||||
value_after_depreciation -= flt(depreciation_amount,
|
||||
self.precision("gross_purchase_amount"))
|
||||
|
||||
depreciation_amount = days * rate_per_day
|
||||
from_date = schedule_date
|
||||
else:
|
||||
depreciation_amount = self.get_depreciation_amount(value_after_depreciation,
|
||||
d.total_number_of_depreciations, d)
|
||||
# Adjust depreciation amount in the last period based on the expected value after useful life
|
||||
if d.expected_value_after_useful_life and ((n == cint(number_of_pending_depreciations) - 1
|
||||
and value_after_depreciation != d.expected_value_after_useful_life)
|
||||
or value_after_depreciation < d.expected_value_after_useful_life):
|
||||
depreciation_amount += (value_after_depreciation - d.expected_value_after_useful_life)
|
||||
skip_row = True
|
||||
|
||||
if depreciation_amount:
|
||||
value_after_depreciation -= flt(depreciation_amount)
|
||||
if depreciation_amount > 0:
|
||||
self.append("schedules", {
|
||||
"schedule_date": schedule_date,
|
||||
"depreciation_amount": depreciation_amount,
|
||||
"depreciation_method": d.depreciation_method,
|
||||
"finance_book": d.finance_book,
|
||||
"finance_book_id": d.idx
|
||||
})
|
||||
|
||||
self.append("schedules", {
|
||||
"schedule_date": schedule_date,
|
||||
"depreciation_amount": depreciation_amount,
|
||||
"depreciation_method": d.depreciation_method,
|
||||
"finance_book": d.finance_book,
|
||||
"finance_book_id": d.idx
|
||||
})
|
||||
def check_is_pro_rata(self, row):
|
||||
has_pro_rata = False
|
||||
|
||||
days = date_diff(row.depreciation_start_date, self.available_for_use_date) + 1
|
||||
total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation)
|
||||
|
||||
if days < total_days:
|
||||
has_pro_rata = True
|
||||
|
||||
return has_pro_rata
|
||||
|
||||
def validate_asset_finance_books(self, row):
|
||||
if flt(row.expected_value_after_useful_life) >= flt(self.gross_purchase_amount):
|
||||
@ -261,31 +252,20 @@ class Asset(AccountsController):
|
||||
return flt(self.get('finance_books')[cint(idx)-1].value_after_depreciation)
|
||||
|
||||
def get_depreciation_amount(self, depreciable_value, total_number_of_depreciations, row):
|
||||
if row.depreciation_method in ["Straight Line", "Manual"]:
|
||||
amt = (flt(self.gross_purchase_amount) - flt(row.expected_value_after_useful_life) -
|
||||
flt(self.opening_accumulated_depreciation))
|
||||
|
||||
depreciation_amount = amt * row.rate_of_depreciation
|
||||
else:
|
||||
depreciation_amount = flt(depreciable_value) * (flt(row.rate_of_depreciation) / 100)
|
||||
value_after_depreciation = flt(depreciable_value) - depreciation_amount
|
||||
if value_after_depreciation < flt(row.expected_value_after_useful_life):
|
||||
depreciation_amount = flt(depreciable_value) - flt(row.expected_value_after_useful_life)
|
||||
|
||||
return depreciation_amount
|
||||
|
||||
def get_depreciation_amount_prorata_temporis(self, depreciable_value, row, start_date=None, end_date=None):
|
||||
if start_date and end_date:
|
||||
prorata_temporis = min(abs(flt(date_diff(str(end_date), str(start_date)))) / flt(frappe.db.get_value("Asset Settings", None, "number_of_days_in_fiscal_year")), 1)
|
||||
else:
|
||||
prorata_temporis = 1
|
||||
precision = self.precision("gross_purchase_amount")
|
||||
|
||||
if row.depreciation_method in ("Straight Line", "Manual"):
|
||||
depreciation_left = (cint(row.total_number_of_depreciations) - cint(self.number_of_depreciations_booked))
|
||||
|
||||
if not depreciation_left:
|
||||
frappe.msgprint(_("All the depreciations has been booked"))
|
||||
depreciation_amount = flt(row.expected_value_after_useful_life)
|
||||
return depreciation_amount
|
||||
|
||||
depreciation_amount = (flt(row.value_after_depreciation) -
|
||||
flt(row.expected_value_after_useful_life)) / (cint(row.total_number_of_depreciations) -
|
||||
cint(self.number_of_depreciations_booked)) * prorata_temporis
|
||||
flt(row.expected_value_after_useful_life)) / depreciation_left
|
||||
else:
|
||||
depreciation_amount = self.get_depreciation_amount(depreciable_value, row.total_number_of_depreciations, row)
|
||||
depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100), precision)
|
||||
|
||||
return depreciation_amount
|
||||
|
||||
@ -301,9 +281,12 @@ class Asset(AccountsController):
|
||||
flt(accumulated_depreciation_after_full_schedule),
|
||||
self.precision('gross_purchase_amount'))
|
||||
|
||||
if row.expected_value_after_useful_life < asset_value_after_full_schedule:
|
||||
if (row.expected_value_after_useful_life and
|
||||
row.expected_value_after_useful_life < asset_value_after_full_schedule):
|
||||
frappe.throw(_("Depreciation Row {0}: Expected value after useful life must be greater than or equal to {1}")
|
||||
.format(row.idx, asset_value_after_full_schedule))
|
||||
elif not row.expected_value_after_useful_life:
|
||||
row.expected_value_after_useful_life = asset_value_after_full_schedule
|
||||
|
||||
def validate_cancellation(self):
|
||||
if self.status not in ("Submitted", "Partially Depreciated", "Fully Depreciated"):
|
||||
@ -412,15 +395,7 @@ class Asset(AccountsController):
|
||||
if isinstance(args, string_types):
|
||||
args = json.loads(args)
|
||||
|
||||
number_of_depreciations_booked = 0
|
||||
if self.is_existing_asset:
|
||||
number_of_depreciations_booked = self.number_of_depreciations_booked
|
||||
|
||||
float_precision = cint(frappe.db.get_default("float_precision")) or 2
|
||||
tot_no_of_depreciation = flt(args.get("total_number_of_depreciations")) - flt(number_of_depreciations_booked)
|
||||
|
||||
if args.get("depreciation_method") in ["Straight Line", "Manual"]:
|
||||
return 1.0 / tot_no_of_depreciation
|
||||
|
||||
if args.get("depreciation_method") == 'Double Declining Balance':
|
||||
return 200.0 / args.get("total_number_of_depreciations")
|
||||
@ -600,3 +575,15 @@ def make_journal_entry(asset_name):
|
||||
|
||||
def is_cwip_accounting_disabled():
|
||||
return cint(frappe.db.get_single_value("Asset Settings", "disable_cwip_accounting"))
|
||||
|
||||
def get_pro_rata_amt(row, depreciation_amount, from_date, to_date):
|
||||
days = date_diff(to_date, from_date)
|
||||
total_days = get_total_days(to_date, row.frequency_of_depreciation)
|
||||
|
||||
return (depreciation_amount * flt(days)) / flt(total_days), days
|
||||
|
||||
def get_total_days(date, frequency):
|
||||
period_start_date = add_months(date,
|
||||
cint(frequency) * -1)
|
||||
|
||||
return date_diff(date, period_start_date)
|
@ -88,23 +88,23 @@ class TestAsset(unittest.TestCase):
|
||||
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
|
||||
asset = frappe.get_doc('Asset', asset_name)
|
||||
asset.calculate_depreciation = 1
|
||||
asset.available_for_use_date = '2020-06-06'
|
||||
asset.purchase_date = '2020-06-06'
|
||||
asset.available_for_use_date = '2030-01-01'
|
||||
asset.purchase_date = '2030-01-01'
|
||||
|
||||
asset.append("finance_books", {
|
||||
"expected_value_after_useful_life": 10000,
|
||||
"next_depreciation_date": "2020-12-31",
|
||||
"depreciation_method": "Straight Line",
|
||||
"total_number_of_depreciations": 3,
|
||||
"frequency_of_depreciation": 10,
|
||||
"depreciation_start_date": "2020-06-06"
|
||||
"frequency_of_depreciation": 12,
|
||||
"depreciation_start_date": "2030-12-31"
|
||||
})
|
||||
asset.save()
|
||||
|
||||
self.assertEqual(asset.status, "Draft")
|
||||
expected_schedules = [
|
||||
["2020-06-06", 147.54, 147.54],
|
||||
["2021-04-06", 44852.46, 45000.0],
|
||||
["2022-02-06", 45000.0, 90000.00]
|
||||
["2030-12-31", 30000.00, 30000.00],
|
||||
["2031-12-31", 30000.00, 60000.00],
|
||||
["2032-12-31", 30000.00, 90000.00]
|
||||
]
|
||||
|
||||
schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
|
||||
@ -118,20 +118,21 @@ class TestAsset(unittest.TestCase):
|
||||
asset.calculate_depreciation = 1
|
||||
asset.number_of_depreciations_booked = 1
|
||||
asset.opening_accumulated_depreciation = 40000
|
||||
asset.available_for_use_date = "2030-06-06"
|
||||
asset.append("finance_books", {
|
||||
"expected_value_after_useful_life": 10000,
|
||||
"next_depreciation_date": "2020-12-31",
|
||||
"depreciation_method": "Straight Line",
|
||||
"total_number_of_depreciations": 3,
|
||||
"frequency_of_depreciation": 10,
|
||||
"depreciation_start_date": "2020-06-06"
|
||||
"frequency_of_depreciation": 12,
|
||||
"depreciation_start_date": "2030-12-31"
|
||||
})
|
||||
asset.insert()
|
||||
self.assertEqual(asset.status, "Draft")
|
||||
asset.save()
|
||||
expected_schedules = [
|
||||
["2020-06-06", 164.47, 40164.47],
|
||||
["2021-04-06", 49835.53, 90000.00]
|
||||
["2030-12-31", 14246.58, 54246.58],
|
||||
["2031-12-31", 25000.00, 79246.58],
|
||||
["2032-06-06", 10753.42, 90000.00]
|
||||
]
|
||||
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
|
||||
for d in asset.get("schedules")]
|
||||
@ -145,24 +146,23 @@ class TestAsset(unittest.TestCase):
|
||||
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
|
||||
asset = frappe.get_doc('Asset', asset_name)
|
||||
asset.calculate_depreciation = 1
|
||||
asset.available_for_use_date = '2020-06-06'
|
||||
asset.purchase_date = '2020-06-06'
|
||||
asset.available_for_use_date = '2030-01-01'
|
||||
asset.purchase_date = '2030-01-01'
|
||||
asset.append("finance_books", {
|
||||
"expected_value_after_useful_life": 10000,
|
||||
"next_depreciation_date": "2020-12-31",
|
||||
"depreciation_method": "Double Declining Balance",
|
||||
"total_number_of_depreciations": 3,
|
||||
"frequency_of_depreciation": 10,
|
||||
"depreciation_start_date": "2020-06-06"
|
||||
"frequency_of_depreciation": 12,
|
||||
"depreciation_start_date": '2030-12-31'
|
||||
})
|
||||
asset.insert()
|
||||
self.assertEqual(asset.status, "Draft")
|
||||
asset.save()
|
||||
|
||||
expected_schedules = [
|
||||
["2020-06-06", 66666.67, 66666.67],
|
||||
["2021-04-06", 22222.22, 88888.89],
|
||||
["2022-02-06", 1111.11, 90000.0]
|
||||
['2030-12-31', 66667.00, 66667.00],
|
||||
['2031-12-31', 22222.11, 88889.11],
|
||||
['2032-12-31', 1110.89, 90000.0]
|
||||
]
|
||||
|
||||
schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
|
||||
@ -177,23 +177,21 @@ class TestAsset(unittest.TestCase):
|
||||
asset.is_existing_asset = 1
|
||||
asset.number_of_depreciations_booked = 1
|
||||
asset.opening_accumulated_depreciation = 50000
|
||||
asset.available_for_use_date = '2030-01-01'
|
||||
asset.purchase_date = '2029-11-30'
|
||||
asset.append("finance_books", {
|
||||
"expected_value_after_useful_life": 10000,
|
||||
"next_depreciation_date": "2020-12-31",
|
||||
"depreciation_method": "Double Declining Balance",
|
||||
"total_number_of_depreciations": 3,
|
||||
"frequency_of_depreciation": 10,
|
||||
"depreciation_start_date": "2020-06-06"
|
||||
"frequency_of_depreciation": 12,
|
||||
"depreciation_start_date": "2030-12-31"
|
||||
})
|
||||
asset.insert()
|
||||
self.assertEqual(asset.status, "Draft")
|
||||
asset.save()
|
||||
|
||||
asset.save()
|
||||
|
||||
expected_schedules = [
|
||||
["2020-06-06", 33333.33, 83333.33],
|
||||
["2021-04-06", 6666.67, 90000.0]
|
||||
["2030-12-31", 33333.50, 83333.50],
|
||||
["2031-12-31", 6666.50, 90000.0]
|
||||
]
|
||||
|
||||
schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
|
||||
@ -209,25 +207,25 @@ class TestAsset(unittest.TestCase):
|
||||
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
|
||||
asset = frappe.get_doc('Asset', asset_name)
|
||||
asset.calculate_depreciation = 1
|
||||
asset.purchase_date = '2020-01-30'
|
||||
asset.purchase_date = '2030-01-30'
|
||||
asset.is_existing_asset = 0
|
||||
asset.available_for_use_date = "2020-01-30"
|
||||
asset.available_for_use_date = "2030-01-30"
|
||||
asset.append("finance_books", {
|
||||
"expected_value_after_useful_life": 10000,
|
||||
"depreciation_method": "Straight Line",
|
||||
"total_number_of_depreciations": 3,
|
||||
"frequency_of_depreciation": 10,
|
||||
"depreciation_start_date": "2020-12-31"
|
||||
"frequency_of_depreciation": 12,
|
||||
"depreciation_start_date": "2030-12-31"
|
||||
})
|
||||
|
||||
asset.insert()
|
||||
asset.save()
|
||||
|
||||
expected_schedules = [
|
||||
["2020-12-31", 28000.0, 28000.0],
|
||||
["2021-12-31", 30000.0, 58000.0],
|
||||
["2022-12-31", 30000.0, 88000.0],
|
||||
["2023-01-30", 2000.0, 90000.0]
|
||||
["2030-12-31", 27534.25, 27534.25],
|
||||
["2031-12-31", 30000.0, 57534.25],
|
||||
["2032-12-31", 30000.0, 87534.25],
|
||||
["2033-01-30", 2465.75, 90000.0]
|
||||
]
|
||||
|
||||
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
|
||||
@ -266,8 +264,8 @@ class TestAsset(unittest.TestCase):
|
||||
self.assertEqual(asset.get("schedules")[0].journal_entry[:4], "DEPR")
|
||||
|
||||
expected_gle = (
|
||||
("_Test Accumulated Depreciations - _TC", 0.0, 32129.24),
|
||||
("_Test Depreciations - _TC", 32129.24, 0.0)
|
||||
("_Test Accumulated Depreciations - _TC", 0.0, 30000.0),
|
||||
("_Test Depreciations - _TC", 30000.0, 0.0)
|
||||
)
|
||||
|
||||
gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
|
||||
@ -277,15 +275,15 @@ class TestAsset(unittest.TestCase):
|
||||
self.assertEqual(gle, expected_gle)
|
||||
self.assertEqual(asset.get("value_after_depreciation"), 0)
|
||||
|
||||
def test_depreciation_entry_for_wdv(self):
|
||||
def test_depreciation_entry_for_wdv_without_pro_rata(self):
|
||||
pr = make_purchase_receipt(item_code="Macbook Pro",
|
||||
qty=1, rate=8000.0, location="Test Location")
|
||||
|
||||
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
|
||||
asset = frappe.get_doc('Asset', asset_name)
|
||||
asset.calculate_depreciation = 1
|
||||
asset.available_for_use_date = '2030-06-06'
|
||||
asset.purchase_date = '2030-06-06'
|
||||
asset.available_for_use_date = '2030-01-01'
|
||||
asset.purchase_date = '2030-01-01'
|
||||
asset.append("finance_books", {
|
||||
"expected_value_after_useful_life": 1000,
|
||||
"depreciation_method": "Written Down Value",
|
||||
@ -298,9 +296,41 @@ class TestAsset(unittest.TestCase):
|
||||
self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
|
||||
|
||||
expected_schedules = [
|
||||
["2030-12-31", 4000.0, 4000.0],
|
||||
["2031-12-31", 2000.0, 6000.0],
|
||||
["2032-12-31", 1000.0, 7000.0],
|
||||
["2030-12-31", 4000.00, 4000.00],
|
||||
["2031-12-31", 2000.00, 6000.00],
|
||||
["2032-12-31", 1000.00, 7000.0],
|
||||
]
|
||||
|
||||
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
|
||||
for d in asset.get("schedules")]
|
||||
|
||||
self.assertEqual(schedules, expected_schedules)
|
||||
|
||||
def test_pro_rata_depreciation_entry_for_wdv(self):
|
||||
pr = make_purchase_receipt(item_code="Macbook Pro",
|
||||
qty=1, rate=8000.0, location="Test Location")
|
||||
|
||||
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
|
||||
asset = frappe.get_doc('Asset', asset_name)
|
||||
asset.calculate_depreciation = 1
|
||||
asset.available_for_use_date = '2030-06-06'
|
||||
asset.purchase_date = '2030-01-01'
|
||||
asset.append("finance_books", {
|
||||
"expected_value_after_useful_life": 1000,
|
||||
"depreciation_method": "Written Down Value",
|
||||
"total_number_of_depreciations": 3,
|
||||
"frequency_of_depreciation": 12,
|
||||
"depreciation_start_date": "2030-12-31"
|
||||
})
|
||||
asset.save(ignore_permissions=True)
|
||||
|
||||
self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
|
||||
|
||||
expected_schedules = [
|
||||
["2030-12-31", 2279.45, 2279.45],
|
||||
["2031-12-31", 2860.28, 5139.73],
|
||||
["2032-12-31", 1430.14, 6569.87],
|
||||
["2033-06-06", 430.13, 7000.0],
|
||||
]
|
||||
|
||||
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
|
||||
@ -346,18 +376,19 @@ class TestAsset(unittest.TestCase):
|
||||
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
|
||||
asset = frappe.get_doc('Asset', asset_name)
|
||||
asset.calculate_depreciation = 1
|
||||
asset.available_for_use_date = '2020-06-06'
|
||||
asset.purchase_date = '2020-06-06'
|
||||
asset.available_for_use_date = nowdate()
|
||||
asset.purchase_date = nowdate()
|
||||
asset.append("finance_books", {
|
||||
"expected_value_after_useful_life": 10000,
|
||||
"depreciation_method": "Straight Line",
|
||||
"total_number_of_depreciations": 3,
|
||||
"frequency_of_depreciation": 10,
|
||||
"depreciation_start_date": "2020-06-06"
|
||||
"depreciation_start_date": nowdate()
|
||||
})
|
||||
asset.insert()
|
||||
asset.submit()
|
||||
post_depreciation_entries(date="2021-01-01")
|
||||
|
||||
post_depreciation_entries(date=add_months(nowdate(), 10))
|
||||
|
||||
scrap_asset(asset.name)
|
||||
|
||||
@ -366,9 +397,9 @@ class TestAsset(unittest.TestCase):
|
||||
self.assertTrue(asset.journal_entry_for_scrap)
|
||||
|
||||
expected_gle = (
|
||||
("_Test Accumulated Depreciations - _TC", 147.54, 0.0),
|
||||
("_Test Accumulated Depreciations - _TC", 30000.0, 0.0),
|
||||
("_Test Fixed Asset - _TC", 0.0, 100000.0),
|
||||
("_Test Gain/Loss on Asset Disposal - _TC", 99852.46, 0.0)
|
||||
("_Test Gain/Loss on Asset Disposal - _TC", 70000.0, 0.0)
|
||||
)
|
||||
|
||||
gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
|
||||
@ -412,9 +443,9 @@ class TestAsset(unittest.TestCase):
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
|
||||
|
||||
expected_gle = (
|
||||
("_Test Accumulated Depreciations - _TC", 23051.47, 0.0),
|
||||
("_Test Accumulated Depreciations - _TC", 20392.16, 0.0),
|
||||
("_Test Fixed Asset - _TC", 0.0, 100000.0),
|
||||
("_Test Gain/Loss on Asset Disposal - _TC", 51948.53, 0.0),
|
||||
("_Test Gain/Loss on Asset Disposal - _TC", 54607.84, 0.0),
|
||||
("Debtors - _TC", 25000.0, 0.0)
|
||||
)
|
||||
|
||||
|
@ -46,75 +46,6 @@
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "schedule_based_on_fiscal_year",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Calculate Prorated Depreciation Schedule Based on Fiscal Year",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "360",
|
||||
"depends_on": "eval:doc.schedule_based_on_fiscal_year",
|
||||
"description": "This value is used for pro-rata temporis calculation",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "number_of_days_in_fiscal_year",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Number of Days in Fiscal Year",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
@ -159,7 +90,7 @@
|
||||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-03-08 10:44:41.924547",
|
||||
"modified": "2019-05-26 18:31:19.930563",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Settings",
|
||||
|
@ -484,7 +484,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)
|
@ -6,15 +6,13 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from erpnext.crm.doctype.utils import get_scheduled_employees_for_popup
|
||||
from erpnext.crm.doctype.utils import get_scheduled_employees_for_popup, strip_number
|
||||
from frappe.contacts.doctype.contact.contact import get_contact_with_phone_number
|
||||
from erpnext.crm.doctype.lead.lead import get_lead_with_phone_number
|
||||
|
||||
class CallLog(Document):
|
||||
def before_insert(self):
|
||||
# strip 0 from the start of the number for proper number comparisions
|
||||
# eg. 07888383332 should match with 7888383332
|
||||
number = self.get('from').lstrip('0')
|
||||
number = strip_number(self.get('from'))
|
||||
self.contact = get_contact_with_phone_number(number)
|
||||
self.lead = get_lead_with_phone_number(number)
|
||||
|
||||
@ -48,13 +46,14 @@ def add_call_summary(call_log, summary):
|
||||
doc.add_comment('Comment', frappe.bold(_('Call Summary')) + '<br><br>' + summary)
|
||||
|
||||
def get_employees_with_number(number):
|
||||
number = strip_number(number)
|
||||
if not number: return []
|
||||
|
||||
employee_emails = frappe.cache().hget('employees_with_number', number)
|
||||
if employee_emails: return employee_emails
|
||||
|
||||
employees = frappe.get_all('Employee', filters={
|
||||
'cell_number': ['like', '%{}'.format(number.lstrip('0'))],
|
||||
'cell_number': ['like', '%{}%'.format(number)],
|
||||
'user_id': ['!=', '']
|
||||
}, fields=['user_id'])
|
||||
|
||||
@ -64,23 +63,29 @@ def get_employees_with_number(number):
|
||||
return employee
|
||||
|
||||
def set_caller_information(doc, state):
|
||||
'''Called from hoooks on creation of Lead or Contact'''
|
||||
'''Called from hooks on creation of Lead or Contact'''
|
||||
if doc.doctype not in ['Lead', 'Contact']: return
|
||||
|
||||
numbers = [doc.get('phone'), doc.get('mobile_no')]
|
||||
for_doc = doc.doctype.lower()
|
||||
# contact for Contact and lead for Lead
|
||||
fieldname = doc.doctype.lower()
|
||||
|
||||
# contact_name or lead_name
|
||||
display_name_field = '{}_name'.format(fieldname)
|
||||
|
||||
for number in numbers:
|
||||
number = strip_number(number)
|
||||
if not number: continue
|
||||
print(number)
|
||||
|
||||
filters = frappe._dict({
|
||||
'from': ['like', '%{}'.format(number.lstrip('0'))],
|
||||
for_doc: ''
|
||||
'from': ['like', '%{}'.format(number)],
|
||||
fieldname: ''
|
||||
})
|
||||
|
||||
logs = frappe.get_all('Call Log', filters=filters)
|
||||
|
||||
for log in logs:
|
||||
call_log = frappe.get_doc('Call Log', log.name)
|
||||
call_log.set(for_doc, doc.name)
|
||||
call_log.save(ignore_permissions=True)
|
||||
frappe.db.set_value('Call Log', log.name, {
|
||||
fieldname: doc.name,
|
||||
display_name_field: doc.get_title()
|
||||
}, update_modified=False)
|
||||
|
@ -41,6 +41,11 @@ def get_data():
|
||||
"name": "Lead Source",
|
||||
"description": _("Track Leads by Lead Source.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Contract",
|
||||
"description": _("Helps you keep tracks of Contracts based on Supplier, Customer and Employee"),
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -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:
|
||||
|
@ -371,7 +371,7 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
return frappe.db.sql("""select tabAccount.name from `tabAccount`
|
||||
where (tabAccount.report_type = "Profit and Loss"
|
||||
or tabAccount.account_type in ("Expense Account", "Fixed Asset", "Temporary", "Asset Received But Not Billed"))
|
||||
or tabAccount.account_type in ("Expense Account", "Fixed Asset", "Temporary", "Asset Received But Not Billed", "Capital Work in Progress"))
|
||||
and tabAccount.is_group=0
|
||||
and tabAccount.docstatus!=2
|
||||
and tabAccount.{key} LIKE %(txt)s
|
||||
|
@ -24,7 +24,9 @@ def validate_return_against(doc):
|
||||
else:
|
||||
ref_doc = frappe.get_doc(doc.doctype, doc.return_against)
|
||||
|
||||
if ref_doc.company == doc.company and ref_doc.customer == doc.customer and ref_doc.docstatus == 1:
|
||||
party_type = "customer" if doc.doctype in ("Sales Invoice", "Delivery Note") else "supplier"
|
||||
|
||||
if ref_doc.company == doc.company and ref_doc.get(party_type) == doc.get(party_type) and ref_doc.docstatus == 1:
|
||||
# validate posting date time
|
||||
return_posting_datetime = "%s %s" % (doc.posting_date, doc.get("posting_time") or "00:00:00")
|
||||
ref_posting_datetime = "%s %s" % (ref_doc.posting_date, ref_doc.get("posting_time") or "00:00:00")
|
||||
|
@ -45,6 +45,7 @@ class SellingController(StockController):
|
||||
self.set_gross_profit()
|
||||
set_default_income_account_for_item(self)
|
||||
self.set_customer_address()
|
||||
self.validate_for_duplicate_items()
|
||||
|
||||
def set_missing_values(self, for_validate=False):
|
||||
|
||||
@ -381,6 +382,34 @@ class SellingController(StockController):
|
||||
if self.get(address_field):
|
||||
self.set(address_display_field, get_address_display(self.get(address_field)))
|
||||
|
||||
def validate_for_duplicate_items(self):
|
||||
check_list, chk_dupl_itm = [], []
|
||||
if cint(frappe.db.get_single_value("Selling Settings", "allow_multiple_items")):
|
||||
return
|
||||
|
||||
for d in self.get('items'):
|
||||
if self.doctype == "Sales Invoice":
|
||||
e = [d.item_code, d.description, d.warehouse, d.sales_order or d.delivery_note, d.batch_no or '']
|
||||
f = [d.item_code, d.description, d.sales_order or d.delivery_note]
|
||||
elif self.doctype == "Delivery Note":
|
||||
e = [d.item_code, d.description, d.warehouse, d.against_sales_order or d.against_sales_invoice, d.batch_no or '']
|
||||
f = [d.item_code, d.description, d.against_sales_order or d.against_sales_invoice]
|
||||
elif self.doctype == "Sales Order":
|
||||
e = [d.item_code, d.description, d.warehouse, d.batch_no or '']
|
||||
f = [d.item_code, d.description]
|
||||
|
||||
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1:
|
||||
if e in check_list:
|
||||
frappe.throw(_("Note: Item {0} entered multiple times").format(d.item_code))
|
||||
else:
|
||||
check_list.append(e)
|
||||
else:
|
||||
if f in chk_dupl_itm:
|
||||
frappe.throw(_("Note: Item {0} entered multiple times").format(d.item_code))
|
||||
else:
|
||||
chk_dupl_itm.append(f)
|
||||
|
||||
|
||||
def validate_items(self):
|
||||
# validate items to see if they have is_sales_item enabled
|
||||
from erpnext.controllers.buying_controller import validate_item_type
|
||||
|
@ -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)
|
||||
@ -77,22 +80,12 @@ def get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_len
|
||||
|
||||
if or_filters:
|
||||
for r in frappe.get_list(doctype, fields=fields,filters=filters, or_filters=or_filters,
|
||||
limit_start=limit_start, limit_page_length=limit_page_length,
|
||||
limit_start=limit_start, limit_page_length=limit_page_length,
|
||||
ignore_permissions=ignore_permissions, order_by=order_by):
|
||||
data.append(r)
|
||||
|
||||
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,38 +123,56 @@ 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
|
||||
select
|
||||
`tabContact`.email_id,
|
||||
`tabDynamic Link`.link_doctype,
|
||||
`tabDynamic Link`.link_name
|
||||
from
|
||||
from
|
||||
`tabContact`, `tabDynamic Link`
|
||||
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'
|
@ -54,6 +54,8 @@ def get_last_issue_from_customer(customer_name):
|
||||
|
||||
|
||||
def get_scheduled_employees_for_popup(communication_medium):
|
||||
if not communication_medium: return []
|
||||
|
||||
now_time = frappe.utils.nowtime()
|
||||
weekday = frappe.utils.get_weekday()
|
||||
|
||||
@ -73,3 +75,10 @@ def get_scheduled_employees_for_popup(communication_medium):
|
||||
employee_emails = set([employee.user_id for employee in employees])
|
||||
|
||||
return employee_emails
|
||||
|
||||
def strip_number(number):
|
||||
if not number: return
|
||||
# strip 0 from the start of the number for proper number comparisions
|
||||
# eg. 07888383332 should match with 7888383332
|
||||
number = number.lstrip('0')
|
||||
return number
|
@ -5,6 +5,8 @@ frappe.listview_settings['Leave Application'] = {
|
||||
return [__("Approved"), "green", "status,=,Approved"];
|
||||
} else if (doc.status === "Rejected") {
|
||||
return [__("Rejected"), "red", "status,=,Rejected"];
|
||||
} else {
|
||||
return [__("Open"), "red", "status,=,Open"];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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",
|
||||
|
@ -14,12 +14,12 @@ from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
|
||||
class PayrollEntry(Document):
|
||||
def onload(self):
|
||||
if not self.docstatus==1 or self.salary_slips_submitted:
|
||||
return
|
||||
return
|
||||
|
||||
# check if salary slips were manually submitted
|
||||
entries = frappe.db.count("Salary Slip", {'payroll_entry': self.name, 'docstatus': 1}, ['name'])
|
||||
if cint(entries) == len(self.employees):
|
||||
self.set_onload("submitted_ss", True)
|
||||
self.set_onload("submitted_ss", True)
|
||||
|
||||
def on_submit(self):
|
||||
self.create_salary_slips()
|
||||
|
@ -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)],
|
||||
'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'])
|
||||
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'
|
||||
}, 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)
|
@ -23,7 +23,8 @@ def get_columns():
|
||||
_("Model") + ":data:50", _("Location") + ":data:100",
|
||||
_("Log") + ":Link/Vehicle Log:100", _("Odometer") + ":Int:80",
|
||||
_("Date") + ":Date:100", _("Fuel Qty") + ":Float:80",
|
||||
_("Fuel Price") + ":Float:100",_("Service Expense") + ":Float:100"
|
||||
_("Fuel Price") + ":Float:100",_("Fuel Expense") + ":Float:100",
|
||||
_("Service Expense") + ":Float:100"
|
||||
]
|
||||
return columns
|
||||
|
||||
@ -32,7 +33,8 @@ def get_log_data(filters):
|
||||
data = frappe.db.sql("""select
|
||||
vhcl.license_plate as "License", vhcl.make as "Make", vhcl.model as "Model",
|
||||
vhcl.location as "Location", log.name as "Log", log.odometer as "Odometer",
|
||||
log.date as "Date", log.fuel_qty as "Fuel Qty", log.price as "Fuel Price"
|
||||
log.date as "Date", log.fuel_qty as "Fuel Qty", log.price as "Fuel Price",
|
||||
log.fuel_qty * log.price as "Fuel Expense"
|
||||
from
|
||||
`tabVehicle` vhcl,`tabVehicle Log` log
|
||||
where
|
||||
@ -58,7 +60,7 @@ def get_chart_data(data,period_list):
|
||||
total_ser_exp=0
|
||||
for row in data:
|
||||
if row["Date"] <= period.to_date and row["Date"] >= period.from_date:
|
||||
total_fuel_exp+=flt(row["Fuel Price"])
|
||||
total_fuel_exp+=flt(row["Fuel Expense"])
|
||||
total_ser_exp+=flt(row["Service Expense"])
|
||||
fueldata.append([period.key,total_fuel_exp])
|
||||
servicedata.append([period.key,total_ser_exp])
|
||||
@ -84,4 +86,4 @@ def get_chart_data(data,period_list):
|
||||
}
|
||||
}
|
||||
chart["type"] = "line"
|
||||
return chart
|
||||
return chart
|
||||
|
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)
|
||||
|
@ -1,484 +1,504 @@
|
||||
{
|
||||
"allow_import": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2013-01-10 16:34:16",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"field_order": [
|
||||
"item",
|
||||
"naming_series",
|
||||
"status",
|
||||
"production_item",
|
||||
"item_name",
|
||||
"image",
|
||||
"bom_no",
|
||||
"allow_alternative_item",
|
||||
"use_multi_level_bom",
|
||||
"skip_transfer",
|
||||
"column_break1",
|
||||
"company",
|
||||
"qty",
|
||||
"material_transferred_for_manufacturing",
|
||||
"produced_qty",
|
||||
"sales_order",
|
||||
"project",
|
||||
"from_wip_warehouse",
|
||||
"warehouses",
|
||||
"wip_warehouse",
|
||||
"fg_warehouse",
|
||||
"column_break_12",
|
||||
"scrap_warehouse",
|
||||
"required_items_section",
|
||||
"required_items",
|
||||
"time",
|
||||
"planned_start_date",
|
||||
"actual_start_date",
|
||||
"column_break_13",
|
||||
"planned_end_date",
|
||||
"actual_end_date",
|
||||
"expected_delivery_date",
|
||||
"operations_section",
|
||||
"transfer_material_against",
|
||||
"operations",
|
||||
"section_break_22",
|
||||
"planned_operating_cost",
|
||||
"actual_operating_cost",
|
||||
"additional_operating_cost",
|
||||
"column_break_24",
|
||||
"total_operating_cost",
|
||||
"more_info",
|
||||
"description",
|
||||
"stock_uom",
|
||||
"column_break2",
|
||||
"material_request",
|
||||
"material_request_item",
|
||||
"sales_order_item",
|
||||
"production_plan",
|
||||
"production_plan_item",
|
||||
"product_bundle_item",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "item",
|
||||
"fieldtype": "Section Break",
|
||||
"options": "fa fa-gift"
|
||||
},
|
||||
{
|
||||
"fieldname": "naming_series",
|
||||
"fieldtype": "Select",
|
||||
"label": "Series",
|
||||
"options": "MFG-WO-.YYYY.-",
|
||||
"print_hide": 1,
|
||||
"reqd": 1,
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"default": "Draft",
|
||||
"depends_on": "eval:!doc.__islocal",
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"label": "Status",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "status",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "\nDraft\nSubmitted\nNot Started\nIn Process\nCompleted\nStopped\nCancelled",
|
||||
"read_only": 1,
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "production_item",
|
||||
"fieldtype": "Link",
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Item To Manufacture",
|
||||
"oldfieldname": "production_item",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Item",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.production_item",
|
||||
"fieldname": "item_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Item Name",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "production_item.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach Image",
|
||||
"hidden": 1,
|
||||
"label": "Image",
|
||||
"options": "image",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "bom_no",
|
||||
"fieldtype": "Link",
|
||||
"label": "BOM No",
|
||||
"oldfieldname": "bom_no",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "BOM",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_alternative_item",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Alternative Item"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"description": "Plan material for sub-assemblies",
|
||||
"fieldname": "use_multi_level_bom",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Multi-Level BOM",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Check if material transfer entry is not required",
|
||||
"fieldname": "skip_transfer",
|
||||
"fieldtype": "Check",
|
||||
"label": "Skip Material Transfer to WIP Warehouse"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break1",
|
||||
"fieldtype": "Column Break",
|
||||
"oldfieldtype": "Column Break",
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"oldfieldname": "company",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Company",
|
||||
"remember_last_selected_value": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Qty To Manufacture",
|
||||
"oldfieldname": "qty",
|
||||
"oldfieldtype": "Currency",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.docstatus==1 && doc.skip_transfer==0",
|
||||
"fieldname": "material_transferred_for_manufacturing",
|
||||
"fieldtype": "Float",
|
||||
"label": "Material Transferred for Manufacturing",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.docstatus==1",
|
||||
"fieldname": "produced_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Manufactured Qty",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "produced_qty",
|
||||
"oldfieldtype": "Currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "sales_order",
|
||||
"fieldtype": "Link",
|
||||
"in_global_search": 1,
|
||||
"label": "Sales Order",
|
||||
"options": "Sales Order"
|
||||
},
|
||||
{
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"oldfieldname": "project",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "skip_transfer",
|
||||
"fieldname": "from_wip_warehouse",
|
||||
"fieldtype": "Check",
|
||||
"label": "Backflush Raw Materials From Work-in-Progress Warehouse"
|
||||
},
|
||||
{
|
||||
"fieldname": "warehouses",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Warehouses",
|
||||
"options": "fa fa-building"
|
||||
},
|
||||
{
|
||||
"fieldname": "wip_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Work-in-Progress Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
"fieldname": "fg_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Target Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_12",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "scrap_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Scrap Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
"fieldname": "required_items_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Required Items"
|
||||
},
|
||||
{
|
||||
"fieldname": "required_items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Required Items",
|
||||
"no_copy": 1,
|
||||
"options": "Work Order Item",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "time",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Time",
|
||||
"options": "fa fa-time"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": "now",
|
||||
"fieldname": "planned_start_date",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Planned Start Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "actual_start_date",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Actual Start Date",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_13",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "planned_end_date",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Planned End Date",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "actual_end_date",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Actual End Date",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "expected_delivery_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Expected Delivery Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "operations_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Operations",
|
||||
"options": "fa fa-wrench"
|
||||
},
|
||||
{
|
||||
"default": "Work Order",
|
||||
"depends_on": "operations",
|
||||
"fieldname": "transfer_material_against",
|
||||
"fieldtype": "Select",
|
||||
"label": "Transfer Material Against",
|
||||
"options": "\nWork Order\nJob Card"
|
||||
},
|
||||
{
|
||||
"fieldname": "operations",
|
||||
"fieldtype": "Table",
|
||||
"label": "Operations",
|
||||
"options": "Work Order Operation",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "operations",
|
||||
"fieldname": "section_break_22",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Operation Cost"
|
||||
},
|
||||
{
|
||||
"fieldname": "planned_operating_cost",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Planned Operating Cost",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "actual_operating_cost",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Actual Operating Cost",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "additional_operating_cost",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Additional Operating Cost",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_24",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "total_operating_cost",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Operating Cost",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "more_info",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "More Information",
|
||||
"options": "fa fa-file-text"
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Item Description",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "stock_uom",
|
||||
"fieldtype": "Link",
|
||||
"label": "Stock UOM",
|
||||
"oldfieldname": "stock_uom",
|
||||
"oldfieldtype": "Data",
|
||||
"options": "UOM",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break2",
|
||||
"fieldtype": "Column Break",
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"description": "Manufacture against Material Request",
|
||||
"fieldname": "material_request",
|
||||
"fieldtype": "Link",
|
||||
"label": "Material Request",
|
||||
"options": "Material Request"
|
||||
},
|
||||
{
|
||||
"fieldname": "material_request_item",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Material Request Item",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "sales_order_item",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Sales Order Item",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "production_plan",
|
||||
"fieldtype": "Link",
|
||||
"label": "Production Plan",
|
||||
"no_copy": 1,
|
||||
"options": "Production Plan",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "production_plan_item",
|
||||
"fieldtype": "Data",
|
||||
"label": "Production Plan Item",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "product_bundle_item",
|
||||
"fieldtype": "Link",
|
||||
"label": "Product Bundle Item",
|
||||
"no_copy": 1,
|
||||
"options": "Item",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Amended From",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "amended_from",
|
||||
"oldfieldtype": "Data",
|
||||
"options": "Work Order",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cogs",
|
||||
"idx": 1,
|
||||
"image_field": "image",
|
||||
"is_submittable": 1,
|
||||
"modified": "2019-05-27 09:36:16.707719",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Work Order",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"import": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Manufacturing User",
|
||||
"set_user_permissions": 1,
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Stock User"
|
||||
}
|
||||
],
|
||||
"sort_order": "ASC",
|
||||
"title_field": "production_item",
|
||||
"track_changes": 1,
|
||||
"track_seen": 1
|
||||
}
|
||||
"allow_import": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2013-01-10 16:34:16",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"item",
|
||||
"naming_series",
|
||||
"status",
|
||||
"production_item",
|
||||
"item_name",
|
||||
"image",
|
||||
"bom_no",
|
||||
"column_break1",
|
||||
"company",
|
||||
"qty",
|
||||
"material_transferred_for_manufacturing",
|
||||
"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",
|
||||
"column_break_12",
|
||||
"scrap_warehouse",
|
||||
"required_items_section",
|
||||
"required_items",
|
||||
"time",
|
||||
"planned_start_date",
|
||||
"actual_start_date",
|
||||
"column_break_13",
|
||||
"planned_end_date",
|
||||
"actual_end_date",
|
||||
"expected_delivery_date",
|
||||
"operations_section",
|
||||
"transfer_material_against",
|
||||
"operations",
|
||||
"section_break_22",
|
||||
"planned_operating_cost",
|
||||
"actual_operating_cost",
|
||||
"additional_operating_cost",
|
||||
"column_break_24",
|
||||
"total_operating_cost",
|
||||
"more_info",
|
||||
"description",
|
||||
"stock_uom",
|
||||
"column_break2",
|
||||
"material_request",
|
||||
"material_request_item",
|
||||
"sales_order_item",
|
||||
"production_plan",
|
||||
"production_plan_item",
|
||||
"product_bundle_item",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "item",
|
||||
"fieldtype": "Section Break",
|
||||
"options": "fa fa-gift"
|
||||
},
|
||||
{
|
||||
"fieldname": "naming_series",
|
||||
"fieldtype": "Select",
|
||||
"label": "Series",
|
||||
"options": "MFG-WO-.YYYY.-",
|
||||
"print_hide": 1,
|
||||
"reqd": 1,
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"default": "Draft",
|
||||
"depends_on": "eval:!doc.__islocal",
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"label": "Status",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "status",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "\nDraft\nSubmitted\nNot Started\nIn Process\nCompleted\nStopped\nCancelled",
|
||||
"read_only": 1,
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "production_item",
|
||||
"fieldtype": "Link",
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Item To Manufacture",
|
||||
"oldfieldname": "production_item",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Item",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.production_item",
|
||||
"fieldname": "item_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Item Name",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "production_item.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach Image",
|
||||
"hidden": 1,
|
||||
"label": "Image",
|
||||
"options": "image",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "bom_no",
|
||||
"fieldtype": "Link",
|
||||
"label": "BOM No",
|
||||
"oldfieldname": "bom_no",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "BOM",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_alternative_item",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Alternative Item"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"description": "Plan material for sub-assemblies",
|
||||
"fieldname": "use_multi_level_bom",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Multi-Level BOM",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Check if material transfer entry is not required",
|
||||
"fieldname": "skip_transfer",
|
||||
"fieldtype": "Check",
|
||||
"label": "Skip Material Transfer to WIP Warehouse"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break1",
|
||||
"fieldtype": "Column Break",
|
||||
"oldfieldtype": "Column Break",
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"oldfieldname": "company",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Company",
|
||||
"remember_last_selected_value": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Qty To Manufacture",
|
||||
"oldfieldname": "qty",
|
||||
"oldfieldtype": "Currency",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.docstatus==1 && doc.skip_transfer==0",
|
||||
"fieldname": "material_transferred_for_manufacturing",
|
||||
"fieldtype": "Float",
|
||||
"label": "Material Transferred for Manufacturing",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.docstatus==1",
|
||||
"fieldname": "produced_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Manufactured Qty",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "produced_qty",
|
||||
"oldfieldtype": "Currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "sales_order",
|
||||
"fieldtype": "Link",
|
||||
"in_global_search": 1,
|
||||
"label": "Sales Order",
|
||||
"options": "Sales Order"
|
||||
},
|
||||
{
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"oldfieldname": "project",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "skip_transfer",
|
||||
"fieldname": "from_wip_warehouse",
|
||||
"fieldtype": "Check",
|
||||
"label": "Backflush Raw Materials From Work-in-Progress Warehouse"
|
||||
},
|
||||
{
|
||||
"fieldname": "warehouses",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Warehouses",
|
||||
"options": "fa fa-building"
|
||||
},
|
||||
{
|
||||
"fieldname": "wip_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Work-in-Progress Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
"fieldname": "fg_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Target Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_12",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "scrap_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Scrap Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
"fieldname": "required_items_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Required Items"
|
||||
},
|
||||
{
|
||||
"fieldname": "required_items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Required Items",
|
||||
"no_copy": 1,
|
||||
"options": "Work Order Item",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "time",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Time",
|
||||
"options": "fa fa-time"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": "now",
|
||||
"fieldname": "planned_start_date",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Planned Start Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "actual_start_date",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Actual Start Date",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_13",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "planned_end_date",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Planned End Date",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "actual_end_date",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Actual End Date",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "expected_delivery_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Expected Delivery Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "operations_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Operations",
|
||||
"options": "fa fa-wrench"
|
||||
},
|
||||
{
|
||||
"default": "Work Order",
|
||||
"depends_on": "operations",
|
||||
"fieldname": "transfer_material_against",
|
||||
"fieldtype": "Select",
|
||||
"label": "Transfer Material Against",
|
||||
"options": "\nWork Order\nJob Card"
|
||||
},
|
||||
{
|
||||
"fieldname": "operations",
|
||||
"fieldtype": "Table",
|
||||
"label": "Operations",
|
||||
"options": "Work Order Operation",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "operations",
|
||||
"fieldname": "section_break_22",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Operation Cost"
|
||||
},
|
||||
{
|
||||
"fieldname": "planned_operating_cost",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Planned Operating Cost",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "actual_operating_cost",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Actual Operating Cost",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "additional_operating_cost",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Additional Operating Cost",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_24",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "total_operating_cost",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Operating Cost",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "more_info",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "More Information",
|
||||
"options": "fa fa-file-text"
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Item Description",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "stock_uom",
|
||||
"fieldtype": "Link",
|
||||
"label": "Stock UOM",
|
||||
"oldfieldname": "stock_uom",
|
||||
"oldfieldtype": "Data",
|
||||
"options": "UOM",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break2",
|
||||
"fieldtype": "Column Break",
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"description": "Manufacture against Material Request",
|
||||
"fieldname": "material_request",
|
||||
"fieldtype": "Link",
|
||||
"label": "Material Request",
|
||||
"options": "Material Request"
|
||||
},
|
||||
{
|
||||
"fieldname": "material_request_item",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Material Request Item",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "sales_order_item",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Sales Order Item",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "production_plan",
|
||||
"fieldtype": "Link",
|
||||
"label": "Production Plan",
|
||||
"no_copy": 1,
|
||||
"options": "Production Plan",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "production_plan_item",
|
||||
"fieldtype": "Data",
|
||||
"label": "Production Plan Item",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "product_bundle_item",
|
||||
"fieldtype": "Link",
|
||||
"label": "Product Bundle Item",
|
||||
"no_copy": 1,
|
||||
"options": "Item",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Amended From",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "amended_from",
|
||||
"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-07-31 00:13:38.218277",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Work Order",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"import": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Manufacturing User",
|
||||
"set_user_permissions": 1,
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"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,7 +66,8 @@ 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)
|
||||
expire_allocation(allocation_obj)
|
||||
if allocation_obj.to_date <= getdate(today()):
|
||||
expire_allocation(allocation_obj)
|
||||
|
||||
def get_allocation_records():
|
||||
return frappe.get_all("Leave Allocation", filters={
|
||||
|
@ -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);
|
||||
|
@ -156,33 +156,21 @@ class Gstr1Report(object):
|
||||
|
||||
|
||||
if self.filters.get("type_of_business") == "B2B":
|
||||
customers = frappe.get_all("Customer",
|
||||
filters={
|
||||
"gst_category": ["in", ["Registered Regular", "Deemed Export", "SEZ"]]
|
||||
})
|
||||
|
||||
if customers:
|
||||
conditions += """ and ifnull(gst_category, '') != 'Overseas' and is_return != 1
|
||||
and customer in ({0})""".format(", ".join([frappe.db.escape(c.name) for c in customers]))
|
||||
conditions += "and ifnull(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') and is_return != 1"
|
||||
|
||||
if self.filters.get("type_of_business") in ("B2C Large", "B2C Small"):
|
||||
b2c_limit = frappe.db.get_single_value('GST Settings', 'b2c_limit')
|
||||
if not b2c_limit:
|
||||
frappe.throw(_("Please set B2C Limit in GST Settings."))
|
||||
|
||||
customers = frappe.get_all("Customer",
|
||||
filters={
|
||||
"gst_category": ["in", ["Unregistered"]]
|
||||
})
|
||||
|
||||
if self.filters.get("type_of_business") == "B2C Large" and customers:
|
||||
conditions += """ and SUBSTR(place_of_supply, 1, 2) != SUBSTR(company_gstin, 1, 2)
|
||||
and grand_total > {0} and is_return != 1 and customer in ({1})""".\
|
||||
and grand_total > {0} and is_return != 1 and gst_category ='Unregistered' """.\
|
||||
format(flt(b2c_limit), ", ".join([frappe.db.escape(c.name) for c in customers]))
|
||||
elif self.filters.get("type_of_business") == "B2C Small" and customers:
|
||||
conditions += """ and (
|
||||
SUBSTR(place_of_supply, 1, 2) = SUBSTR(company_gstin, 1, 2)
|
||||
or grand_total <= {0}) and is_return != 1 and customer in ({1})""".\
|
||||
or grand_total <= {0}) and is_return != 1 and gst_category ='Unregistered' """.\
|
||||
format(flt(b2c_limit), ", ".join([frappe.db.escape(c.name) for c in customers]))
|
||||
|
||||
elif self.filters.get("type_of_business") == "CDNR":
|
||||
|
@ -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):
|
||||
|
@ -72,9 +72,7 @@ class SalesOrder(SellingController):
|
||||
frappe.msgprint(_("Warning: Sales Order {0} already exists against Customer's Purchase Order {1}").format(so[0][0], self.po_no))
|
||||
|
||||
def validate_for_items(self):
|
||||
check_list = []
|
||||
for d in self.get('items'):
|
||||
check_list.append(cstr(d.item_code))
|
||||
|
||||
# used for production plan
|
||||
d.transaction_date = self.transaction_date
|
||||
@ -83,13 +81,6 @@ class SalesOrder(SellingController):
|
||||
where item_code = %s and warehouse = %s", (d.item_code, d.warehouse))
|
||||
d.projected_qty = tot_avail_qty and flt(tot_avail_qty[0][0]) or 0
|
||||
|
||||
# check for same entry multiple times
|
||||
unique_chk_list = set(check_list)
|
||||
if len(unique_chk_list) != len(check_list) and \
|
||||
not cint(frappe.db.get_single_value("Selling Settings", "allow_multiple_items")):
|
||||
frappe.msgprint(_("Same item has been entered multiple times"),
|
||||
title=_("Warning"), indicator='orange')
|
||||
|
||||
def product_bundle_has_stock_item(self, product_bundle):
|
||||
"""Returns true if product bundle has stock item"""
|
||||
ret = len(frappe.db.sql("""select i.name from tabItem i, `tabProduct Bundle Item` pbi
|
||||
|
@ -252,7 +252,7 @@ class Company(NestedSet):
|
||||
def set_mode_of_payment_account(self):
|
||||
cash = frappe.db.get_value('Mode of Payment', {'type': 'Cash'}, 'name')
|
||||
if cash and self.default_cash_account \
|
||||
and not frappe.db.get_value('Mode of Payment Account', {'company': self.name, 'parent': cash}):
|
||||
and not frappe.db.get_value('Mode of Payment Account', {'company': self.name, 'parent': cash}):
|
||||
mode_of_payment = frappe.get_doc('Mode of Payment', cash)
|
||||
mode_of_payment.append('accounts', {
|
||||
'company': self.name,
|
||||
|
@ -166,24 +166,7 @@ class DeliveryNote(SellingController):
|
||||
frappe.throw(_("Customer {0} does not belong to project {1}").format(self.customer, self.project))
|
||||
|
||||
def validate_for_items(self):
|
||||
check_list, chk_dupl_itm = [], []
|
||||
if cint(frappe.db.get_single_value("Selling Settings", "allow_multiple_items")):
|
||||
return
|
||||
|
||||
for d in self.get('items'):
|
||||
e = [d.item_code, d.description, d.warehouse, d.against_sales_order or d.against_sales_invoice, d.batch_no or '']
|
||||
f = [d.item_code, d.description, d.against_sales_order or d.against_sales_invoice]
|
||||
|
||||
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1:
|
||||
if e in check_list:
|
||||
frappe.msgprint(_("Note: Item {0} entered multiple times").format(d.item_code))
|
||||
else:
|
||||
check_list.append(e)
|
||||
else:
|
||||
if f in chk_dupl_itm:
|
||||
frappe.msgprint(_("Note: Item {0} entered multiple times").format(d.item_code))
|
||||
else:
|
||||
chk_dupl_itm.append(f)
|
||||
#Customer Provided parts will have zero valuation rate
|
||||
if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'):
|
||||
d.allow_zero_valuation_rate = 1
|
||||
|
@ -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…
x
Reference in New Issue
Block a user