Merge branch 'develop' of github.com:frappe/erpnext into develop
This commit is contained in:
commit
ca37380d2e
23
.github/workflows/ci-tests.yml
vendored
23
.github/workflows/ci-tests.yml
vendored
@ -80,14 +80,29 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
TYPE: ${{ matrix.TYPE }}
|
TYPE: ${{ matrix.TYPE }}
|
||||||
|
|
||||||
- name: Coverage
|
- name: Coverage - Pull Request
|
||||||
if: matrix.TYPE == 'server'
|
if: matrix.TYPE == 'server' && github.event_name == 'pull_request'
|
||||||
run: |
|
run: |
|
||||||
cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
|
cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
|
||||||
cd ${GITHUB_WORKSPACE}
|
cd ${GITHUB_WORKSPACE}
|
||||||
pip install coveralls==3.0.1
|
pip install coveralls==2.2.0
|
||||||
pip install coverage==5.5
|
pip install coverage==4.5.4
|
||||||
coveralls --service=github
|
coveralls --service=github
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
|
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
|
||||||
|
COVERALLS_SERVICE_NAME: github
|
||||||
|
|
||||||
|
- name: Coverage - Push
|
||||||
|
if: matrix.TYPE == 'server' && github.event_name == 'push'
|
||||||
|
run: |
|
||||||
|
cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
|
||||||
|
cd ${GITHUB_WORKSPACE}
|
||||||
|
pip install coveralls==2.2.0
|
||||||
|
pip install coverage==4.5.4
|
||||||
|
coveralls --service=github-actions
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
|
||||||
|
COVERALLS_SERVICE_NAME: github-actions
|
||||||
|
|
||||||
|
|||||||
@ -39,6 +39,10 @@ ERPNext is built on the [Frappe Framework](https://github.com/frappe/frappe), a
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### Containerized Installation
|
||||||
|
|
||||||
|
Use docker to deploy ERPNext in production or for development of [Frappe](https://github.com/frappe/frappe) apps. See https://github.com/frappe/frappe_docker for more details.
|
||||||
|
|
||||||
### Full Install
|
### Full Install
|
||||||
|
|
||||||
The Easy Way: our install script for bench will install all dependencies (e.g. MariaDB). See https://github.com/frappe/bench for more details.
|
The Easy Way: our install script for bench will install all dependencies (e.g. MariaDB). See https://github.com/frappe/bench for more details.
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import frappe
|
|||||||
from erpnext.hooks import regional_overrides
|
from erpnext.hooks import regional_overrides
|
||||||
from frappe.utils import getdate
|
from frappe.utils import getdate
|
||||||
|
|
||||||
__version__ = '13.1.0'
|
__version__ = '13.2.0'
|
||||||
|
|
||||||
def get_default_company(user=None):
|
def get_default_company(user=None):
|
||||||
'''Get default company for user'''
|
'''Get default company for user'''
|
||||||
|
|||||||
@ -592,6 +592,7 @@ class JournalEntry(AccountsController):
|
|||||||
|
|
||||||
self.validate_total_debit_and_credit()
|
self.validate_total_debit_and_credit()
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def get_outstanding_invoices(self):
|
def get_outstanding_invoices(self):
|
||||||
self.set('accounts', [])
|
self.set('accounts', [])
|
||||||
total = 0
|
total = 0
|
||||||
|
|||||||
@ -561,7 +561,7 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
flt(frm.doc.received_amount) * flt(frm.doc.target_exchange_rate));
|
flt(frm.doc.received_amount) * flt(frm.doc.target_exchange_rate));
|
||||||
|
|
||||||
if(frm.doc.payment_type == "Pay")
|
if(frm.doc.payment_type == "Pay")
|
||||||
frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount);
|
frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount, 1);
|
||||||
else
|
else
|
||||||
frm.events.set_unallocated_amount(frm);
|
frm.events.set_unallocated_amount(frm);
|
||||||
|
|
||||||
@ -582,7 +582,7 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(frm.doc.payment_type == "Receive")
|
if(frm.doc.payment_type == "Receive")
|
||||||
frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.paid_amount);
|
frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.paid_amount, 1);
|
||||||
else
|
else
|
||||||
frm.events.set_unallocated_amount(frm);
|
frm.events.set_unallocated_amount(frm);
|
||||||
},
|
},
|
||||||
@ -743,7 +743,7 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
allocate_party_amount_against_ref_docs: function(frm, paid_amount) {
|
allocate_party_amount_against_ref_docs: function(frm, paid_amount, paid_amount_change) {
|
||||||
var total_positive_outstanding_including_order = 0;
|
var total_positive_outstanding_including_order = 0;
|
||||||
var total_negative_outstanding = 0;
|
var total_negative_outstanding = 0;
|
||||||
var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [],
|
var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [],
|
||||||
@ -800,22 +800,15 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
//If allocate payment amount checkbox is unchecked, set zero to allocate amount
|
//If allocate payment amount checkbox is unchecked, set zero to allocate amount
|
||||||
row.allocated_amount = 0;
|
row.allocated_amount = 0;
|
||||||
|
|
||||||
} else if (frappe.flags.allocate_payment_amount != 0 && !row.allocated_amount) {
|
} else if (frappe.flags.allocate_payment_amount != 0 && (!row.allocated_amount || paid_amount_change)) {
|
||||||
if (row.outstanding_amount > 0 && allocated_positive_outstanding > 0) {
|
if (row.outstanding_amount > 0 && allocated_positive_outstanding >= 0) {
|
||||||
if (row.outstanding_amount >= allocated_positive_outstanding) {
|
row.allocated_amount = (row.outstanding_amount >= allocated_positive_outstanding) ?
|
||||||
row.allocated_amount = allocated_positive_outstanding;
|
allocated_positive_outstanding : row.outstanding_amount;
|
||||||
} else {
|
|
||||||
row.allocated_amount = row.outstanding_amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
allocated_positive_outstanding -= flt(row.allocated_amount);
|
allocated_positive_outstanding -= flt(row.allocated_amount);
|
||||||
} else if (row.outstanding_amount < 0 && allocated_negative_outstanding) {
|
|
||||||
if (Math.abs(row.outstanding_amount) >= allocated_negative_outstanding) {
|
|
||||||
row.allocated_amount = -1*allocated_negative_outstanding;
|
|
||||||
} else {
|
|
||||||
row.allocated_amount = row.outstanding_amount;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
} else if (row.outstanding_amount < 0 && allocated_negative_outstanding) {
|
||||||
|
row.allocated_amount = (Math.abs(row.outstanding_amount) >= allocated_negative_outstanding) ?
|
||||||
|
-1*allocated_negative_outstanding : row.outstanding_amount;
|
||||||
allocated_negative_outstanding -= Math.abs(flt(row.allocated_amount));
|
allocated_negative_outstanding -= Math.abs(flt(row.allocated_amount));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -234,7 +234,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (invoices) {
|
if (invoices) {
|
||||||
this.frm.fields_dict.payment.grid.update_docfield_property(
|
this.frm.fields_dict.payments.grid.update_docfield_property(
|
||||||
'invoice_number', 'options', "\n" + invoices.join("\n")
|
'invoice_number', 'options', "\n" + invoices.join("\n")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -20,10 +20,11 @@
|
|||||||
"discount",
|
"discount",
|
||||||
"section_break_9",
|
"section_break_9",
|
||||||
"payment_amount",
|
"payment_amount",
|
||||||
|
"outstanding",
|
||||||
|
"paid_amount",
|
||||||
"discounted_amount",
|
"discounted_amount",
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
"outstanding",
|
"base_payment_amount"
|
||||||
"paid_amount"
|
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -78,7 +79,8 @@
|
|||||||
"depends_on": "paid_amount",
|
"depends_on": "paid_amount",
|
||||||
"fieldname": "paid_amount",
|
"fieldname": "paid_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Paid Amount"
|
"label": "Paid Amount",
|
||||||
|
"options": "currency"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_3",
|
"fieldname": "column_break_3",
|
||||||
@ -97,6 +99,7 @@
|
|||||||
"fieldname": "outstanding",
|
"fieldname": "outstanding",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Outstanding",
|
"label": "Outstanding",
|
||||||
|
"options": "currency",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -145,12 +148,18 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "section_break_4",
|
"fieldname": "section_break_4",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "base_payment_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Payment Amount (Company Currency)",
|
||||||
|
"options": "Company:company:default_currency"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-02-15 21:03:12.540546",
|
"modified": "2021-04-28 05:41:35.084233",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Schedule",
|
"name": "Payment Schedule",
|
||||||
|
|||||||
@ -235,11 +235,11 @@ def get_invoice_customer_map(pos_invoices):
|
|||||||
|
|
||||||
return pos_invoice_customer_map
|
return pos_invoice_customer_map
|
||||||
|
|
||||||
def consolidate_pos_invoices(pos_invoices=[], closing_entry={}):
|
def consolidate_pos_invoices(pos_invoices=None, closing_entry=None):
|
||||||
invoices = pos_invoices or closing_entry.get('pos_transactions') or get_all_unconsolidated_invoices()
|
invoices = pos_invoices or (closing_entry and closing_entry.get('pos_transactions')) or get_all_unconsolidated_invoices()
|
||||||
invoice_by_customer = get_invoice_customer_map(invoices)
|
invoice_by_customer = get_invoice_customer_map(invoices)
|
||||||
|
|
||||||
if len(invoices) >= 5 and closing_entry:
|
if len(invoices) >= 1 and closing_entry:
|
||||||
closing_entry.set_status(update=True, status='Queued')
|
closing_entry.set_status(update=True, status='Queued')
|
||||||
enqueue_job(create_merge_logs, invoice_by_customer=invoice_by_customer, closing_entry=closing_entry)
|
enqueue_job(create_merge_logs, invoice_by_customer=invoice_by_customer, closing_entry=closing_entry)
|
||||||
else:
|
else:
|
||||||
@ -252,18 +252,18 @@ def unconsolidate_pos_invoices(closing_entry):
|
|||||||
pluck='name'
|
pluck='name'
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(merge_logs) >= 5:
|
if len(merge_logs) >= 1:
|
||||||
closing_entry.set_status(update=True, status='Queued')
|
closing_entry.set_status(update=True, status='Queued')
|
||||||
enqueue_job(cancel_merge_logs, merge_logs=merge_logs, closing_entry=closing_entry)
|
enqueue_job(cancel_merge_logs, merge_logs=merge_logs, closing_entry=closing_entry)
|
||||||
else:
|
else:
|
||||||
cancel_merge_logs(merge_logs, closing_entry)
|
cancel_merge_logs(merge_logs, closing_entry)
|
||||||
|
|
||||||
def create_merge_logs(invoice_by_customer, closing_entry={}):
|
def create_merge_logs(invoice_by_customer, closing_entry=None):
|
||||||
for customer, invoices in iteritems(invoice_by_customer):
|
for customer, invoices in iteritems(invoice_by_customer):
|
||||||
merge_log = frappe.new_doc('POS Invoice Merge Log')
|
merge_log = frappe.new_doc('POS Invoice Merge Log')
|
||||||
merge_log.posting_date = getdate(closing_entry.get('posting_date'))
|
merge_log.posting_date = getdate(closing_entry.get('posting_date')) if closing_entry else nowdate()
|
||||||
merge_log.customer = customer
|
merge_log.customer = customer
|
||||||
merge_log.pos_closing_entry = closing_entry.get('name', None)
|
merge_log.pos_closing_entry = closing_entry.get('name') if closing_entry else None
|
||||||
|
|
||||||
merge_log.set('pos_invoices', invoices)
|
merge_log.set('pos_invoices', invoices)
|
||||||
merge_log.save(ignore_permissions=True)
|
merge_log.save(ignore_permissions=True)
|
||||||
@ -273,7 +273,7 @@ def create_merge_logs(invoice_by_customer, closing_entry={}):
|
|||||||
closing_entry.set_status(update=True, status='Submitted')
|
closing_entry.set_status(update=True, status='Submitted')
|
||||||
closing_entry.update_opening_entry()
|
closing_entry.update_opening_entry()
|
||||||
|
|
||||||
def cancel_merge_logs(merge_logs, closing_entry={}):
|
def cancel_merge_logs(merge_logs, closing_entry=None):
|
||||||
for log in merge_logs:
|
for log in merge_logs:
|
||||||
merge_log = frappe.get_doc('POS Invoice Merge Log', log)
|
merge_log = frappe.get_doc('POS Invoice Merge Log', log)
|
||||||
merge_log.flags.ignore_permissions = True
|
merge_log.flags.ignore_permissions = True
|
||||||
@ -283,20 +283,20 @@ def cancel_merge_logs(merge_logs, closing_entry={}):
|
|||||||
closing_entry.set_status(update=True, status='Cancelled')
|
closing_entry.set_status(update=True, status='Cancelled')
|
||||||
closing_entry.update_opening_entry(for_cancel=True)
|
closing_entry.update_opening_entry(for_cancel=True)
|
||||||
|
|
||||||
def enqueue_job(job, merge_logs=None, invoice_by_customer=None, closing_entry=None):
|
def enqueue_job(job, **kwargs):
|
||||||
check_scheduler_status()
|
check_scheduler_status()
|
||||||
|
|
||||||
|
closing_entry = kwargs.get('closing_entry') or {}
|
||||||
|
|
||||||
job_name = closing_entry.get("name")
|
job_name = closing_entry.get("name")
|
||||||
if not job_already_enqueued(job_name):
|
if not job_already_enqueued(job_name):
|
||||||
enqueue(
|
enqueue(
|
||||||
job,
|
job,
|
||||||
|
**kwargs,
|
||||||
queue="long",
|
queue="long",
|
||||||
timeout=10000,
|
timeout=10000,
|
||||||
event="processing_merge_logs",
|
event="processing_merge_logs",
|
||||||
job_name=job_name,
|
job_name=job_name,
|
||||||
closing_entry=closing_entry,
|
|
||||||
invoice_by_customer=invoice_by_customer,
|
|
||||||
merge_logs=merge_logs,
|
|
||||||
now=frappe.conf.developer_mode or frappe.flags.in_test
|
now=frappe.conf.developer_mode or frappe.flags.in_test
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2021-04-19 14:56:06.652327",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"field",
|
||||||
|
"fieldname"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "fieldname",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Fieldname"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "field",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Field"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2021-04-21 11:12:54.632093",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "POS Search Fields",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class POSSearchFields(Document):
|
||||||
|
pass
|
||||||
@ -1,9 +1,17 @@
|
|||||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
let search_fields_datatypes = ['Data', 'Link', 'Dynamic Link', 'Long Text', 'Select', 'Small Text', 'Text', 'Text Editor'];
|
||||||
|
let do_not_include_fields = ["naming_series", "item_code", "item_name", "stock_uom", "hub_sync_id", "asset_naming_series",
|
||||||
|
"default_material_request_type", "valuation_method", "warranty_period", "weight_uom", "batch_number_series",
|
||||||
|
"serial_no_series", "purchase_uom", "customs_tariff_number", "sales_uom", "deferred_revenue_account",
|
||||||
|
"deferred_expense_account", "quality_inspection_template", "route", "slideshow", "website_image_alt", "thumbnail",
|
||||||
|
"web_long_description", "hub_sync_id"]
|
||||||
|
|
||||||
frappe.ui.form.on('POS Settings', {
|
frappe.ui.form.on('POS Settings', {
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
frm.trigger("get_invoice_fields");
|
frm.trigger("get_invoice_fields");
|
||||||
|
frm.trigger("add_search_options");
|
||||||
},
|
},
|
||||||
|
|
||||||
get_invoice_fields: function(frm) {
|
get_invoice_fields: function(frm) {
|
||||||
@ -21,6 +29,38 @@ frappe.ui.form.on('POS Settings', {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
add_search_options: function(frm) {
|
||||||
|
frappe.model.with_doctype("Item", () => {
|
||||||
|
var fields = $.map(frappe.get_doc("DocType", "Item").fields, function(d) {
|
||||||
|
if (search_fields_datatypes.includes(d.fieldtype) && !(do_not_include_fields.includes(d.fieldname))) {
|
||||||
|
return [d.label];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fields.unshift('');
|
||||||
|
frm.fields_dict.pos_search_fields.grid.update_docfield_property('field', 'options', fields);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.ui.form.on("POS Search Fields", {
|
||||||
|
field: function(frm, doctype, name) {
|
||||||
|
var doc = frappe.get_doc(doctype, name);
|
||||||
|
var df = $.map(frappe.get_doc("DocType", "Item").fields, function(d) {
|
||||||
|
if (doc.field == d.label && search_fields_datatypes.includes(d.fieldtype)) {
|
||||||
|
return d;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})[0];
|
||||||
|
|
||||||
|
doc.fieldname = df.fieldname;
|
||||||
|
frm.refresh_field("fields");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,8 @@
|
|||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"invoice_fields"
|
"invoice_fields",
|
||||||
|
"pos_search_fields"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -13,11 +14,17 @@
|
|||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"label": "POS Field",
|
"label": "POS Field",
|
||||||
"options": "POS Field"
|
"options": "POS Field"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "pos_search_fields",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "POS Search Fields",
|
||||||
|
"options": "POS Search Fields"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-06-01 15:46:41.478928",
|
"modified": "2021-04-19 14:56:24.465218",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Settings",
|
"name": "POS Settings",
|
||||||
|
|||||||
@ -60,8 +60,8 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<br><br>
|
<br><br>
|
||||||
{% if aging %}
|
{% if ageing %}
|
||||||
<h3 class="text-center">{{ _("Ageing Report Based On ") }} {{ aging.ageing_based_on }}</h3>
|
<h3 class="text-center">{{ _("Ageing Report Based On ") }} {{ ageing.ageing_based_on }}</h3>
|
||||||
<h5 class="text-center">
|
<h5 class="text-center">
|
||||||
{{ _("Up to " ) }} {{ frappe.format(filters.to_date, 'Date')}}
|
{{ _("Up to " ) }} {{ frappe.format(filters.to_date, 'Date')}}
|
||||||
</h5>
|
</h5>
|
||||||
@ -78,10 +78,10 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ aging.range1 }}</td>
|
<td>{{ frappe.utils.fmt_money(ageing.range1, currency=filters.presentation_currency) }}</td>
|
||||||
<td>{{ aging.range2 }}</td>
|
<td>{{ frappe.utils.fmt_money(ageing.range2, currency=filters.presentation_currency) }}</td>
|
||||||
<td>{{ aging.range3 }}</td>
|
<td>{{ frappe.utils.fmt_money(ageing.range3, currency=filters.presentation_currency) }}</td>
|
||||||
<td>{{ aging.range4 }}</td>
|
<td>{{ frappe.utils.fmt_money(ageing.range4, currency=filters.presentation_currency) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@ -4,10 +4,12 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from erpnext.accounts.report.general_ledger.general_ledger import execute as get_soa
|
from erpnext.accounts.report.general_ledger.general_ledger import execute as get_soa
|
||||||
from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import execute as get_ageing
|
from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import execute as get_ageing
|
||||||
from frappe.core.doctype.communication.email import make
|
from erpnext import get_company_currency
|
||||||
|
from erpnext.accounts.party import get_party_account_currency
|
||||||
|
|
||||||
from frappe.utils.print_format import report_to_pdf
|
from frappe.utils.print_format import report_to_pdf
|
||||||
from frappe.utils.pdf import get_pdf
|
from frappe.utils.pdf import get_pdf
|
||||||
@ -29,7 +31,7 @@ class ProcessStatementOfAccounts(Document):
|
|||||||
validate_template(self.body)
|
validate_template(self.body)
|
||||||
|
|
||||||
if not self.customers:
|
if not self.customers:
|
||||||
frappe.throw(frappe._('Customers not selected.'))
|
frappe.throw(_('Customers not selected.'))
|
||||||
|
|
||||||
if self.enable_auto_email:
|
if self.enable_auto_email:
|
||||||
self.to_date = self.start_date
|
self.to_date = self.start_date
|
||||||
@ -38,7 +40,7 @@ class ProcessStatementOfAccounts(Document):
|
|||||||
|
|
||||||
def get_report_pdf(doc, consolidated=True):
|
def get_report_pdf(doc, consolidated=True):
|
||||||
statement_dict = {}
|
statement_dict = {}
|
||||||
aging = ''
|
ageing = ''
|
||||||
base_template_path = "frappe/www/printview.html"
|
base_template_path = "frappe/www/printview.html"
|
||||||
template_path = "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html"
|
template_path = "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html"
|
||||||
|
|
||||||
@ -54,26 +56,30 @@ def get_report_pdf(doc, consolidated=True):
|
|||||||
'range4': 120,
|
'range4': 120,
|
||||||
'customer': entry.customer
|
'customer': entry.customer
|
||||||
})
|
})
|
||||||
col1, aging = get_ageing(ageing_filters)
|
col1, ageing = get_ageing(ageing_filters)
|
||||||
aging[0]['ageing_based_on'] = doc.ageing_based_on
|
|
||||||
|
if ageing:
|
||||||
|
ageing[0]['ageing_based_on'] = doc.ageing_based_on
|
||||||
|
|
||||||
tax_id = frappe.get_doc('Customer', entry.customer).tax_id
|
tax_id = frappe.get_doc('Customer', entry.customer).tax_id
|
||||||
|
presentation_currency = get_party_account_currency('Customer', entry.customer, doc.company) \
|
||||||
|
or doc.currency or get_company_currency(doc.company)
|
||||||
|
|
||||||
filters= frappe._dict({
|
filters= frappe._dict({
|
||||||
'from_date': doc.from_date,
|
'from_date': doc.from_date,
|
||||||
'to_date': doc.to_date,
|
'to_date': doc.to_date,
|
||||||
'company': doc.company,
|
'company': doc.company,
|
||||||
'finance_book': doc.finance_book if doc.finance_book else None,
|
'finance_book': doc.finance_book if doc.finance_book else None,
|
||||||
"account": doc.account if doc.account else None,
|
'account': doc.account if doc.account else None,
|
||||||
'party_type': 'Customer',
|
'party_type': 'Customer',
|
||||||
'party': [entry.customer],
|
'party': [entry.customer],
|
||||||
|
'presentation_currency': presentation_currency,
|
||||||
'group_by': doc.group_by,
|
'group_by': doc.group_by,
|
||||||
'currency': doc.currency,
|
'currency': doc.currency,
|
||||||
'cost_center': [cc.cost_center_name for cc in doc.cost_center],
|
'cost_center': [cc.cost_center_name for cc in doc.cost_center],
|
||||||
'project': [p.project_name for p in doc.project],
|
'project': [p.project_name for p in doc.project],
|
||||||
'show_opening_entries': 0,
|
'show_opening_entries': 0,
|
||||||
'include_default_book_entries': 0,
|
'include_default_book_entries': 0,
|
||||||
'show_cancelled_entries': 1,
|
|
||||||
'tax_id': tax_id if tax_id else None
|
'tax_id': tax_id if tax_id else None
|
||||||
})
|
})
|
||||||
col, res = get_soa(filters)
|
col, res = get_soa(filters)
|
||||||
@ -83,11 +89,14 @@ def get_report_pdf(doc, consolidated=True):
|
|||||||
|
|
||||||
if len(res) == 3:
|
if len(res) == 3:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
html = frappe.render_template(template_path, \
|
html = frappe.render_template(template_path, \
|
||||||
{"filters": filters, "data": res, "aging": aging[0] if doc.include_ageing else None})
|
{"filters": filters, "data": res, "ageing": ageing[0] if (doc.include_ageing and ageing) else None})
|
||||||
|
|
||||||
html = frappe.render_template(base_template_path, {"body": html, \
|
html = frappe.render_template(base_template_path, {"body": html, \
|
||||||
"css": get_print_style(), "title": "Statement For " + entry.customer})
|
"css": get_print_style(), "title": "Statement For " + entry.customer})
|
||||||
statement_dict[entry.customer] = html
|
statement_dict[entry.customer] = html
|
||||||
|
|
||||||
if not bool(statement_dict):
|
if not bool(statement_dict):
|
||||||
return False
|
return False
|
||||||
elif consolidated:
|
elif consolidated:
|
||||||
@ -167,7 +176,7 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory):
|
|||||||
if customer_collection == 'Sales Person':
|
if customer_collection == 'Sales Person':
|
||||||
customers = get_customers_based_on_sales_person(collection_name)
|
customers = get_customers_based_on_sales_person(collection_name)
|
||||||
if not bool(customers):
|
if not bool(customers):
|
||||||
frappe.throw('No Customers found with selected options.')
|
frappe.throw(_('No Customers found with selected options.'))
|
||||||
else:
|
else:
|
||||||
if customer_collection == 'Sales Partner':
|
if customer_collection == 'Sales Partner':
|
||||||
customers = frappe.get_list('Customer', fields=['name', 'email_id'], \
|
customers = frappe.get_list('Customer', fields=['name', 'email_id'], \
|
||||||
@ -199,14 +208,14 @@ def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=Tr
|
|||||||
|
|
||||||
if len(billing_email) == 0 or (billing_email[0][0] is None):
|
if len(billing_email) == 0 or (billing_email[0][0] is None):
|
||||||
if billing_and_primary:
|
if billing_and_primary:
|
||||||
frappe.throw('No billing email found for customer: '+ customer_name)
|
frappe.throw(_("No billing email found for customer: {0}").format(customer_name))
|
||||||
else:
|
else:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
if billing_and_primary:
|
if billing_and_primary:
|
||||||
primary_email = frappe.get_value('Customer', customer_name, 'email_id')
|
primary_email = frappe.get_value('Customer', customer_name, 'email_id')
|
||||||
if primary_email is None and int(primary_mandatory):
|
if primary_email is None and int(primary_mandatory):
|
||||||
frappe.throw('No primary email found for customer: '+ customer_name)
|
frappe.throw(_("No primary email found for customer: {0}").format(customer_name))
|
||||||
return [primary_email or '', billing_email[0][0]]
|
return [primary_email or '', billing_email[0][0]]
|
||||||
else:
|
else:
|
||||||
return billing_email[0][0] or ''
|
return billing_email[0][0] or ''
|
||||||
|
|||||||
@ -514,6 +514,28 @@ frappe.ui.form.on("Purchase Invoice", {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
refresh: function(frm) {
|
||||||
|
frm.events.add_custom_buttons(frm);
|
||||||
|
},
|
||||||
|
|
||||||
|
add_custom_buttons: function(frm) {
|
||||||
|
if (frm.doc.per_received < 100) {
|
||||||
|
frm.add_custom_button(__('Purchase Receipt'), () => {
|
||||||
|
frm.events.make_purchase_receipt(frm);
|
||||||
|
}, __('Create'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frm.doc.docstatus == 1 && frm.doc.per_received > 0) {
|
||||||
|
frm.add_custom_button(__('Purchase Receipt'), () => {
|
||||||
|
frappe.route_options = {
|
||||||
|
'purchase_invoice': frm.doc.name
|
||||||
|
}
|
||||||
|
|
||||||
|
frappe.set_route("List", "Purchase Receipt", "List")
|
||||||
|
}, __('View'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
if(frm.doc.__onload && frm.is_new()) {
|
if(frm.doc.__onload && frm.is_new()) {
|
||||||
if(frm.doc.supplier) {
|
if(frm.doc.supplier) {
|
||||||
@ -539,5 +561,13 @@ frappe.ui.form.on("Purchase Invoice", {
|
|||||||
update_stock: function(frm) {
|
update_stock: function(frm) {
|
||||||
hide_fields(frm.doc);
|
hide_fields(frm.doc);
|
||||||
frm.fields_dict.items.grid.toggle_reqd("item_code", frm.doc.update_stock? true: false);
|
frm.fields_dict.items.grid.toggle_reqd("item_code", frm.doc.update_stock? true: false);
|
||||||
|
},
|
||||||
|
|
||||||
|
make_purchase_receipt: function(frm) {
|
||||||
|
frappe.model.open_mapped_doc({
|
||||||
|
method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_purchase_receipt",
|
||||||
|
frm: frm,
|
||||||
|
freeze_message: __("Creating Purchase Receipt ...")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -163,7 +163,8 @@
|
|||||||
"to_date",
|
"to_date",
|
||||||
"column_break_114",
|
"column_break_114",
|
||||||
"auto_repeat",
|
"auto_repeat",
|
||||||
"update_auto_repeat_reference"
|
"update_auto_repeat_reference",
|
||||||
|
"per_received"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -1364,6 +1365,15 @@
|
|||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_width": "50px",
|
"print_width": "50px",
|
||||||
"width": "50px"
|
"width": "50px"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "per_received",
|
||||||
|
"fieldtype": "Percent",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Per Received",
|
||||||
|
"no_copy": 1,
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
|
|||||||
@ -1207,3 +1207,41 @@ def make_inter_company_sales_invoice(source_name, target_doc=None):
|
|||||||
|
|
||||||
def on_doctype_update():
|
def on_doctype_update():
|
||||||
frappe.db.add_index("Purchase Invoice", ["supplier", "is_return", "return_against"])
|
frappe.db.add_index("Purchase Invoice", ["supplier", "is_return", "return_against"])
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def make_purchase_receipt(source_name, target_doc=None):
|
||||||
|
def update_item(obj, target, source_parent):
|
||||||
|
target.qty = flt(obj.qty) - flt(obj.received_qty)
|
||||||
|
target.received_qty = flt(obj.qty) - flt(obj.received_qty)
|
||||||
|
target.stock_qty = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.conversion_factor)
|
||||||
|
target.amount = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.rate)
|
||||||
|
target.base_amount = (flt(obj.qty) - flt(obj.received_qty)) * \
|
||||||
|
flt(obj.rate) * flt(source_parent.conversion_rate)
|
||||||
|
|
||||||
|
doc = get_mapped_doc("Purchase Invoice", source_name, {
|
||||||
|
"Purchase Invoice": {
|
||||||
|
"doctype": "Purchase Receipt",
|
||||||
|
"validation": {
|
||||||
|
"docstatus": ["=", 1],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Purchase Invoice Item": {
|
||||||
|
"doctype": "Purchase Receipt Item",
|
||||||
|
"field_map": {
|
||||||
|
"name": "purchase_invoice_item",
|
||||||
|
"parent": "purchase_invoice",
|
||||||
|
"bom": "bom",
|
||||||
|
"purchase_order": "purchase_order",
|
||||||
|
"po_detail": "purchase_order_item",
|
||||||
|
"material_request": "material_request",
|
||||||
|
"material_request_item": "material_request_item"
|
||||||
|
},
|
||||||
|
"postprocess": update_item,
|
||||||
|
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty)
|
||||||
|
},
|
||||||
|
"Purchase Taxes and Charges": {
|
||||||
|
"doctype": "Purchase Taxes and Charges"
|
||||||
|
}
|
||||||
|
}, target_doc)
|
||||||
|
|
||||||
|
return doc
|
||||||
|
|||||||
@ -397,7 +397,7 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
pi.update({
|
pi.update({
|
||||||
"payment_schedule": get_payment_terms("_Test Payment Term Template",
|
"payment_schedule": get_payment_terms("_Test Payment Term Template",
|
||||||
pi.posting_date, pi.grand_total)
|
pi.posting_date, pi.grand_total, pi.base_grand_total)
|
||||||
})
|
})
|
||||||
|
|
||||||
pi.save()
|
pi.save()
|
||||||
|
|||||||
@ -607,6 +607,7 @@
|
|||||||
"oldfieldname": "purchase_order",
|
"oldfieldname": "purchase_order",
|
||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"options": "Purchase Order",
|
"options": "Purchase Order",
|
||||||
|
"print_hide": 1,
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"search_index": 1
|
"search_index": 1
|
||||||
},
|
},
|
||||||
@ -853,7 +854,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-02-23 00:59:52.614805",
|
"modified": "2021-03-30 09:02:39.256602",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice Item",
|
"name": "Purchase Invoice Item",
|
||||||
|
|||||||
@ -36,6 +36,7 @@
|
|||||||
"additional_discount_percentage",
|
"additional_discount_percentage",
|
||||||
"additional_discount_amount",
|
"additional_discount_amount",
|
||||||
"sb_3",
|
"sb_3",
|
||||||
|
"submit_invoice",
|
||||||
"invoices",
|
"invoices",
|
||||||
"accounting_dimensions_section",
|
"accounting_dimensions_section",
|
||||||
"cost_center",
|
"cost_center",
|
||||||
@ -45,9 +46,7 @@
|
|||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
"fieldname": "cb_1",
|
"fieldname": "cb_1",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "status",
|
"fieldname": "status",
|
||||||
@ -55,97 +54,73 @@
|
|||||||
"label": "Status",
|
"label": "Status",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "\nTrialling\nActive\nPast Due Date\nCancelled\nUnpaid\nCompleted",
|
"options": "\nTrialling\nActive\nPast Due Date\nCancelled\nUnpaid\nCompleted",
|
||||||
"read_only": 1,
|
"read_only": 1
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "subscription_period",
|
"fieldname": "subscription_period",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Subscription Period",
|
"label": "Subscription Period"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "cancelation_date",
|
"fieldname": "cancelation_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Cancelation Date",
|
"label": "Cancelation Date",
|
||||||
"read_only": 1,
|
"read_only": 1
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
"fieldname": "trial_period_start",
|
"fieldname": "trial_period_start",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Trial Period Start Date",
|
"label": "Trial Period Start Date",
|
||||||
"set_only_once": 1,
|
"set_only_once": 1
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.trial_period_start",
|
"depends_on": "eval:doc.trial_period_start",
|
||||||
"fieldname": "trial_period_end",
|
"fieldname": "trial_period_end",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Trial Period End Date",
|
"label": "Trial Period End Date",
|
||||||
"set_only_once": 1,
|
"set_only_once": 1
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_11",
|
"fieldname": "column_break_11",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "current_invoice_start",
|
"fieldname": "current_invoice_start",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Current Invoice Start Date",
|
"label": "Current Invoice Start Date",
|
||||||
"read_only": 1,
|
"read_only": 1
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "current_invoice_end",
|
"fieldname": "current_invoice_end",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Current Invoice End Date",
|
"label": "Current Invoice End Date",
|
||||||
"read_only": 1,
|
"read_only": 1
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"description": "Number of days that the subscriber has to pay invoices generated by this subscription",
|
"description": "Number of days that the subscriber has to pay invoices generated by this subscription",
|
||||||
"fieldname": "days_until_due",
|
"fieldname": "days_until_due",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"label": "Days Until Due",
|
"label": "Days Until Due"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "cancel_at_period_end",
|
"fieldname": "cancel_at_period_end",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Cancel At End Of Period",
|
"label": "Cancel At End Of Period"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "generate_invoice_at_period_start",
|
"fieldname": "generate_invoice_at_period_start",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Generate Invoice At Beginning Of Period",
|
"label": "Generate Invoice At Beginning Of Period"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
"fieldname": "sb_4",
|
"fieldname": "sb_4",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Plans",
|
"label": "Plans"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
@ -153,84 +128,62 @@
|
|||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"label": "Plans",
|
"label": "Plans",
|
||||||
"options": "Subscription Plan Detail",
|
"options": "Subscription Plan Detail",
|
||||||
"reqd": 1,
|
"reqd": 1
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:['Customer', 'Supplier'].includes(doc.party_type)",
|
"depends_on": "eval:['Customer', 'Supplier'].includes(doc.party_type)",
|
||||||
"fieldname": "sb_1",
|
"fieldname": "sb_1",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Taxes",
|
"label": "Taxes"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "sb_2",
|
"fieldname": "sb_2",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Discounts",
|
"label": "Discounts"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "apply_additional_discount",
|
"fieldname": "apply_additional_discount",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Apply Additional Discount On",
|
"label": "Apply Additional Discount On",
|
||||||
"options": "\nGrand Total\nNet Total",
|
"options": "\nGrand Total\nNet Total"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "cb_2",
|
"fieldname": "cb_2",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "additional_discount_percentage",
|
"fieldname": "additional_discount_percentage",
|
||||||
"fieldtype": "Percent",
|
"fieldtype": "Percent",
|
||||||
"label": "Additional DIscount Percentage",
|
"label": "Additional DIscount Percentage"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"fieldname": "additional_discount_amount",
|
"fieldname": "additional_discount_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Additional DIscount Amount",
|
"label": "Additional DIscount Amount"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.invoices",
|
"depends_on": "eval:doc.invoices",
|
||||||
"fieldname": "sb_3",
|
"fieldname": "sb_3",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Invoices",
|
"label": "Invoices"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"fieldname": "invoices",
|
"fieldname": "invoices",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"label": "Invoices",
|
"label": "Invoices",
|
||||||
"options": "Subscription Invoice",
|
"options": "Subscription Invoice"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"fieldname": "accounting_dimensions_section",
|
"fieldname": "accounting_dimensions_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Accounting Dimensions",
|
"label": "Accounting Dimensions"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "dimension_col_break",
|
"fieldname": "dimension_col_break",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "party_type",
|
"fieldname": "party_type",
|
||||||
@ -238,9 +191,7 @@
|
|||||||
"label": "Party Type",
|
"label": "Party Type",
|
||||||
"options": "DocType",
|
"options": "DocType",
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"set_only_once": 1,
|
"set_only_once": 1
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "party",
|
"fieldname": "party",
|
||||||
@ -249,27 +200,21 @@
|
|||||||
"label": "Party",
|
"label": "Party",
|
||||||
"options": "party_type",
|
"options": "party_type",
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"set_only_once": 1,
|
"set_only_once": 1
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.party_type === 'Customer'",
|
"depends_on": "eval:doc.party_type === 'Customer'",
|
||||||
"fieldname": "sales_tax_template",
|
"fieldname": "sales_tax_template",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Sales Taxes and Charges Template",
|
"label": "Sales Taxes and Charges Template",
|
||||||
"options": "Sales Taxes and Charges Template",
|
"options": "Sales Taxes and Charges Template"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.party_type === 'Supplier'",
|
"depends_on": "eval:doc.party_type === 'Supplier'",
|
||||||
"fieldname": "purchase_tax_template",
|
"fieldname": "purchase_tax_template",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Purchase Taxes and Charges Template",
|
"label": "Purchase Taxes and Charges Template",
|
||||||
"options": "Purchase Taxes and Charges Template",
|
"options": "Purchase Taxes and Charges Template"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
@ -277,55 +222,49 @@
|
|||||||
"fieldname": "follow_calendar_months",
|
"fieldname": "follow_calendar_months",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Follow Calendar Months",
|
"label": "Follow Calendar Months",
|
||||||
"set_only_once": 1,
|
"set_only_once": 1
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"description": "New invoices will be generated as per schedule even if current invoices are unpaid or past due date",
|
"description": "New invoices will be generated as per schedule even if current invoices are unpaid or past due date",
|
||||||
"fieldname": "generate_new_invoices_past_due_date",
|
"fieldname": "generate_new_invoices_past_due_date",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Generate New Invoices Past Due Date",
|
"label": "Generate New Invoices Past Due Date"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "end_date",
|
"fieldname": "end_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Subscription End Date",
|
"label": "Subscription End Date",
|
||||||
"set_only_once": 1,
|
"set_only_once": 1
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "start_date",
|
"fieldname": "start_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Subscription Start Date",
|
"label": "Subscription Start Date",
|
||||||
"set_only_once": 1,
|
"set_only_once": 1
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "cost_center",
|
"fieldname": "cost_center",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Cost Center",
|
"label": "Cost Center",
|
||||||
"options": "Cost Center",
|
"options": "Cost Center"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "company",
|
"fieldname": "company",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Company",
|
"label": "Company",
|
||||||
"options": "Company",
|
"options": "Company"
|
||||||
"show_days": 1,
|
},
|
||||||
"show_seconds": 1
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "submit_invoice",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Submit Invoice Automatically"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-02-09 15:44:20.024789",
|
"modified": "2021-04-19 15:24:27.550797",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Subscription",
|
"name": "Subscription",
|
||||||
|
|||||||
@ -276,7 +276,7 @@ class Subscription(Document):
|
|||||||
frappe.throw(_('Subscription End Date is mandatory to follow calendar months'))
|
frappe.throw(_('Subscription End Date is mandatory to follow calendar months'))
|
||||||
|
|
||||||
if billing_info[0]['billing_interval'] != 'Month':
|
if billing_info[0]['billing_interval'] != 'Month':
|
||||||
frappe.throw('Billing Interval in Subscription Plan must be Month to follow calendar months')
|
frappe.throw(_('Billing Interval in Subscription Plan must be Month to follow calendar months'))
|
||||||
|
|
||||||
def after_insert(self):
|
def after_insert(self):
|
||||||
# todo: deal with users who collect prepayments. Maybe a new Subscription Invoice doctype?
|
# todo: deal with users who collect prepayments. Maybe a new Subscription Invoice doctype?
|
||||||
@ -383,6 +383,8 @@ class Subscription(Document):
|
|||||||
|
|
||||||
invoice.flags.ignore_mandatory = True
|
invoice.flags.ignore_mandatory = True
|
||||||
invoice.save()
|
invoice.save()
|
||||||
|
|
||||||
|
if self.submit_invoice:
|
||||||
invoice.submit()
|
invoice.submit()
|
||||||
|
|
||||||
return invoice
|
return invoice
|
||||||
|
|||||||
@ -18,7 +18,8 @@ def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, upd
|
|||||||
gl_map = process_gl_map(gl_map, merge_entries)
|
gl_map = process_gl_map(gl_map, merge_entries)
|
||||||
if gl_map and len(gl_map) > 1:
|
if gl_map and len(gl_map) > 1:
|
||||||
save_entries(gl_map, adv_adj, update_outstanding, from_repost)
|
save_entries(gl_map, adv_adj, update_outstanding, from_repost)
|
||||||
else:
|
# Post GL Map proccess there may no be any GL Entries
|
||||||
|
elif gl_map:
|
||||||
frappe.throw(_("Incorrect number of General Ledger Entries found. You might have selected a wrong Account in the transaction."))
|
frappe.throw(_("Incorrect number of General Ledger Entries found. You might have selected a wrong Account in the transaction."))
|
||||||
else:
|
else:
|
||||||
make_reverse_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding)
|
make_reverse_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding)
|
||||||
@ -170,7 +171,7 @@ def round_off_debit_credit(gl_map):
|
|||||||
else:
|
else:
|
||||||
allowance = .5
|
allowance = .5
|
||||||
|
|
||||||
if abs(debit_credit_diff) >= allowance:
|
if abs(debit_credit_diff) > allowance:
|
||||||
frappe.throw(_("Debit and Credit not equal for {0} #{1}. Difference is {2}.")
|
frappe.throw(_("Debit and Credit not equal for {0} #{1}. Difference is {2}.")
|
||||||
.format(gl_map[0].voucher_type, gl_map[0].voucher_no, debit_credit_diff))
|
.format(gl_map[0].voucher_type, gl_map[0].voucher_no, debit_credit_diff))
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,29 @@
|
|||||||
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
frappe.query_reports['Billed Items To Be Received'] = {
|
||||||
|
'filters': [
|
||||||
|
{
|
||||||
|
'label': __('Company'),
|
||||||
|
'fieldname': 'company',
|
||||||
|
'fieldtype': 'Link',
|
||||||
|
'options': 'Company',
|
||||||
|
'reqd': 1,
|
||||||
|
'default': frappe.defaults.get_default('Company')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': __('As on Date'),
|
||||||
|
'fieldname': 'posting_date',
|
||||||
|
'fieldtype': 'Date',
|
||||||
|
'reqd': 1,
|
||||||
|
'default': get_today()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': __('Purchase Invoice'),
|
||||||
|
'fieldname': 'purchase_invoice',
|
||||||
|
'fieldtype': 'Link',
|
||||||
|
'options': 'Purchase Invoice'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"add_total_row": 0,
|
||||||
|
"columns": [],
|
||||||
|
"creation": "2021-03-30 09:35:38.683028",
|
||||||
|
"disable_prepared_report": 0,
|
||||||
|
"disabled": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Report",
|
||||||
|
"filters": [],
|
||||||
|
"idx": 0,
|
||||||
|
"is_standard": "Yes",
|
||||||
|
"modified": "2021-03-31 08:48:30.944429",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "Billed Items To Be Received",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"prepared_report": 0,
|
||||||
|
"query": "",
|
||||||
|
"ref_doctype": "Purchase Invoice",
|
||||||
|
"report_name": "Billed Items To Be Received",
|
||||||
|
"report_type": "Script Report",
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "Accounts User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Purchase User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Accounts Manager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Auditor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Stock User"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,107 @@
|
|||||||
|
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
def execute(filters=None):
|
||||||
|
data = get_data(filters) or []
|
||||||
|
columns = get_columns()
|
||||||
|
|
||||||
|
return columns, data
|
||||||
|
|
||||||
|
def get_data(report_filters):
|
||||||
|
filters = get_report_filters(report_filters)
|
||||||
|
fields = get_report_fields()
|
||||||
|
|
||||||
|
return frappe.get_all('Purchase Invoice',
|
||||||
|
fields= fields, filters=filters)
|
||||||
|
|
||||||
|
def get_report_filters(report_filters):
|
||||||
|
filters = [['Purchase Invoice','company','=',report_filters.get('company')],
|
||||||
|
['Purchase Invoice','posting_date','<=',report_filters.get('posting_date')], ['Purchase Invoice','docstatus','=',1],
|
||||||
|
['Purchase Invoice','per_received','<',100], ['Purchase Invoice','update_stock','=',0]]
|
||||||
|
|
||||||
|
if report_filters.get('purchase_invoice'):
|
||||||
|
filters.append(['Purchase Invoice','per_received','in',[report_filters.get('purchase_invoice')]])
|
||||||
|
|
||||||
|
return filters
|
||||||
|
|
||||||
|
def get_report_fields():
|
||||||
|
fields = []
|
||||||
|
for p_field in ['name', 'supplier', 'company', 'posting_date', 'currency']:
|
||||||
|
fields.append('`tabPurchase Invoice`.`{}`'.format(p_field))
|
||||||
|
|
||||||
|
for c_field in ['item_code', 'item_name', 'uom', 'qty', 'received_qty', 'rate', 'amount']:
|
||||||
|
fields.append('`tabPurchase Invoice Item`.`{}`'.format(c_field))
|
||||||
|
|
||||||
|
return fields
|
||||||
|
|
||||||
|
def get_columns():
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'label': _('Purchase Invoice'),
|
||||||
|
'fieldname': 'name',
|
||||||
|
'fieldtype': 'Link',
|
||||||
|
'options': 'Purchase Invoice',
|
||||||
|
'width': 170
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': _('Supplier'),
|
||||||
|
'fieldname': 'supplier',
|
||||||
|
'fieldtype': 'Link',
|
||||||
|
'options': 'Supplier',
|
||||||
|
'width': 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': _('Posting Date'),
|
||||||
|
'fieldname': 'posting_date',
|
||||||
|
'fieldtype': 'Date',
|
||||||
|
'width': 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': _('Item Code'),
|
||||||
|
'fieldname': 'item_code',
|
||||||
|
'fieldtype': 'Link',
|
||||||
|
'options': 'Item',
|
||||||
|
'width': 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': _('Item Name'),
|
||||||
|
'fieldname': 'item_name',
|
||||||
|
'fieldtype': 'Data',
|
||||||
|
'width': 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': _('UOM'),
|
||||||
|
'fieldname': 'uom',
|
||||||
|
'fieldtype': 'Link',
|
||||||
|
'options': 'UOM',
|
||||||
|
'width': 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': _('Invoiced Qty'),
|
||||||
|
'fieldname': 'qty',
|
||||||
|
'fieldtype': 'Float',
|
||||||
|
'width': 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': _('Received Qty'),
|
||||||
|
'fieldname': 'received_qty',
|
||||||
|
'fieldtype': 'Float',
|
||||||
|
'width': 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': _('Rate'),
|
||||||
|
'fieldname': 'rate',
|
||||||
|
'fieldtype': 'Currency',
|
||||||
|
'width': 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': _('Amount'),
|
||||||
|
'fieldname': 'amount',
|
||||||
|
'fieldtype': 'Currency',
|
||||||
|
'width': 100
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -435,6 +435,35 @@ class TestPurchaseOrder(unittest.TestCase):
|
|||||||
po.load_from_db()
|
po.load_from_db()
|
||||||
self.assertEqual(po.get("items")[0].received_qty, 5)
|
self.assertEqual(po.get("items")[0].received_qty, 5)
|
||||||
|
|
||||||
|
def test_purchase_order_invoice_receipt_workflow(self):
|
||||||
|
from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_purchase_receipt
|
||||||
|
|
||||||
|
po = create_purchase_order()
|
||||||
|
pi = make_pi_from_po(po.name)
|
||||||
|
|
||||||
|
pi.submit()
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(pi.name)
|
||||||
|
pr.submit()
|
||||||
|
|
||||||
|
pi.load_from_db()
|
||||||
|
|
||||||
|
self.assertEquals(pi.per_received, 100.00)
|
||||||
|
self.assertEquals(pi.items[0].qty, pi.items[0].received_qty)
|
||||||
|
|
||||||
|
po.load_from_db()
|
||||||
|
|
||||||
|
self.assertEquals(po.per_received, 100.00)
|
||||||
|
self.assertEquals(po.per_billed, 100.00)
|
||||||
|
|
||||||
|
pr.cancel()
|
||||||
|
|
||||||
|
pi.load_from_db()
|
||||||
|
pi.cancel()
|
||||||
|
|
||||||
|
po.load_from_db()
|
||||||
|
po.cancel()
|
||||||
|
|
||||||
def test_make_purchase_invoice(self):
|
def test_make_purchase_invoice(self):
|
||||||
po = create_purchase_order(do_not_submit=True)
|
po = create_purchase_order(do_not_submit=True)
|
||||||
|
|
||||||
|
|||||||
56
erpnext/change_log/v13/v13_2_0.md
Normal file
56
erpnext/change_log/v13/v13_2_0.md
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# Version 13.2.0 Release Notes
|
||||||
|
|
||||||
|
### Features & Enhancements
|
||||||
|
|
||||||
|
- Employee Hours Utilization Report ([#25209](https://github.com/frappe/erpnext/pull/25209))
|
||||||
|
- Delayed Tasks Summary Report ([#25024](https://github.com/frappe/erpnext/pull/25024))
|
||||||
|
- Project Profitability Report ([#24944](https://github.com/frappe/erpnext/pull/24944))
|
||||||
|
- Timer in LMS Quiz ([#24246](https://github.com/frappe/erpnext/pull/24246))
|
||||||
|
- Role to allow over billing, delivery, receipt ([#24854](https://github.com/frappe/erpnext/pull/24854))
|
||||||
|
- Auto calculate distance for e-way bill generations ([#25480](https://github.com/frappe/erpnext/pull/25480))
|
||||||
|
- Add total available stock field in PO ([#24878](https://github.com/frappe/erpnext/pull/24878))
|
||||||
|
- Refactored Setup Taxes and Charges ([#24805](https://github.com/frappe/erpnext/pull/24805))
|
||||||
|
- Inpatient Occupancy Table Editable for Healthcare Admin ([#24989](https://github.com/frappe/erpnext/pull/24989))
|
||||||
|
- Added Disable Rounded Total in sales transactions ([#25362](https://github.com/frappe/erpnext/pull/25362))
|
||||||
|
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Incorrect GL Entry validation ([#25474](https://github.com/frappe/erpnext/pull/25474))
|
||||||
|
- Cannot create item variants ([#25433](https://github.com/frappe/erpnext/pull/25433))
|
||||||
|
- Leave policy in leave allocation ([#25334](https://github.com/frappe/erpnext/pull/25334))
|
||||||
|
- Let Administrator delete company transactions ([#25300](https://github.com/frappe/erpnext/pull/25300))
|
||||||
|
- Display reconcile tool when closing balance 0 ([#25417](https://github.com/frappe/erpnext/pull/25417))
|
||||||
|
- Bulk Salary Structure Assignment ([#25389](https://github.com/frappe/erpnext/pull/25389))
|
||||||
|
- Payment amount showing in foreign currency ([#25518](https://github.com/frappe/erpnext/pull/25518))
|
||||||
|
- Commit changes to shipment status in database ([#25374](https://github.com/frappe/erpnext/pull/25374))
|
||||||
|
- Add amend perm for loan and system manager for loan doctypes ([#25393](https://github.com/frappe/erpnext/pull/25393))
|
||||||
|
- Cashier query in POS Opening/Closing Entry ([#25398](https://github.com/frappe/erpnext/pull/25398))
|
||||||
|
- Apply single transaction threshold on net_total instead of supplier credit amount ([#25243](https://github.com/frappe/erpnext/pull/25243))
|
||||||
|
- Update allocated amount after paid amount is changed in PE ([#25528](https://github.com/frappe/erpnext/pull/25528))
|
||||||
|
- Remove non-standard module cards from Home Workspace ([#25391](https://github.com/frappe/erpnext/pull/25391))
|
||||||
|
- Cannot scan spacebar character in pos ([#25479](https://github.com/frappe/erpnext/pull/25479))
|
||||||
|
- Permission error after submitting exchange rate revaluation ([#25432](https://github.com/frappe/erpnext/pull/25432))
|
||||||
|
- Equality check instead of assignment in cart ([#25372](https://github.com/frappe/erpnext/pull/25372))
|
||||||
|
- Disable auto naming of customer during import ([#25152](https://github.com/frappe/erpnext/pull/25152))
|
||||||
|
- Additional Salary component amount not getting set ([#25355](https://github.com/frappe/erpnext/pull/25355))
|
||||||
|
- Round off values near to zero ([#25304](https://github.com/frappe/erpnext/pull/25304))
|
||||||
|
- Allow to cancel loan with cancelled repayment entry ([#25508](https://github.com/frappe/erpnext/pull/25508))
|
||||||
|
- Currency symbol in bank transaction list view ([#25336](https://github.com/frappe/erpnext/pull/25336))
|
||||||
|
- Incorrect batch picked in subcontracted purchase receipt ([#25186](https://github.com/frappe/erpnext/pull/25186))
|
||||||
|
- Issue in project custom status ([#25452](https://github.com/frappe/erpnext/pull/25452))
|
||||||
|
- Shipment pickup_to, pickup_from functionality. ([#25359](https://github.com/frappe/erpnext/pull/25359))
|
||||||
|
- Stock ledger entry created against draft stock entry ([#25539](https://github.com/frappe/erpnext/pull/25539))
|
||||||
|
- Ageing errors in PSOA ([#25529](https://github.com/frappe/erpnext/pull/25529))
|
||||||
|
- Permission error while adding weekly holidays ([#25450](https://github.com/frappe/erpnext/pull/25450))
|
||||||
|
- Filter for employees in salary slip ([#25360](https://github.com/frappe/erpnext/pull/25360))
|
||||||
|
- Backward compatibility for GSTR-1 report ([#25444](https://github.com/frappe/erpnext/pull/25444))
|
||||||
|
- Incorrect incoming rate for the sales return ([#25145](https://github.com/frappe/erpnext/pull/25145))
|
||||||
|
- POS print receipt ([#25328](https://github.com/frappe/erpnext/pull/25328))
|
||||||
|
- Laboratory Module patch ([#25431](https://github.com/frappe/erpnext/pull/25431))
|
||||||
|
- Performance: fetching exchange rate on every line item slows down PO ([#25345](https://github.com/frappe/erpnext/pull/25345))
|
||||||
|
- Presentation currency in statement of accounts ([#25367](https://github.com/frappe/erpnext/pull/25367))
|
||||||
|
- Serial No not updated correctly via Inter Company Stock Transfer ([#25006](https://github.com/frappe/erpnext/pull/25006))
|
||||||
|
- Ignore Customer Group Perm on All Products page ([#25396](https://github.com/frappe/erpnext/pull/25396))
|
||||||
|
- Change subcontracted item display ([#25425](https://github.com/frappe/erpnext/pull/25425))
|
||||||
|
- Add company validation for e-invoicing ([#25348](https://github.com/frappe/erpnext/pull/25348))
|
||||||
@ -90,6 +90,8 @@ class AccountsController(TransactionBase):
|
|||||||
self.ensure_supplier_is_not_blocked()
|
self.ensure_supplier_is_not_blocked()
|
||||||
|
|
||||||
self.validate_date_with_fiscal_year()
|
self.validate_date_with_fiscal_year()
|
||||||
|
self.validate_party_accounts()
|
||||||
|
|
||||||
self.validate_inter_company_reference()
|
self.validate_inter_company_reference()
|
||||||
|
|
||||||
self.set_incoming_rate()
|
self.set_incoming_rate()
|
||||||
@ -233,6 +235,23 @@ class AccountsController(TransactionBase):
|
|||||||
validate_fiscal_year(self.get(date_field), self.fiscal_year, self.company,
|
validate_fiscal_year(self.get(date_field), self.fiscal_year, self.company,
|
||||||
self.meta.get_label(date_field), self)
|
self.meta.get_label(date_field), self)
|
||||||
|
|
||||||
|
def validate_party_accounts(self):
|
||||||
|
if self.doctype not in ('Sales Invoice', 'Purchase Invoice'):
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.doctype == 'Sales Invoice':
|
||||||
|
party_account_field = 'debit_to'
|
||||||
|
item_field = 'income_account'
|
||||||
|
else:
|
||||||
|
party_account_field = 'credit_to'
|
||||||
|
item_field = 'expense_account'
|
||||||
|
|
||||||
|
for item in self.get('items'):
|
||||||
|
if item.get(item_field) == self.get(party_account_field):
|
||||||
|
frappe.throw(_("Row {0}: {1} {2} cannot be same as {3} (Party Account) {4}").format(item.idx,
|
||||||
|
frappe.bold(frappe.unscrub(item_field)), item.get(item_field),
|
||||||
|
frappe.bold(frappe.unscrub(party_account_field)), self.get(party_account_field)))
|
||||||
|
|
||||||
def validate_inter_company_reference(self):
|
def validate_inter_company_reference(self):
|
||||||
if self.doctype not in ('Purchase Invoice', 'Purchase Receipt', 'Purchase Order'):
|
if self.doctype not in ('Purchase Invoice', 'Purchase Receipt', 'Purchase Order'):
|
||||||
return
|
return
|
||||||
@ -904,29 +923,34 @@ class AccountsController(TransactionBase):
|
|||||||
date = self.get("due_date")
|
date = self.get("due_date")
|
||||||
due_date = date or posting_date
|
due_date = date or posting_date
|
||||||
|
|
||||||
if party_account_currency == self.company_currency:
|
base_grand_total = self.get("base_rounded_total") or self.base_grand_total
|
||||||
grand_total = self.get("base_rounded_total") or self.base_grand_total
|
|
||||||
else:
|
|
||||||
grand_total = self.get("rounded_total") or self.grand_total
|
grand_total = self.get("rounded_total") or self.grand_total
|
||||||
|
|
||||||
if self.doctype in ("Sales Invoice", "Purchase Invoice"):
|
if self.doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||||
|
base_grand_total = base_grand_total - flt(self.base_write_off_amount)
|
||||||
grand_total = grand_total - flt(self.write_off_amount)
|
grand_total = grand_total - flt(self.write_off_amount)
|
||||||
|
|
||||||
if self.get("total_advance"):
|
if self.get("total_advance"):
|
||||||
|
if party_account_currency == self.company_currency:
|
||||||
|
base_grand_total -= self.get("total_advance")
|
||||||
|
grand_total = flt(base_grand_total / self.get("conversion_rate"), self.precision("grand_total"))
|
||||||
|
else:
|
||||||
grand_total -= self.get("total_advance")
|
grand_total -= self.get("total_advance")
|
||||||
|
base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total"))
|
||||||
|
|
||||||
if not self.get("payment_schedule"):
|
if not self.get("payment_schedule"):
|
||||||
if self.get("payment_terms_template"):
|
if self.get("payment_terms_template"):
|
||||||
data = get_payment_terms(self.payment_terms_template, posting_date, grand_total)
|
data = get_payment_terms(self.payment_terms_template, posting_date, grand_total, base_grand_total)
|
||||||
for item in data:
|
for item in data:
|
||||||
self.append("payment_schedule", item)
|
self.append("payment_schedule", item)
|
||||||
else:
|
else:
|
||||||
data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total)
|
data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total, base_payment_amount=base_grand_total)
|
||||||
self.append("payment_schedule", data)
|
self.append("payment_schedule", data)
|
||||||
else:
|
else:
|
||||||
for d in self.get("payment_schedule"):
|
for d in self.get("payment_schedule"):
|
||||||
if d.invoice_portion:
|
if d.invoice_portion:
|
||||||
d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
|
d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
|
||||||
|
d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
|
||||||
d.outstanding = d.payment_amount
|
d.outstanding = d.payment_amount
|
||||||
|
|
||||||
def set_due_date(self):
|
def set_due_date(self):
|
||||||
@ -963,22 +987,28 @@ class AccountsController(TransactionBase):
|
|||||||
|
|
||||||
if self.get("payment_schedule"):
|
if self.get("payment_schedule"):
|
||||||
total = 0
|
total = 0
|
||||||
|
base_total = 0
|
||||||
for d in self.get("payment_schedule"):
|
for d in self.get("payment_schedule"):
|
||||||
total += flt(d.payment_amount)
|
total += flt(d.payment_amount)
|
||||||
|
base_total += flt(d.base_payment_amount)
|
||||||
|
|
||||||
if party_account_currency == self.company_currency:
|
base_grand_total = self.get("base_rounded_total") or self.base_grand_total
|
||||||
total = flt(total, self.precision("base_grand_total"))
|
grand_total = self.get("rounded_total") or self.grand_total
|
||||||
grand_total = flt(self.get("base_rounded_total") or self.base_grand_total, self.precision('base_grand_total'))
|
|
||||||
else:
|
|
||||||
total = flt(total, self.precision("grand_total"))
|
|
||||||
grand_total = flt(self.get("rounded_total") or self.grand_total, self.precision('grand_total'))
|
|
||||||
|
|
||||||
if self.get("total_advance"):
|
|
||||||
grand_total -= self.get("total_advance")
|
|
||||||
|
|
||||||
if self.doctype in ("Sales Invoice", "Purchase Invoice"):
|
if self.doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||||
|
base_grand_total = base_grand_total - flt(self.base_write_off_amount)
|
||||||
grand_total = grand_total - flt(self.write_off_amount)
|
grand_total = grand_total - flt(self.write_off_amount)
|
||||||
if total != flt(grand_total, self.precision("grand_total")):
|
|
||||||
|
if self.get("total_advance"):
|
||||||
|
if party_account_currency == self.company_currency:
|
||||||
|
base_grand_total -= self.get("total_advance")
|
||||||
|
grand_total = flt(base_grand_total / self.get("conversion_rate"), self.precision("grand_total"))
|
||||||
|
else:
|
||||||
|
grand_total -= self.get("total_advance")
|
||||||
|
base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total"))
|
||||||
|
print(grand_total, base_grand_total)
|
||||||
|
if total != flt(grand_total, self.precision("grand_total")) or \
|
||||||
|
base_total != flt(base_grand_total, self.precision("base_grand_total")):
|
||||||
frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total"))
|
frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total"))
|
||||||
|
|
||||||
def is_rounded_total_disabled(self):
|
def is_rounded_total_disabled(self):
|
||||||
@ -1218,7 +1248,7 @@ def update_invoice_status():
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_payment_terms(terms_template, posting_date=None, grand_total=None, bill_date=None):
|
def get_payment_terms(terms_template, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None):
|
||||||
if not terms_template:
|
if not terms_template:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -1226,14 +1256,14 @@ def get_payment_terms(terms_template, posting_date=None, grand_total=None, bill_
|
|||||||
|
|
||||||
schedule = []
|
schedule = []
|
||||||
for d in terms_doc.get("terms"):
|
for d in terms_doc.get("terms"):
|
||||||
term_details = get_payment_term_details(d, posting_date, grand_total, bill_date)
|
term_details = get_payment_term_details(d, posting_date, grand_total, base_grand_total, bill_date)
|
||||||
schedule.append(term_details)
|
schedule.append(term_details)
|
||||||
|
|
||||||
return schedule
|
return schedule
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_payment_term_details(term, posting_date=None, grand_total=None, bill_date=None):
|
def get_payment_term_details(term, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None):
|
||||||
term_details = frappe._dict()
|
term_details = frappe._dict()
|
||||||
if isinstance(term, text_type):
|
if isinstance(term, text_type):
|
||||||
term = frappe.get_doc("Payment Term", term)
|
term = frappe.get_doc("Payment Term", term)
|
||||||
@ -1242,9 +1272,9 @@ def get_payment_term_details(term, posting_date=None, grand_total=None, bill_dat
|
|||||||
term_details.description = term.description
|
term_details.description = term.description
|
||||||
term_details.invoice_portion = term.invoice_portion
|
term_details.invoice_portion = term.invoice_portion
|
||||||
term_details.payment_amount = flt(term.invoice_portion) * flt(grand_total) / 100
|
term_details.payment_amount = flt(term.invoice_portion) * flt(grand_total) / 100
|
||||||
|
term_details.base_payment_amount = flt(term.invoice_portion) * flt(base_grand_total) / 100
|
||||||
term_details.discount_type = term.discount_type
|
term_details.discount_type = term.discount_type
|
||||||
term_details.discount = term.discount
|
term_details.discount = term.discount
|
||||||
# term_details.discounted_amount = flt(grand_total) * (term.discount / 100) if term.discount_type == 'Percentage' else discount
|
|
||||||
term_details.outstanding = term_details.payment_amount
|
term_details.outstanding = term_details.payment_amount
|
||||||
term_details.mode_of_payment = term.mode_of_payment
|
term_details.mode_of_payment = term.mode_of_payment
|
||||||
|
|
||||||
|
|||||||
@ -262,7 +262,8 @@ def copy_attributes_to_variant(item, variant):
|
|||||||
# copy non no-copy fields
|
# copy non no-copy fields
|
||||||
|
|
||||||
exclude_fields = ["naming_series", "item_code", "item_name", "show_in_website",
|
exclude_fields = ["naming_series", "item_code", "item_name", "show_in_website",
|
||||||
"show_variant_in_website", "opening_stock", "variant_of", "valuation_rate"]
|
"show_variant_in_website", "opening_stock", "variant_of", "valuation_rate",
|
||||||
|
"has_variants", "attributes"]
|
||||||
|
|
||||||
if item.variant_based_on=='Manufacturer':
|
if item.variant_based_on=='Manufacturer':
|
||||||
# don't copy manufacturer values if based on part no
|
# don't copy manufacturer values if based on part no
|
||||||
|
|||||||
@ -17,10 +17,12 @@ class AmazonMWSSettings(Document):
|
|||||||
else:
|
else:
|
||||||
self.enable_sync = 0
|
self.enable_sync = 0
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def get_products_details(self):
|
def get_products_details(self):
|
||||||
if self.enable_amazon == 1:
|
if self.enable_amazon == 1:
|
||||||
frappe.enqueue('erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_methods.get_products_details')
|
frappe.enqueue('erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_methods.get_products_details')
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def get_order_details(self):
|
def get_order_details(self):
|
||||||
if self.enable_amazon == 1:
|
if self.enable_amazon == 1:
|
||||||
after_date = dateutil.parser.parse(self.after_date).strftime("%Y-%m-%d")
|
after_date = dateutil.parser.parse(self.after_date).strftime("%Y-%m-%d")
|
||||||
|
|||||||
@ -50,6 +50,7 @@ class TherapyType(Document):
|
|||||||
|
|
||||||
self.db_set('change_in_item', 0)
|
self.db_set('change_in_item', 0)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def add_exercises(self):
|
def add_exercises(self):
|
||||||
exercises = self.get_exercises_for_body_parts()
|
exercises = self.get_exercises_for_body_parts()
|
||||||
last_idx = max([cint(d.idx) for d in self.get('exercises')] or [0,])
|
last_idx = max([cint(d.idx) for d in self.get('exercises')] or [0,])
|
||||||
|
|||||||
@ -34,7 +34,7 @@ frappe.ui.form.on('Employee Advance', {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query('salary_component', function(doc) {
|
frm.set_query('salary_component', function() {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
"type": "Deduction"
|
"type": "Deduction"
|
||||||
@ -44,16 +44,17 @@ frappe.ui.form.on('Employee Advance', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
if (frm.doc.docstatus===1
|
if (frm.doc.docstatus === 1 &&
|
||||||
&& (flt(frm.doc.paid_amount) < flt(frm.doc.advance_amount))
|
(flt(frm.doc.paid_amount) < flt(frm.doc.advance_amount)) &&
|
||||||
&& frappe.model.can_create("Payment Entry")) {
|
frappe.model.can_create("Payment Entry")) {
|
||||||
frm.add_custom_button(__('Payment'),
|
frm.add_custom_button(__('Payment'),
|
||||||
function() { frm.events.make_payment_entry(frm); }, __('Create'));
|
function () {
|
||||||
}
|
frm.events.make_payment_entry(frm);
|
||||||
else if (
|
}, __('Create'));
|
||||||
frm.doc.docstatus === 1
|
} else if (
|
||||||
&& flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) - flt(frm.doc.return_amount)
|
frm.doc.docstatus === 1 &&
|
||||||
&& frappe.model.can_create("Expense Claim")
|
flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) - flt(frm.doc.return_amount) &&
|
||||||
|
frappe.model.can_create("Expense Claim")
|
||||||
) {
|
) {
|
||||||
frm.add_custom_button(
|
frm.add_custom_button(
|
||||||
__("Expense Claim"),
|
__("Expense Claim"),
|
||||||
@ -64,8 +65,8 @@ frappe.ui.form.on('Employee Advance', {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frm.doc.docstatus === 1
|
if (frm.doc.docstatus === 1 &&
|
||||||
&& (flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) && flt(frm.doc.paid_amount) != flt(frm.doc.return_amount))) {
|
(flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) && flt(frm.doc.paid_amount) != flt(frm.doc.return_amount))) {
|
||||||
|
|
||||||
if (frm.doc.repay_unclaimed_amount_from_salary == 0 && frappe.model.can_create("Journal Entry")) {
|
if (frm.doc.repay_unclaimed_amount_from_salary == 0 && frappe.model.can_create("Journal Entry")) {
|
||||||
frm.add_custom_button(__("Return"), function() {
|
frm.add_custom_button(__("Return"), function() {
|
||||||
@ -215,8 +216,8 @@ frappe.ui.form.on('Employee Advance', {
|
|||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
frm.set_value("exchange_rate", flt(r.message));
|
frm.set_value("exchange_rate", flt(r.message));
|
||||||
frm.set_df_property('exchange_rate', 'hidden', 0);
|
frm.set_df_property('exchange_rate', 'hidden', 0);
|
||||||
frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency
|
frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency +
|
||||||
+ " = [?] " + company_currency);
|
" = [?] " + company_currency);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
0
erpnext/hr/doctype/employee_referral/__init__.py
Normal file
0
erpnext/hr/doctype/employee_referral/__init__.py
Normal file
68
erpnext/hr/doctype/employee_referral/employee_referral.js
Normal file
68
erpnext/hr/doctype/employee_referral/employee_referral.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on("Employee Referral", {
|
||||||
|
refresh: function(frm) {
|
||||||
|
if (frm.doc.docstatus === 1 && frm.doc.status === "Pending") {
|
||||||
|
frm.add_custom_button(__("Reject Employee Referral"), function() {
|
||||||
|
frappe.confirm(
|
||||||
|
__("Are you sure you want to reject the Employee Referral?"),
|
||||||
|
function() {
|
||||||
|
frm.doc.status = "Rejected";
|
||||||
|
frm.dirty();
|
||||||
|
frm.save_or_update();
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.add_custom_button(__("Create Job Applicant"), function() {
|
||||||
|
frm.events.create_job_applicant(frm);
|
||||||
|
}).addClass("btn-primary");
|
||||||
|
}
|
||||||
|
|
||||||
|
// To check whether Payment is done or not
|
||||||
|
if (frm.doc.docstatus === 1 && frm.doc.status === "Accepted") {
|
||||||
|
frappe.db.get_list("Additional Salary", {
|
||||||
|
filters: {
|
||||||
|
ref_docname: cur_frm.doc.name,
|
||||||
|
docstatus: 1
|
||||||
|
},
|
||||||
|
fields: ["count(name) as additional_salary_count"]
|
||||||
|
}).then((data) => {
|
||||||
|
|
||||||
|
let additional_salary_count = data[0].additional_salary_count;
|
||||||
|
|
||||||
|
if (frm.doc.is_applicable_for_referral_bonus && !additional_salary_count) {
|
||||||
|
frm.add_custom_button(__("Create Additional Salary"), function() {
|
||||||
|
frm.events.create_additional_salary(frm);
|
||||||
|
}).addClass("btn-primary");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
create_job_applicant: function(frm) {
|
||||||
|
frappe.model.open_mapped_doc({
|
||||||
|
method: "erpnext.hr.doctype.employee_referral.employee_referral.create_job_applicant",
|
||||||
|
frm: frm
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
create_additional_salary: function(frm) {
|
||||||
|
frappe.call({
|
||||||
|
method: "erpnext.hr.doctype.employee_referral.employee_referral.create_additional_salary",
|
||||||
|
args: {
|
||||||
|
doc: frm.doc
|
||||||
|
},
|
||||||
|
callback: function (r) {
|
||||||
|
var doclist = frappe.model.sync(r.message);
|
||||||
|
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
294
erpnext/hr/doctype/employee_referral/employee_referral.json
Normal file
294
erpnext/hr/doctype/employee_referral/employee_referral.json
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"autoname": "format:HR-REF-{####}",
|
||||||
|
"creation": "2021-03-23 14:54:45.047051",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"first_name",
|
||||||
|
"last_name",
|
||||||
|
"full_name",
|
||||||
|
"email",
|
||||||
|
"contact_no",
|
||||||
|
"resume",
|
||||||
|
"resume_link",
|
||||||
|
"column_break_6",
|
||||||
|
"date",
|
||||||
|
"status",
|
||||||
|
"for_designation",
|
||||||
|
"current_employer",
|
||||||
|
"current_job_title",
|
||||||
|
"referrer_details_section",
|
||||||
|
"referrer",
|
||||||
|
"referrer_name",
|
||||||
|
"column_break_14",
|
||||||
|
"is_applicable_for_referral_bonus",
|
||||||
|
"referral_payment_status",
|
||||||
|
"department",
|
||||||
|
"additional_information_section",
|
||||||
|
"qualification_reason",
|
||||||
|
"work_references",
|
||||||
|
"amended_from"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "first_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "First Name ",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "last_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Last Name",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "full_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Full Name",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "contact_no",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Contact No.",
|
||||||
|
"options": "Phone"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "current_employer",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Current Employer "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_6",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Date",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
|
"fieldname": "status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Status",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Pending\nIn Process\nAccepted\nRejected",
|
||||||
|
"permlevel": 1,
|
||||||
|
"read_only": 1,
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "current_job_title",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Current Job Title"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "resume",
|
||||||
|
"fieldtype": "Attach",
|
||||||
|
"label": "Resume"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "referrer_details_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Referrer Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "employee.department",
|
||||||
|
"fieldname": "department",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Department",
|
||||||
|
"options": "Department",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "additional_information_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Additional Information "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "work_references",
|
||||||
|
"fieldtype": "Text Editor",
|
||||||
|
"label": "Work References"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "amended_from",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Amended From",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Employee Referral",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_14",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "for_designation",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "For Designation ",
|
||||||
|
"options": "Designation",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "email",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Email",
|
||||||
|
"options": "Email",
|
||||||
|
"reqd": 1,
|
||||||
|
"unique": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "is_applicable_for_referral_bonus",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Is Applicable for Referral Bonus"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "qualification_reason",
|
||||||
|
"fieldtype": "Text Editor",
|
||||||
|
"label": "Why is this Candidate Qualified for this Position?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "referrer",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Referrer",
|
||||||
|
"options": "Employee",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "referrer.employee_name",
|
||||||
|
"fieldname": "referrer_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Referrer Name",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "resume_link",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Resume Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "referral_payment_status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Referral Bonus Payment Status",
|
||||||
|
"options": "\nUnpaid\nPaid",
|
||||||
|
"read_only": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"is_submittable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2021-04-26 21:21:38.094086",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "HR",
|
||||||
|
"name": "Employee Referral",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"amend": 1,
|
||||||
|
"create": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Employee",
|
||||||
|
"share": 1,
|
||||||
|
"submit": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"amend": 1,
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "HR Manager",
|
||||||
|
"share": 1,
|
||||||
|
"submit": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"amend": 1,
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "HR User",
|
||||||
|
"share": 1,
|
||||||
|
"submit": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"permlevel": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "HR Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"permlevel": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "HR User",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"permlevel": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Employee",
|
||||||
|
"share": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"title_field": "full_name"
|
||||||
|
}
|
||||||
71
erpnext/hr/doctype/employee_referral/employee_referral.py
Normal file
71
erpnext/hr/doctype/employee_referral/employee_referral.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.utils import get_link_to_form
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class EmployeeReferral(Document):
|
||||||
|
def validate(self):
|
||||||
|
self.set_full_name()
|
||||||
|
self.set_referral_bonus_payment_status()
|
||||||
|
|
||||||
|
def set_full_name(self):
|
||||||
|
self.full_name = " ".join(filter(None, [self.first_name, self.last_name]))
|
||||||
|
|
||||||
|
def set_referral_bonus_payment_status(self):
|
||||||
|
if not self.is_applicable_for_referral_bonus:
|
||||||
|
self.referral_payment_status = ""
|
||||||
|
else:
|
||||||
|
if not self.referral_payment_status:
|
||||||
|
self.referral_payment_status = "Unpaid"
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def create_job_applicant(source_name, target_doc=None):
|
||||||
|
emp_ref = frappe.get_doc("Employee Referral", source_name)
|
||||||
|
#just for Api call if some set status apart from default Status
|
||||||
|
status = emp_ref.status
|
||||||
|
if emp_ref.status in ["Pending", "In process"]:
|
||||||
|
status = "Open"
|
||||||
|
|
||||||
|
job_applicant = frappe.new_doc("Job Applicant")
|
||||||
|
job_applicant.employee_referral = emp_ref.name
|
||||||
|
job_applicant.status = status
|
||||||
|
job_applicant.applicant_name = emp_ref.full_name
|
||||||
|
job_applicant.email_id = emp_ref.email
|
||||||
|
job_applicant.phone_number = emp_ref.contact_no
|
||||||
|
job_applicant.resume_attachment = emp_ref.resume
|
||||||
|
job_applicant.resume_link = emp_ref.resume_link
|
||||||
|
job_applicant.save()
|
||||||
|
|
||||||
|
frappe.msgprint(_("Job Applicant {0} created successfully.").format(
|
||||||
|
get_link_to_form("Job Applicant", job_applicant.name)),
|
||||||
|
title=_("Success"), indicator="green")
|
||||||
|
|
||||||
|
emp_ref.db_set("status", "In Process")
|
||||||
|
|
||||||
|
return job_applicant
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def create_additional_salary(doc):
|
||||||
|
import json
|
||||||
|
from six import string_types
|
||||||
|
|
||||||
|
if isinstance(doc, string_types):
|
||||||
|
doc = frappe._dict(json.loads(doc))
|
||||||
|
|
||||||
|
if not frappe.db.exists("Additional Salary", {"ref_docname": doc.name}):
|
||||||
|
additional_salary = frappe.new_doc("Additional Salary")
|
||||||
|
additional_salary.employee = doc.referrer
|
||||||
|
additional_salary.company = frappe.db.get_value("Employee", doc.referrer, "company")
|
||||||
|
additional_salary.overwrite_salary_structure_amount = 0
|
||||||
|
additional_salary.ref_doctype = doc.doctype
|
||||||
|
additional_salary.ref_docname = doc.name
|
||||||
|
|
||||||
|
return additional_salary
|
||||||
|
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
def get_data():
|
||||||
|
return {
|
||||||
|
'fieldname': 'employee_referral',
|
||||||
|
'non_standard_fieldnames': {
|
||||||
|
'Additional Salary': 'ref_docname'
|
||||||
|
},
|
||||||
|
'transactions': [
|
||||||
|
{
|
||||||
|
'items': ['Job Applicant', 'Additional Salary']
|
||||||
|
},
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
frappe.listview_settings['Employee Referral'] = {
|
||||||
|
add_fields: ["status"],
|
||||||
|
get_indicator: function (doc) {
|
||||||
|
if (doc.status == "Pending") {
|
||||||
|
return [__(doc.status), "grey", "status,=," + doc.status];
|
||||||
|
} else if (doc.status == "In Process") {
|
||||||
|
return [__(doc.status), "orange", "status,=," + doc.status];
|
||||||
|
} else if (doc.status == "Accepted") {
|
||||||
|
return [__(doc.status), "green", "status,=," + doc.status];
|
||||||
|
} else if (doc.status == "Rejected") {
|
||||||
|
return [__(doc.status), "red", "status,=," + doc.status];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe.utils import today
|
||||||
|
from erpnext.hr.doctype.designation.test_designation import create_designation
|
||||||
|
from erpnext.hr.doctype.employee_referral.employee_referral import create_job_applicant, create_additional_salary
|
||||||
|
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
class TestEmployeeReferral(unittest.TestCase):
|
||||||
|
def test_workflow_and_status_sync(self):
|
||||||
|
emp_ref = create_employee_referral()
|
||||||
|
|
||||||
|
#Check Initial status
|
||||||
|
self.assertTrue(emp_ref.status, "Pending")
|
||||||
|
|
||||||
|
job_applicant = create_job_applicant(emp_ref.name)
|
||||||
|
|
||||||
|
|
||||||
|
#Check status sync
|
||||||
|
emp_ref.reload()
|
||||||
|
self.assertTrue(emp_ref.status, "In Process")
|
||||||
|
|
||||||
|
job_applicant.reload()
|
||||||
|
job_applicant.status = "Rejected"
|
||||||
|
job_applicant.save()
|
||||||
|
|
||||||
|
emp_ref.reload()
|
||||||
|
self.assertTrue(emp_ref.status, "Rejected")
|
||||||
|
|
||||||
|
job_applicant.reload()
|
||||||
|
job_applicant.status = "Accepted"
|
||||||
|
job_applicant.save()
|
||||||
|
|
||||||
|
emp_ref.reload()
|
||||||
|
self.assertTrue(emp_ref.status, "Accepted")
|
||||||
|
|
||||||
|
|
||||||
|
# Check for Referral reference in additional salary
|
||||||
|
|
||||||
|
add_sal = create_additional_salary(emp_ref)
|
||||||
|
self.assertTrue(add_sal.ref_docname, emp_ref.name)
|
||||||
|
|
||||||
|
|
||||||
|
def create_employee_referral():
|
||||||
|
emp_ref = frappe.new_doc("Employee Referral")
|
||||||
|
emp_ref.first_name = "Mahesh"
|
||||||
|
emp_ref.last_name = "Singh"
|
||||||
|
emp_ref.email = "a@b.c"
|
||||||
|
emp_ref.date = today()
|
||||||
|
emp_ref.for_designation = create_designation().name
|
||||||
|
emp_ref.referrer = make_employee("testassetmovemp@example.com", company="_Test Company")
|
||||||
|
emp_ref.is_applicable_for_employee_referral_compensation = 1
|
||||||
|
emp_ref.save()
|
||||||
|
emp_ref.submit()
|
||||||
|
|
||||||
|
return emp_ref
|
||||||
@ -1,596 +1,156 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"autoname": "HR-EMP-SEP-.YYYY.-.#####",
|
"autoname": "HR-EMP-SEP-.YYYY.-.#####",
|
||||||
"beta": 0,
|
|
||||||
"creation": "2018-05-10 02:29:16.740490",
|
"creation": "2018-05-10 02:29:16.740490",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "",
|
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"employee",
|
||||||
|
"employee_name",
|
||||||
|
"department",
|
||||||
|
"designation",
|
||||||
|
"employee_grade",
|
||||||
|
"column_break_7",
|
||||||
|
"company",
|
||||||
|
"boarding_status",
|
||||||
|
"resignation_letter_date",
|
||||||
|
"project",
|
||||||
|
"table_for_activity",
|
||||||
|
"employee_separation_template",
|
||||||
|
"activities",
|
||||||
|
"notify_users_by_email",
|
||||||
|
"section_break_14",
|
||||||
|
"exit_interview",
|
||||||
|
"amended_from"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "employee",
|
"fieldname": "employee",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"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": "Employee",
|
"label": "Employee",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Employee",
|
"options": "Employee",
|
||||||
"permlevel": 0,
|
"reqd": 1
|
||||||
"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,
|
|
||||||
"fetch_from": "employee.employee_name",
|
"fetch_from": "employee.employee_name",
|
||||||
"fieldname": "employee_name",
|
"fieldname": "employee_name",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Employee Name",
|
"label": "Employee Name",
|
||||||
"length": 0,
|
"read_only": 1
|
||||||
"no_copy": 0,
|
|
||||||
"options": "",
|
|
||||||
"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,
|
|
||||||
"fetch_from": "employee.resignation_letter_date",
|
"fetch_from": "employee.resignation_letter_date",
|
||||||
"fieldname": "resignation_letter_date",
|
"fieldname": "resignation_letter_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Resignation Letter Date",
|
"label": "Resignation Letter Date",
|
||||||
"length": 0,
|
"read_only": 1
|
||||||
"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": 1,
|
"allow_on_submit": 1,
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "boarding_status",
|
"fieldname": "boarding_status",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"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": "Status",
|
"label": "Status",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "\nPending\nIn Process\nCompleted",
|
"options": "\nPending\nIn Process\nCompleted",
|
||||||
"permlevel": 0,
|
"reqd": 1
|
||||||
"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_bulk_edit": 0,
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
"bold": 0,
|
"default": "0",
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "notify_users_by_email",
|
"fieldname": "notify_users_by_email",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 0,
|
"label": "Notify users by email"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Notify users by email",
|
|
||||||
"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,
|
|
||||||
"fieldname": "column_break_7",
|
"fieldname": "column_break_7",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break"
|
||||||
"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,
|
|
||||||
"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,
|
|
||||||
"fieldname": "employee_separation_template",
|
"fieldname": "employee_separation_template",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"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": "Employee Separation Template",
|
"label": "Employee Separation Template",
|
||||||
"length": 0,
|
"options": "Employee Separation Template"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Employee Separation Template",
|
|
||||||
"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,
|
|
||||||
"fetch_from": "employee.company",
|
"fetch_from": "employee.company",
|
||||||
"fieldname": "company",
|
"fieldname": "company",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"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": "Company",
|
"label": "Company",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Company",
|
"options": "Company",
|
||||||
"permlevel": 0,
|
"reqd": 1
|
||||||
"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,
|
|
||||||
"fieldname": "project",
|
"fieldname": "project",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"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": "Project",
|
"label": "Project",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Project",
|
"options": "Project",
|
||||||
"permlevel": 0,
|
"read_only": 1
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"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,
|
|
||||||
"fetch_from": "employee.department",
|
"fetch_from": "employee.department",
|
||||||
"fieldname": "department",
|
"fieldname": "department",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Department",
|
"label": "Department",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Department",
|
"options": "Department",
|
||||||
"permlevel": 0,
|
"read_only": 1
|
||||||
"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,
|
|
||||||
"fetch_from": "employee.designation",
|
"fetch_from": "employee.designation",
|
||||||
"fieldname": "designation",
|
"fieldname": "designation",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Designation",
|
"label": "Designation",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Designation",
|
"options": "Designation",
|
||||||
"permlevel": 0,
|
"read_only": 1
|
||||||
"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,
|
|
||||||
"fetch_from": "employee.grade",
|
"fetch_from": "employee.grade",
|
||||||
"fieldname": "employee_grade",
|
"fieldname": "employee_grade",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"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": "Employee Grade",
|
"label": "Employee Grade",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Employee Grade",
|
"options": "Employee Grade",
|
||||||
"permlevel": 0,
|
"read_only": 1
|
||||||
"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,
|
|
||||||
"fieldname": "table_for_activity",
|
"fieldname": "table_for_activity",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"hidden": 0,
|
"label": "Separation Activities"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "",
|
|
||||||
"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": 1,
|
"allow_on_submit": 1,
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "activities",
|
"fieldname": "activities",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"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": "Activities",
|
"label": "Activities",
|
||||||
"length": 0,
|
"options": "Employee Boarding Activity"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Employee Boarding Activity",
|
|
||||||
"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,
|
|
||||||
"fieldname": "section_break_14",
|
"fieldname": "section_break_14",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break"
|
||||||
"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,
|
|
||||||
"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,
|
|
||||||
"fieldname": "exit_interview",
|
"fieldname": "exit_interview",
|
||||||
"fieldtype": "Text Editor",
|
"fieldtype": "Text Editor",
|
||||||
"hidden": 0,
|
"label": "Exit Interview Summary"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Exit Interview Summary",
|
|
||||||
"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,
|
|
||||||
"fieldname": "amended_from",
|
"fieldname": "amended_from",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"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": "Amended From",
|
"label": "Amended From",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Employee Separation",
|
"options": "Employee Separation",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_hide_if_no_value": 0,
|
"read_only": 1
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"idx": 0,
|
|
||||||
"image_view": 0,
|
|
||||||
"in_create": 0,
|
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"issingle": 0,
|
"links": [],
|
||||||
"istable": 0,
|
"modified": "2021-04-28 15:58:36.020196",
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2019-08-03 16:15:39.025898",
|
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Employee Separation",
|
"name": "Employee Separation",
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
@ -600,27 +160,18 @@
|
|||||||
"delete": 1,
|
"delete": 1,
|
||||||
"email": 1,
|
"email": 1,
|
||||||
"export": 1,
|
"export": 1,
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "System Manager",
|
"role": "System Manager",
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"submit": 1,
|
"submit": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"title_field": "employee_name",
|
"title_field": "employee_name",
|
||||||
"track_changes": 1,
|
"track_changes": 1
|
||||||
"track_seen": 0,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
}
|
||||||
@ -18,7 +18,7 @@ class TestEmployeeSeparation(unittest.TestCase):
|
|||||||
'activity_name': 'Deactivate Employee',
|
'activity_name': 'Deactivate Employee',
|
||||||
'role': 'HR User'
|
'role': 'HR User'
|
||||||
})
|
})
|
||||||
separation.status = 'Pending'
|
separation.boarding_status = 'Pending'
|
||||||
separation.insert()
|
separation.insert()
|
||||||
separation.submit()
|
separation.submit()
|
||||||
self.assertEqual(separation.docstatus, 1)
|
self.assertEqual(separation.docstatus, 1)
|
||||||
|
|||||||
@ -16,6 +16,7 @@ class HolidayList(Document):
|
|||||||
self.validate_days()
|
self.validate_days()
|
||||||
self.total_holidays = len(self.holidays)
|
self.total_holidays = len(self.holidays)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def get_weekly_off_dates(self):
|
def get_weekly_off_dates(self):
|
||||||
self.validate_values()
|
self.validate_values()
|
||||||
date_list = self.get_weekly_off_date_list(self.from_date, self.to_date)
|
date_list = self.get_weekly_off_date_list(self.from_date, self.to_date)
|
||||||
@ -61,6 +62,7 @@ class HolidayList(Document):
|
|||||||
|
|
||||||
return date_list
|
return date_list
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def clear_table(self):
|
def clear_table(self):
|
||||||
self.set('holidays', [])
|
self.set('holidays', [])
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
"retirement_age",
|
"retirement_age",
|
||||||
"emp_created_by",
|
"emp_created_by",
|
||||||
"column_break_4",
|
"column_break_4",
|
||||||
|
"standard_working_hours",
|
||||||
"stop_birthday_reminders",
|
"stop_birthday_reminders",
|
||||||
"expense_approver_mandatory_in_expense_claim",
|
"expense_approver_mandatory_in_expense_claim",
|
||||||
"leave_settings",
|
"leave_settings",
|
||||||
@ -143,13 +144,18 @@
|
|||||||
"fieldname": "send_leave_notification",
|
"fieldname": "send_leave_notification",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Send Leave Notification"
|
"label": "Send Leave Notification"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "standard_working_hours",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Standard Working Hours"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-cog",
|
"icon": "fa fa-cog",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-03-14 02:04:22.907159",
|
"modified": "2021-04-26 10:52:56.192773",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "HR Settings",
|
"name": "HR Settings",
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
"job_title",
|
"job_title",
|
||||||
"source",
|
"source",
|
||||||
"source_name",
|
"source_name",
|
||||||
|
"employee_referral",
|
||||||
"applicant_rating",
|
"applicant_rating",
|
||||||
"section_break_6",
|
"section_break_6",
|
||||||
"notes",
|
"notes",
|
||||||
@ -152,13 +153,20 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Currency",
|
"label": "Currency",
|
||||||
"options": "Currency"
|
"options": "Currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "employee_referral",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Employee Referral",
|
||||||
|
"options": "Employee Referral",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-user",
|
"icon": "fa fa-user",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-09-18 12:39:02.557563",
|
"modified": "2021-03-24 15:51:11.117517",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Job Applicant",
|
"name": "Job Applicant",
|
||||||
|
|||||||
@ -28,10 +28,21 @@ class JobApplicant(Document):
|
|||||||
if self.email_id:
|
if self.email_id:
|
||||||
validate_email_address(self.email_id, True)
|
validate_email_address(self.email_id, True)
|
||||||
|
|
||||||
|
if self.employee_referral:
|
||||||
|
self.set_status_for_employee_referral()
|
||||||
|
|
||||||
if not self.applicant_name and self.email_id:
|
if not self.applicant_name and self.email_id:
|
||||||
guess = self.email_id.split('@')[0]
|
guess = self.email_id.split('@')[0]
|
||||||
self.applicant_name = ' '.join([p.capitalize() for p in guess.split('.')])
|
self.applicant_name = ' '.join([p.capitalize() for p in guess.split('.')])
|
||||||
|
|
||||||
|
def set_status_for_employee_referral(self):
|
||||||
|
emp_ref = frappe.get_doc("Employee Referral", self.employee_referral)
|
||||||
|
if self.status in ["Open", "Replied", "Hold"]:
|
||||||
|
emp_ref.db_set("status", "In Process")
|
||||||
|
elif self.status in ["Accepted", "Rejected"]:
|
||||||
|
emp_ref.db_set("status", self.status)
|
||||||
|
|
||||||
|
|
||||||
def check_email_id_is_unique(self):
|
def check_email_id_is_unique(self):
|
||||||
if self.email_id:
|
if self.email_id:
|
||||||
names = frappe.db.sql_list("""select name from `tabJob Applicant`
|
names = frappe.db.sql_list("""select name from `tabJob Applicant`
|
||||||
|
|||||||
@ -29,6 +29,7 @@ class LeaveControlPanel(Document):
|
|||||||
frappe.throw(_("{0} is required").format(self.meta.get_label(f)))
|
frappe.throw(_("{0} is required").format(self.meta.get_label(f)))
|
||||||
self.validate_from_to_dates('from_date', 'to_date')
|
self.validate_from_to_dates('from_date', 'to_date')
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def allocate_leave(self):
|
def allocate_leave(self):
|
||||||
self.validate_values()
|
self.validate_values()
|
||||||
leave_allocated_for = []
|
leave_allocated_for = []
|
||||||
|
|||||||
@ -32,13 +32,15 @@ class EmployeeBoardingController(Document):
|
|||||||
project_name += self.job_applicant
|
project_name += self.job_applicant
|
||||||
else:
|
else:
|
||||||
project_name += self.employee
|
project_name += self.employee
|
||||||
|
|
||||||
project = frappe.get_doc({
|
project = frappe.get_doc({
|
||||||
"doctype": "Project",
|
"doctype": "Project",
|
||||||
"project_name": project_name,
|
"project_name": project_name,
|
||||||
"expected_start_date": self.date_of_joining if self.doctype == "Employee Onboarding" else self.resignation_letter_date,
|
"expected_start_date": self.date_of_joining if self.doctype == "Employee Onboarding" else self.resignation_letter_date,
|
||||||
"department": self.department,
|
"department": self.department,
|
||||||
"company": self.company
|
"company": self.company
|
||||||
}).insert(ignore_permissions=True)
|
}).insert(ignore_permissions=True, ignore_mandatory=True)
|
||||||
|
|
||||||
self.db_set("project", project.name)
|
self.db_set("project", project.name)
|
||||||
self.db_set("boarding_status", "Pending")
|
self.db_set("boarding_status", "Pending")
|
||||||
self.reload()
|
self.reload()
|
||||||
|
|||||||
@ -520,6 +520,15 @@
|
|||||||
"onboard": 1,
|
"onboard": 1,
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Employee Referral",
|
||||||
|
"link_to": "Employee Referral",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"dependencies": "",
|
"dependencies": "",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@ -814,7 +823,7 @@
|
|||||||
"type": "Link"
|
"type": "Link"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2021-03-24 17:35:21.483297",
|
"modified": "2021-04-26 13:36:15.413819",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "HR",
|
"name": "HR",
|
||||||
|
|||||||
@ -44,6 +44,7 @@ class Loan(AccountsController):
|
|||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.unlink_loan_security_pledge()
|
self.unlink_loan_security_pledge()
|
||||||
|
self.ignore_linked_doctypes = ['GL Entry']
|
||||||
|
|
||||||
def set_missing_fields(self):
|
def set_missing_fields(self):
|
||||||
if not self.company:
|
if not self.company:
|
||||||
@ -70,7 +71,6 @@ class Loan(AccountsController):
|
|||||||
frappe.throw(_("Repay From Salary can be selected only for term loans"))
|
frappe.throw(_("Repay From Salary can be selected only for term loans"))
|
||||||
|
|
||||||
def make_repayment_schedule(self):
|
def make_repayment_schedule(self):
|
||||||
|
|
||||||
if not self.repayment_start_date:
|
if not self.repayment_start_date:
|
||||||
frappe.throw(_("Repayment Start Date is mandatory for term loans"))
|
frappe.throw(_("Repayment Start Date is mandatory for term loans"))
|
||||||
|
|
||||||
@ -78,10 +78,9 @@ class Loan(AccountsController):
|
|||||||
payment_date = self.repayment_start_date
|
payment_date = self.repayment_start_date
|
||||||
balance_amount = self.loan_amount
|
balance_amount = self.loan_amount
|
||||||
while(balance_amount > 0):
|
while(balance_amount > 0):
|
||||||
interest_amount = rounded(balance_amount * flt(self.rate_of_interest) / (12*100))
|
interest_amount = flt(balance_amount * flt(self.rate_of_interest) / (12*100))
|
||||||
principal_amount = self.monthly_repayment_amount - interest_amount
|
principal_amount = self.monthly_repayment_amount - interest_amount
|
||||||
balance_amount = rounded(balance_amount + interest_amount - self.monthly_repayment_amount)
|
balance_amount = flt(balance_amount + interest_amount - self.monthly_repayment_amount)
|
||||||
|
|
||||||
if balance_amount < 0:
|
if balance_amount < 0:
|
||||||
principal_amount += balance_amount
|
principal_amount += balance_amount
|
||||||
balance_amount = 0.0
|
balance_amount = 0.0
|
||||||
@ -195,7 +194,8 @@ def request_loan_closure(loan, posting_date=None):
|
|||||||
posting_date = getdate()
|
posting_date = getdate()
|
||||||
|
|
||||||
amounts = calculate_amounts(loan, posting_date)
|
amounts = calculate_amounts(loan, posting_date)
|
||||||
pending_amount = amounts['payable_amount'] + amounts['unaccrued_interest']
|
pending_amount = amounts['pending_principal_amount'] + amounts['unaccrued_interest'] + \
|
||||||
|
amounts['interest_amount'] + amounts['penalty_amount']
|
||||||
|
|
||||||
loan_type = frappe.get_value('Loan', loan, 'loan_type')
|
loan_type = frappe.get_value('Loan', loan, 'loan_type')
|
||||||
write_off_limit = frappe.get_value('Loan Type', loan_type, 'write_off_amount')
|
write_off_limit = frappe.get_value('Loan Type', loan_type, 'write_off_amount')
|
||||||
|
|||||||
@ -56,25 +56,25 @@ class TestLoan(unittest.TestCase):
|
|||||||
def test_loan(self):
|
def test_loan(self):
|
||||||
loan = frappe.get_doc("Loan", {"applicant":self.applicant1})
|
loan = frappe.get_doc("Loan", {"applicant":self.applicant1})
|
||||||
self.assertEquals(loan.monthly_repayment_amount, 15052)
|
self.assertEquals(loan.monthly_repayment_amount, 15052)
|
||||||
self.assertEquals(loan.total_interest_payable, 21034)
|
self.assertEquals(flt(loan.total_interest_payable, 0), 21034)
|
||||||
self.assertEquals(loan.total_payment, 301034)
|
self.assertEquals(flt(loan.total_payment, 0), 301034)
|
||||||
|
|
||||||
schedule = loan.repayment_schedule
|
schedule = loan.repayment_schedule
|
||||||
|
|
||||||
self.assertEqual(len(schedule), 20)
|
self.assertEqual(len(schedule), 20)
|
||||||
|
|
||||||
for idx, principal_amount, interest_amount, balance_loan_amount in [[3, 13369, 1683, 227079], [19, 14941, 105, 0], [17, 14740, 312, 29785]]:
|
for idx, principal_amount, interest_amount, balance_loan_amount in [[3, 13369, 1683, 227080], [19, 14941, 105, 0], [17, 14740, 312, 29785]]:
|
||||||
self.assertEqual(schedule[idx].principal_amount, principal_amount)
|
self.assertEqual(flt(schedule[idx].principal_amount, 0), principal_amount)
|
||||||
self.assertEqual(schedule[idx].interest_amount, interest_amount)
|
self.assertEqual(flt(schedule[idx].interest_amount, 0), interest_amount)
|
||||||
self.assertEqual(schedule[idx].balance_loan_amount, balance_loan_amount)
|
self.assertEqual(flt(schedule[idx].balance_loan_amount, 0), balance_loan_amount)
|
||||||
|
|
||||||
loan.repayment_method = "Repay Fixed Amount per Period"
|
loan.repayment_method = "Repay Fixed Amount per Period"
|
||||||
loan.monthly_repayment_amount = 14000
|
loan.monthly_repayment_amount = 14000
|
||||||
loan.save()
|
loan.save()
|
||||||
|
|
||||||
self.assertEquals(len(loan.repayment_schedule), 22)
|
self.assertEquals(len(loan.repayment_schedule), 22)
|
||||||
self.assertEquals(loan.total_interest_payable, 22712)
|
self.assertEquals(flt(loan.total_interest_payable, 0), 22712)
|
||||||
self.assertEquals(loan.total_payment, 302712)
|
self.assertEquals(flt(loan.total_payment, 0), 302712)
|
||||||
|
|
||||||
def test_loan_with_security(self):
|
def test_loan_with_security(self):
|
||||||
|
|
||||||
|
|||||||
@ -435,7 +435,6 @@ def get_amounts(amounts, against_loan, posting_date):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def calculate_amounts(against_loan, posting_date, payment_type=''):
|
def calculate_amounts(against_loan, posting_date, payment_type=''):
|
||||||
|
|
||||||
amounts = {
|
amounts = {
|
||||||
'penalty_amount': 0.0,
|
'penalty_amount': 0.0,
|
||||||
'interest_amount': 0.0,
|
'interest_amount': 0.0,
|
||||||
|
|||||||
@ -773,3 +773,4 @@ erpnext.patches.v13_0.fix_non_unique_represents_company
|
|||||||
erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing
|
erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing
|
||||||
erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021
|
erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021
|
||||||
erpnext.patches.v13_0.update_shipment_status
|
erpnext.patches.v13_0.update_shipment_status
|
||||||
|
erpnext.patches.v13_0.remove_attribute_field_from_item_variant_setting
|
||||||
|
|||||||
@ -22,5 +22,7 @@ def execute():
|
|||||||
|
|
||||||
frappe.delete_doc("Page", "bank-reconciliation", force=1)
|
frappe.delete_doc("Page", "bank-reconciliation", force=1)
|
||||||
|
|
||||||
|
frappe.reload_doc('accounts', 'doctype', 'bank_transaction')
|
||||||
|
|
||||||
rename_field("Bank Transaction", "debit", "deposit")
|
rename_field("Bank Transaction", "debit", "deposit")
|
||||||
rename_field("Bank Transaction", "credit", "withdrawal")
|
rename_field("Bank Transaction", "credit", "withdrawal")
|
||||||
|
|||||||
@ -0,0 +1,8 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
"""Remove has_variants and attribute fields from item variant settings."""
|
||||||
|
frappe.reload_doc("stock", "doctype", "Item Variant Settings")
|
||||||
|
|
||||||
|
frappe.db.sql("""delete from `tabVariant Field`
|
||||||
|
where field_name in ('attributes', 'has_variants')""")
|
||||||
@ -13,12 +13,19 @@ class AdditionalSalary(Document):
|
|||||||
if self.ref_doctype == "Employee Advance" and self.ref_docname:
|
if self.ref_doctype == "Employee Advance" and self.ref_docname:
|
||||||
frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", self.amount)
|
frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", self.amount)
|
||||||
|
|
||||||
|
self.update_employee_referral()
|
||||||
|
|
||||||
|
def on_cancel(self):
|
||||||
|
self.update_employee_referral(cancel=True)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_dates()
|
self.validate_dates()
|
||||||
self.validate_salary_structure()
|
self.validate_salary_structure()
|
||||||
self.validate_recurring_additional_salary_overlap()
|
self.validate_recurring_additional_salary_overlap()
|
||||||
|
self.validate_employee_referral()
|
||||||
|
|
||||||
if self.amount < 0:
|
if self.amount < 0:
|
||||||
frappe.throw(_("Amount should not be less than zero."))
|
frappe.throw(_("Amount should not be less than zero"))
|
||||||
|
|
||||||
def validate_salary_structure(self):
|
def validate_salary_structure(self):
|
||||||
if not frappe.db.exists('Salary Structure Assignment', {'employee': self.employee}):
|
if not frappe.db.exists('Salary Structure Assignment', {'employee': self.employee}):
|
||||||
@ -70,6 +77,27 @@ class AdditionalSalary(Document):
|
|||||||
if self.payroll_date and getdate(self.payroll_date) > getdate(relieving_date):
|
if self.payroll_date and getdate(self.payroll_date) > getdate(relieving_date):
|
||||||
frappe.throw(_("Payroll date can not be greater than employee's relieving date."))
|
frappe.throw(_("Payroll date can not be greater than employee's relieving date."))
|
||||||
|
|
||||||
|
def validate_employee_referral(self):
|
||||||
|
if self.ref_doctype == "Employee Referral":
|
||||||
|
referral_details = frappe.db.get_value("Employee Referral", self.ref_docname,
|
||||||
|
["is_applicable_for_referral_bonus", "status"], as_dict=1)
|
||||||
|
|
||||||
|
if not referral_details.is_applicable_for_referral_bonus:
|
||||||
|
frappe.throw(_("Employee Referral {0} is not applicable for referral bonus.").format(
|
||||||
|
self.ref_docname))
|
||||||
|
|
||||||
|
if self.type == "Deduction":
|
||||||
|
frappe.throw(_("Earning Salary Component is required for Employee Referral Bonus."))
|
||||||
|
|
||||||
|
if referral_details.status != "Accepted":
|
||||||
|
frappe.throw(_("Additional Salary for referral bonus can only be created against Employee Referral with status {0}").format(
|
||||||
|
frappe.bold("Accepted")))
|
||||||
|
|
||||||
|
def update_employee_referral(self, cancel=False):
|
||||||
|
if self.ref_doctype == "Employee Referral":
|
||||||
|
status = "Unpaid" if cancel else "Paid"
|
||||||
|
frappe.db.set_value("Employee Referral", self.ref_docname, "referral_payment_status", status)
|
||||||
|
|
||||||
def get_amount(self, sal_start_date, sal_end_date):
|
def get_amount(self, sal_start_date, sal_end_date):
|
||||||
start_date = getdate(sal_start_date)
|
start_date = getdate(sal_start_date)
|
||||||
end_date = getdate(sal_end_date)
|
end_date = getdate(sal_end_date)
|
||||||
@ -110,8 +138,7 @@ def get_additional_salaries(employee, start_date, end_date, component_type):
|
|||||||
for d in additional_salary_list:
|
for d in additional_salary_list:
|
||||||
if d.overwrite:
|
if d.overwrite:
|
||||||
if d.component in components_to_overwrite:
|
if d.component in components_to_overwrite:
|
||||||
frappe.throw(_("Multiple Additional Salaries with overwrite "
|
frappe.throw(_("Multiple Additional Salaries with overwrite property exist for Salary Component {0} between {1} and {2}.").format(
|
||||||
"property exist for Salary Component {0} between {1} and {2}.").format(
|
|
||||||
frappe.bold(d.component), start_date, end_date), title=_("Error"))
|
frappe.bold(d.component), start_date, end_date), title=_("Error"))
|
||||||
|
|
||||||
components_to_overwrite.append(d.component)
|
components_to_overwrite.append(d.component)
|
||||||
|
|||||||
@ -16,11 +16,11 @@ frappe.ui.form.on('Salary Structure', {
|
|||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
|
|
||||||
let help_button = $(`<a class = 'control-label'>
|
let help_button = $(`<a class = 'control-label'>
|
||||||
Condition and Formula Help
|
${__("Condition and Formula Help")}
|
||||||
</a>`).click(()=>{
|
</a>`).click(()=>{
|
||||||
|
|
||||||
let d = new frappe.ui.Dialog({
|
let d = new frappe.ui.Dialog({
|
||||||
title: 'Condition and Formula Help',
|
title: __('Condition and Formula Help'),
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
fieldname: 'msg_wrapper',
|
fieldname: 'msg_wrapper',
|
||||||
|
|||||||
@ -179,9 +179,6 @@ class Project(Document):
|
|||||||
if self.percent_complete == 100:
|
if self.percent_complete == 100:
|
||||||
self.status = "Completed"
|
self.status = "Completed"
|
||||||
|
|
||||||
else:
|
|
||||||
self.status = "Open"
|
|
||||||
|
|
||||||
def update_costing(self):
|
def update_costing(self):
|
||||||
from_time_sheet = frappe.db.sql("""select
|
from_time_sheet = frappe.db.sql("""select
|
||||||
sum(costing_amount) as costing_amount,
|
sum(costing_amount) as costing_amount,
|
||||||
|
|||||||
@ -32,7 +32,8 @@ frappe.ui.form.on("Task", {
|
|||||||
|
|
||||||
frm.set_query("parent_task", function () {
|
frm.set_query("parent_task", function () {
|
||||||
let filters = {
|
let filters = {
|
||||||
"is_group": 1
|
"is_group": 1,
|
||||||
|
"name": ["!=", frm.doc.name]
|
||||||
};
|
};
|
||||||
if (frm.doc.project) filters["project"] = frm.doc.project;
|
if (frm.doc.project) filters["project"] = frm.doc.project;
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -151,11 +151,11 @@ class TestTimesheet(unittest.TestCase):
|
|||||||
settings.save()
|
settings.save()
|
||||||
|
|
||||||
|
|
||||||
def make_salary_structure_for_timesheet(employee):
|
def make_salary_structure_for_timesheet(employee, company=None):
|
||||||
salary_structure_name = "Timesheet Salary Structure Test"
|
salary_structure_name = "Timesheet Salary Structure Test"
|
||||||
frequency = "Monthly"
|
frequency = "Monthly"
|
||||||
|
|
||||||
salary_structure = make_salary_structure(salary_structure_name, frequency, dont_submit=True)
|
salary_structure = make_salary_structure(salary_structure_name, frequency, company=company, dont_submit=True)
|
||||||
salary_structure.salary_component = "Timesheet Component"
|
salary_structure.salary_component = "Timesheet Component"
|
||||||
salary_structure.salary_slip_based_on_timesheet = 1
|
salary_structure.salary_slip_based_on_timesheet = 1
|
||||||
salary_structure.hour_rate = 50.0
|
salary_structure.hour_rate = 50.0
|
||||||
|
|||||||
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
frappe.query_reports["Employee Hours Utilization Based On Timesheet"] = {
|
||||||
|
"filters": [
|
||||||
|
{
|
||||||
|
fieldname: "company",
|
||||||
|
label: __("Company"),
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Company",
|
||||||
|
default: frappe.defaults.get_user_default("Company"),
|
||||||
|
reqd: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "from_date",
|
||||||
|
label: __("From Date"),
|
||||||
|
fieldtype: "Date",
|
||||||
|
default: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
|
||||||
|
reqd: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname:"to_date",
|
||||||
|
label: __("To Date"),
|
||||||
|
fieldtype: "Date",
|
||||||
|
default: frappe.datetime.now_date(),
|
||||||
|
reqd: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "employee",
|
||||||
|
label: __("Employee"),
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Employee"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "department",
|
||||||
|
label: __("Department"),
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Department"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "project",
|
||||||
|
label: __("Project"),
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Project"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"add_total_row": 0,
|
||||||
|
"columns": [],
|
||||||
|
"creation": "2021-04-05 19:23:43.838623",
|
||||||
|
"disable_prepared_report": 0,
|
||||||
|
"disabled": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Report",
|
||||||
|
"filters": [],
|
||||||
|
"idx": 0,
|
||||||
|
"is_standard": "Yes",
|
||||||
|
"modified": "2021-04-05 19:23:43.838623",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Projects",
|
||||||
|
"name": "Employee Hours Utilization Based On Timesheet",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"prepared_report": 0,
|
||||||
|
"ref_doctype": "Timesheet",
|
||||||
|
"report_name": "Employee Hours Utilization Based On Timesheet",
|
||||||
|
"report_type": "Script Report",
|
||||||
|
"roles": []
|
||||||
|
}
|
||||||
@ -0,0 +1,280 @@
|
|||||||
|
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.utils import flt, getdate
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
|
def execute(filters=None):
|
||||||
|
return EmployeeHoursReport(filters).run()
|
||||||
|
|
||||||
|
class EmployeeHoursReport:
|
||||||
|
'''Employee Hours Utilization Report Based On Timesheet'''
|
||||||
|
def __init__(self, filters=None):
|
||||||
|
self.filters = frappe._dict(filters or {})
|
||||||
|
|
||||||
|
self.from_date = getdate(self.filters.from_date)
|
||||||
|
self.to_date = getdate(self.filters.to_date)
|
||||||
|
|
||||||
|
self.validate_dates()
|
||||||
|
self.validate_standard_working_hours()
|
||||||
|
|
||||||
|
def validate_dates(self):
|
||||||
|
self.day_span = (self.to_date - self.from_date).days
|
||||||
|
|
||||||
|
if self.day_span <= 0:
|
||||||
|
frappe.throw(_('From Date must come before To Date'))
|
||||||
|
|
||||||
|
def validate_standard_working_hours(self):
|
||||||
|
self.standard_working_hours = frappe.db.get_single_value('HR Settings', 'standard_working_hours')
|
||||||
|
if not self.standard_working_hours:
|
||||||
|
msg = _('The metrics for this report are calculated based on the Standard Working Hours. Please set {0} in {1}.').format(
|
||||||
|
frappe.bold('Standard Working Hours'), frappe.utils.get_link_to_form('HR Settings', 'HR Settings'))
|
||||||
|
|
||||||
|
frappe.throw(msg)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.generate_columns()
|
||||||
|
self.generate_data()
|
||||||
|
self.generate_report_summary()
|
||||||
|
self.generate_chart_data()
|
||||||
|
|
||||||
|
return self.columns, self.data, None, self.chart, self.report_summary
|
||||||
|
|
||||||
|
def generate_columns(self):
|
||||||
|
self.columns = [
|
||||||
|
{
|
||||||
|
'label': _('Employee'),
|
||||||
|
'options': 'Employee',
|
||||||
|
'fieldname': 'employee',
|
||||||
|
'fieldtype': 'Link',
|
||||||
|
'width': 230
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': _('Department'),
|
||||||
|
'options': 'Department',
|
||||||
|
'fieldname': 'department',
|
||||||
|
'fieldtype': 'Link',
|
||||||
|
'width': 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': _('Total Hours (T)'),
|
||||||
|
'fieldname': 'total_hours',
|
||||||
|
'fieldtype': 'Float',
|
||||||
|
'width': 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': _('Billed Hours (B)'),
|
||||||
|
'fieldname': 'billed_hours',
|
||||||
|
'fieldtype': 'Float',
|
||||||
|
'width': 170
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': _('Non-Billed Hours (NB)'),
|
||||||
|
'fieldname': 'non_billed_hours',
|
||||||
|
'fieldtype': 'Float',
|
||||||
|
'width': 170
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': _('Untracked Hours (U)'),
|
||||||
|
'fieldname': 'untracked_hours',
|
||||||
|
'fieldtype': 'Float',
|
||||||
|
'width': 170
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': _('% Utilization (B + NB) / T'),
|
||||||
|
'fieldname': 'per_util',
|
||||||
|
'fieldtype': 'Percentage',
|
||||||
|
'width': 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': _('% Utilization (B / T)'),
|
||||||
|
'fieldname': 'per_util_billed_only',
|
||||||
|
'fieldtype': 'Percentage',
|
||||||
|
'width': 200
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
def generate_data(self):
|
||||||
|
self.generate_filtered_time_logs()
|
||||||
|
self.generate_stats_by_employee()
|
||||||
|
self.set_employee_department_and_name()
|
||||||
|
|
||||||
|
if self.filters.department:
|
||||||
|
self.filter_stats_by_department()
|
||||||
|
|
||||||
|
self.calculate_utilizations()
|
||||||
|
|
||||||
|
self.data = []
|
||||||
|
|
||||||
|
for emp, data in iteritems(self.stats_by_employee):
|
||||||
|
row = frappe._dict()
|
||||||
|
row['employee'] = emp
|
||||||
|
row.update(data)
|
||||||
|
self.data.append(row)
|
||||||
|
|
||||||
|
# Sort by descending order of percentage utilization
|
||||||
|
self.data.sort(key=lambda x: x['per_util'], reverse=True)
|
||||||
|
|
||||||
|
def filter_stats_by_department(self):
|
||||||
|
filtered_data = frappe._dict()
|
||||||
|
for emp, data in self.stats_by_employee.items():
|
||||||
|
if data['department'] == self.filters.department:
|
||||||
|
filtered_data[emp] = data
|
||||||
|
|
||||||
|
# Update stats
|
||||||
|
self.stats_by_employee = filtered_data
|
||||||
|
|
||||||
|
def generate_filtered_time_logs(self):
|
||||||
|
additional_filters = ''
|
||||||
|
|
||||||
|
filter_fields = ['employee', 'project', 'company']
|
||||||
|
|
||||||
|
for field in filter_fields:
|
||||||
|
if self.filters.get(field):
|
||||||
|
if field == 'project':
|
||||||
|
additional_filters += f"AND ttd.{field} = '{self.filters.get(field)}'"
|
||||||
|
else:
|
||||||
|
additional_filters += f"AND tt.{field} = '{self.filters.get(field)}'"
|
||||||
|
|
||||||
|
self.filtered_time_logs = frappe.db.sql('''
|
||||||
|
SELECT tt.employee AS employee, ttd.hours AS hours, ttd.billable AS billable, ttd.project AS project
|
||||||
|
FROM `tabTimesheet Detail` AS ttd
|
||||||
|
JOIN `tabTimesheet` AS tt
|
||||||
|
ON ttd.parent = tt.name
|
||||||
|
WHERE tt.employee IS NOT NULL
|
||||||
|
AND tt.start_date BETWEEN '{0}' AND '{1}'
|
||||||
|
AND tt.end_date BETWEEN '{0}' AND '{1}'
|
||||||
|
{2}
|
||||||
|
'''.format(self.filters.from_date, self.filters.to_date, additional_filters))
|
||||||
|
|
||||||
|
def generate_stats_by_employee(self):
|
||||||
|
self.stats_by_employee = frappe._dict()
|
||||||
|
|
||||||
|
for emp, hours, billable, project in self.filtered_time_logs:
|
||||||
|
self.stats_by_employee.setdefault(
|
||||||
|
emp, frappe._dict()
|
||||||
|
).setdefault('billed_hours', 0.0)
|
||||||
|
|
||||||
|
self.stats_by_employee[emp].setdefault('non_billed_hours', 0.0)
|
||||||
|
|
||||||
|
if billable:
|
||||||
|
self.stats_by_employee[emp]['billed_hours'] += flt(hours, 2)
|
||||||
|
else:
|
||||||
|
self.stats_by_employee[emp]['non_billed_hours'] += flt(hours, 2)
|
||||||
|
|
||||||
|
def set_employee_department_and_name(self):
|
||||||
|
for emp in self.stats_by_employee:
|
||||||
|
emp_name = frappe.db.get_value(
|
||||||
|
'Employee', emp, 'employee_name'
|
||||||
|
)
|
||||||
|
emp_dept = frappe.db.get_value(
|
||||||
|
'Employee', emp, 'department'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.stats_by_employee[emp]['department'] = emp_dept
|
||||||
|
self.stats_by_employee[emp]['employee_name'] = emp_name
|
||||||
|
|
||||||
|
def calculate_utilizations(self):
|
||||||
|
TOTAL_HOURS = flt(self.standard_working_hours * self.day_span, 2)
|
||||||
|
for emp, data in iteritems(self.stats_by_employee):
|
||||||
|
data['total_hours'] = TOTAL_HOURS
|
||||||
|
data['untracked_hours'] = flt(TOTAL_HOURS - data['billed_hours'] - data['non_billed_hours'], 2)
|
||||||
|
|
||||||
|
# To handle overtime edge-case
|
||||||
|
if data['untracked_hours'] < 0:
|
||||||
|
data['untracked_hours'] = 0.0
|
||||||
|
|
||||||
|
data['per_util'] = flt(((data['billed_hours'] + data['non_billed_hours']) / TOTAL_HOURS) * 100, 2)
|
||||||
|
data['per_util_billed_only'] = flt((data['billed_hours'] / TOTAL_HOURS) * 100, 2)
|
||||||
|
|
||||||
|
def generate_report_summary(self):
|
||||||
|
self.report_summary = []
|
||||||
|
|
||||||
|
if not self.data:
|
||||||
|
return
|
||||||
|
|
||||||
|
avg_utilization = 0.0
|
||||||
|
avg_utilization_billed_only = 0.0
|
||||||
|
total_billed, total_non_billed = 0.0, 0.0
|
||||||
|
total_untracked = 0.0
|
||||||
|
|
||||||
|
for row in self.data:
|
||||||
|
avg_utilization += row['per_util']
|
||||||
|
avg_utilization_billed_only += row['per_util_billed_only']
|
||||||
|
total_billed += row['billed_hours']
|
||||||
|
total_non_billed += row['non_billed_hours']
|
||||||
|
total_untracked += row['untracked_hours']
|
||||||
|
|
||||||
|
avg_utilization /= len(self.data)
|
||||||
|
avg_utilization = flt(avg_utilization, 2)
|
||||||
|
|
||||||
|
avg_utilization_billed_only /= len(self.data)
|
||||||
|
avg_utilization_billed_only = flt(avg_utilization_billed_only, 2)
|
||||||
|
|
||||||
|
THRESHOLD_PERCENTAGE = 70.0
|
||||||
|
self.report_summary = [
|
||||||
|
{
|
||||||
|
'value': f'{avg_utilization}%',
|
||||||
|
'indicator': 'Red' if avg_utilization < THRESHOLD_PERCENTAGE else 'Green',
|
||||||
|
'label': _('Avg Utilization'),
|
||||||
|
'datatype': 'Percentage'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': f'{avg_utilization_billed_only}%',
|
||||||
|
'indicator': 'Red' if avg_utilization_billed_only < THRESHOLD_PERCENTAGE else 'Green',
|
||||||
|
'label': _('Avg Utilization (Billed Only)'),
|
||||||
|
'datatype': 'Percentage'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': total_billed,
|
||||||
|
'label': _('Total Billed Hours'),
|
||||||
|
'datatype': 'Float'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'value': total_non_billed,
|
||||||
|
'label': _('Total Non-Billed Hours'),
|
||||||
|
'datatype': 'Float'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
def generate_chart_data(self):
|
||||||
|
self.chart = {}
|
||||||
|
|
||||||
|
labels = []
|
||||||
|
billed_hours = []
|
||||||
|
non_billed_hours = []
|
||||||
|
untracked_hours = []
|
||||||
|
|
||||||
|
|
||||||
|
for row in self.data:
|
||||||
|
labels.append(row.get('employee_name'))
|
||||||
|
billed_hours.append(row.get('billed_hours'))
|
||||||
|
non_billed_hours.append(row.get('non_billed_hours'))
|
||||||
|
untracked_hours.append(row.get('untracked_hours'))
|
||||||
|
|
||||||
|
self.chart = {
|
||||||
|
'data': {
|
||||||
|
'labels': labels[:30],
|
||||||
|
'datasets': [
|
||||||
|
{
|
||||||
|
'name': _('Billed Hours'),
|
||||||
|
'values': billed_hours[:30]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': _('Non-Billed Hours'),
|
||||||
|
'values': non_billed_hours[:30]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': _('Untracked Hours'),
|
||||||
|
'values': untracked_hours[:30]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'type': 'bar',
|
||||||
|
'barOptions': {
|
||||||
|
'stacked': True
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,198 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
import unittest
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
from frappe.utils.make_random import get_random
|
||||||
|
from erpnext.projects.report.employee_hours_utilization_based_on_timesheet.employee_hours_utilization_based_on_timesheet import execute
|
||||||
|
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||||
|
from erpnext.projects.doctype.project.test_project import make_project
|
||||||
|
|
||||||
|
class TestEmployeeUtilization(unittest.TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
# Create test employee
|
||||||
|
cls.test_emp1 = make_employee("test1@employeeutil.com", "_Test Company")
|
||||||
|
cls.test_emp2 = make_employee("test2@employeeutil.com", "_Test Company")
|
||||||
|
|
||||||
|
# Create test project
|
||||||
|
cls.test_project = make_project({"project_name": "_Test Project"})
|
||||||
|
|
||||||
|
# Create test timesheets
|
||||||
|
cls.create_test_timesheets()
|
||||||
|
|
||||||
|
frappe.db.set_value("HR Settings", "HR Settings", "standard_working_hours", 9)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_test_timesheets(cls):
|
||||||
|
timesheet1 = frappe.new_doc("Timesheet")
|
||||||
|
timesheet1.employee = cls.test_emp1
|
||||||
|
timesheet1.company = '_Test Company'
|
||||||
|
|
||||||
|
timesheet1.append("time_logs", {
|
||||||
|
"activity_type": get_random("Activity Type"),
|
||||||
|
"hours": 5,
|
||||||
|
"billable": 1,
|
||||||
|
"from_time": '2021-04-01 13:30:00.000000',
|
||||||
|
"to_time": '2021-04-01 18:30:00.000000'
|
||||||
|
})
|
||||||
|
|
||||||
|
timesheet1.save()
|
||||||
|
timesheet1.submit()
|
||||||
|
|
||||||
|
timesheet2 = frappe.new_doc("Timesheet")
|
||||||
|
timesheet2.employee = cls.test_emp2
|
||||||
|
timesheet2.company = '_Test Company'
|
||||||
|
|
||||||
|
timesheet2.append("time_logs", {
|
||||||
|
"activity_type": get_random("Activity Type"),
|
||||||
|
"hours": 10,
|
||||||
|
"billable": 0,
|
||||||
|
"from_time": '2021-04-01 13:30:00.000000',
|
||||||
|
"to_time": '2021-04-01 23:30:00.000000',
|
||||||
|
"project": cls.test_project.name
|
||||||
|
})
|
||||||
|
|
||||||
|
timesheet2.save()
|
||||||
|
timesheet2.submit()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
# Delete time logs
|
||||||
|
frappe.db.sql("""
|
||||||
|
DELETE FROM `tabTimesheet Detail`
|
||||||
|
WHERE parent IN (
|
||||||
|
SELECT name
|
||||||
|
FROM `tabTimesheet`
|
||||||
|
WHERE company = '_Test Company'
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
frappe.db.sql("DELETE FROM `tabTimesheet` WHERE company='_Test Company'")
|
||||||
|
frappe.db.sql(f"DELETE FROM `tabProject` WHERE name='{cls.test_project.name}'")
|
||||||
|
|
||||||
|
def test_utilization_report_with_required_filters_only(self):
|
||||||
|
filters = {
|
||||||
|
"company": "_Test Company",
|
||||||
|
"from_date": "2021-04-01",
|
||||||
|
"to_date": "2021-04-03"
|
||||||
|
}
|
||||||
|
|
||||||
|
report = execute(filters)
|
||||||
|
|
||||||
|
expected_data = self.get_expected_data_for_test_employees()
|
||||||
|
self.assertEqual(report[1], expected_data)
|
||||||
|
|
||||||
|
def test_utilization_report_for_single_employee(self):
|
||||||
|
filters = {
|
||||||
|
"company": "_Test Company",
|
||||||
|
"from_date": "2021-04-01",
|
||||||
|
"to_date": "2021-04-03",
|
||||||
|
"employee": self.test_emp1
|
||||||
|
}
|
||||||
|
|
||||||
|
report = execute(filters)
|
||||||
|
|
||||||
|
emp1_data = frappe.get_doc('Employee', self.test_emp1)
|
||||||
|
expected_data = [
|
||||||
|
{
|
||||||
|
'employee': self.test_emp1,
|
||||||
|
'employee_name': 'test1@employeeutil.com',
|
||||||
|
'billed_hours': 5.0,
|
||||||
|
'non_billed_hours': 0.0,
|
||||||
|
'department': emp1_data.department,
|
||||||
|
'total_hours': 18.0,
|
||||||
|
'untracked_hours': 13.0,
|
||||||
|
'per_util': 27.78,
|
||||||
|
'per_util_billed_only': 27.78
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
self.assertEqual(report[1], expected_data)
|
||||||
|
|
||||||
|
def test_utilization_report_for_project(self):
|
||||||
|
filters = {
|
||||||
|
"company": "_Test Company",
|
||||||
|
"from_date": "2021-04-01",
|
||||||
|
"to_date": "2021-04-03",
|
||||||
|
"project": self.test_project.name
|
||||||
|
}
|
||||||
|
|
||||||
|
report = execute(filters)
|
||||||
|
|
||||||
|
emp2_data = frappe.get_doc('Employee', self.test_emp2)
|
||||||
|
expected_data = [
|
||||||
|
{
|
||||||
|
'employee': self.test_emp2,
|
||||||
|
'employee_name': 'test2@employeeutil.com',
|
||||||
|
'billed_hours': 0.0,
|
||||||
|
'non_billed_hours': 10.0,
|
||||||
|
'department': emp2_data.department,
|
||||||
|
'total_hours': 18.0,
|
||||||
|
'untracked_hours': 8.0,
|
||||||
|
'per_util': 55.56,
|
||||||
|
'per_util_billed_only': 0.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
self.assertEqual(report[1], expected_data)
|
||||||
|
|
||||||
|
def test_utilization_report_for_department(self):
|
||||||
|
emp1_data = frappe.get_doc('Employee', self.test_emp1)
|
||||||
|
filters = {
|
||||||
|
"company": "_Test Company",
|
||||||
|
"from_date": "2021-04-01",
|
||||||
|
"to_date": "2021-04-03",
|
||||||
|
"department": emp1_data.department
|
||||||
|
}
|
||||||
|
|
||||||
|
report = execute(filters)
|
||||||
|
|
||||||
|
expected_data = self.get_expected_data_for_test_employees()
|
||||||
|
self.assertEqual(report[1], expected_data)
|
||||||
|
|
||||||
|
def test_report_summary_data(self):
|
||||||
|
filters = {
|
||||||
|
"company": "_Test Company",
|
||||||
|
"from_date": "2021-04-01",
|
||||||
|
"to_date": "2021-04-03"
|
||||||
|
}
|
||||||
|
|
||||||
|
report = execute(filters)
|
||||||
|
summary = report[4]
|
||||||
|
expected_summary_values = ['41.67%', '13.89%', 5.0, 10.0]
|
||||||
|
|
||||||
|
self.assertEqual(len(summary), 4)
|
||||||
|
|
||||||
|
for i in range(4):
|
||||||
|
self.assertEqual(
|
||||||
|
summary[i]['value'], expected_summary_values[i]
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_expected_data_for_test_employees(self):
|
||||||
|
emp1_data = frappe.get_doc('Employee', self.test_emp1)
|
||||||
|
emp2_data = frappe.get_doc('Employee', self.test_emp2)
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'employee': self.test_emp2,
|
||||||
|
'employee_name': 'test2@employeeutil.com',
|
||||||
|
'billed_hours': 0.0,
|
||||||
|
'non_billed_hours': 10.0,
|
||||||
|
'department': emp2_data.department,
|
||||||
|
'total_hours': 18.0,
|
||||||
|
'untracked_hours': 8.0,
|
||||||
|
'per_util': 55.56,
|
||||||
|
'per_util_billed_only': 0.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'employee': self.test_emp1,
|
||||||
|
'employee_name': 'test1@employeeutil.com',
|
||||||
|
'billed_hours': 5.0,
|
||||||
|
'non_billed_hours': 0.0,
|
||||||
|
'department': emp1_data.department,
|
||||||
|
'total_hours': 18.0,
|
||||||
|
'untracked_hours': 13.0,
|
||||||
|
'per_util': 27.78,
|
||||||
|
'per_util_billed_only': 27.78
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
frappe.query_reports["Project Profitability"] = {
|
||||||
|
"filters": [
|
||||||
|
{
|
||||||
|
"fieldname": "company",
|
||||||
|
"label": __("Company"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Company",
|
||||||
|
"default": frappe.defaults.get_user_default("Company"),
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "start_date",
|
||||||
|
"label": __("Start Date"),
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"reqd": 1,
|
||||||
|
"default": frappe.datetime.add_months(frappe.datetime.get_today(), -1)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "end_date",
|
||||||
|
"label": __("End Date"),
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"reqd": 1,
|
||||||
|
"default": frappe.datetime.now_date()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "customer_name",
|
||||||
|
"label": __("Customer"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Customer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "employee",
|
||||||
|
"label": __("Employee"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Employee"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "project",
|
||||||
|
"label": __("Project"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Project"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"add_total_row": 0,
|
||||||
|
"columns": [],
|
||||||
|
"creation": "2021-04-16 15:50:28.914872",
|
||||||
|
"disable_prepared_report": 0,
|
||||||
|
"disabled": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Report",
|
||||||
|
"filters": [],
|
||||||
|
"idx": 0,
|
||||||
|
"is_standard": "Yes",
|
||||||
|
"modified": "2021-04-16 15:50:48.490866",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Projects",
|
||||||
|
"name": "Project Profitability",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"prepared_report": 0,
|
||||||
|
"ref_doctype": "Timesheet",
|
||||||
|
"report_name": "Project Profitability",
|
||||||
|
"report_type": "Script Report",
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "HR User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Accounts User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Employee"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Projects User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Manufacturing User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Employee Self Service"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "HR Manager"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,210 @@
|
|||||||
|
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
def execute(filters=None):
|
||||||
|
columns, data = [], []
|
||||||
|
data = get_data(filters)
|
||||||
|
columns = get_columns()
|
||||||
|
charts = get_chart_data(data)
|
||||||
|
return columns, data, None, charts
|
||||||
|
|
||||||
|
def get_data(filters):
|
||||||
|
data = get_rows(filters)
|
||||||
|
data = calculate_cost_and_profit(data)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get_rows(filters):
|
||||||
|
conditions = get_conditions(filters)
|
||||||
|
standard_working_hours = frappe.db.get_single_value("HR Settings", "standard_working_hours")
|
||||||
|
if not standard_working_hours:
|
||||||
|
msg = _("The metrics for this report are calculated based on the Standard Working Hours. Please set {0} in {1}.").format(
|
||||||
|
frappe.bold("Standard Working Hours"), frappe.utils.get_link_to_form("HR Settings", "HR Settings"))
|
||||||
|
|
||||||
|
frappe.msgprint(msg)
|
||||||
|
return []
|
||||||
|
|
||||||
|
sql = """
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
(SELECT
|
||||||
|
si.customer_name,si.base_grand_total,
|
||||||
|
si.name as voucher_no,tabTimesheet.employee,
|
||||||
|
tabTimesheet.title as employee_name,tabTimesheet.parent_project as project,
|
||||||
|
tabTimesheet.start_date,tabTimesheet.end_date,
|
||||||
|
tabTimesheet.total_billed_hours,tabTimesheet.name as timesheet,
|
||||||
|
ss.base_gross_pay,ss.total_working_days,
|
||||||
|
tabTimesheet.total_billed_hours/(ss.total_working_days * {0}) as utilization
|
||||||
|
FROM
|
||||||
|
`tabSalary Slip Timesheet` as sst join `tabTimesheet` on tabTimesheet.name = sst.time_sheet
|
||||||
|
join `tabSales Invoice Timesheet` as sit on sit.time_sheet = tabTimesheet.name
|
||||||
|
join `tabSales Invoice` as si on si.name = sit.parent and si.status != "Cancelled"
|
||||||
|
join `tabSalary Slip` as ss on ss.name = sst.parent and ss.status != "Cancelled" """.format(standard_working_hours)
|
||||||
|
if conditions:
|
||||||
|
sql += """
|
||||||
|
WHERE
|
||||||
|
{0}) as t""".format(conditions)
|
||||||
|
return frappe.db.sql(sql,filters, as_dict=True)
|
||||||
|
|
||||||
|
def calculate_cost_and_profit(data):
|
||||||
|
for row in data:
|
||||||
|
row.fractional_cost = row.base_gross_pay * row.utilization
|
||||||
|
row.profit = row.base_grand_total - row.base_gross_pay * row.utilization
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get_conditions(filters):
|
||||||
|
conditions = []
|
||||||
|
|
||||||
|
if filters.get("company"):
|
||||||
|
conditions.append("tabTimesheet.company={0}".format(frappe.db.escape(filters.get("company"))))
|
||||||
|
|
||||||
|
if filters.get("start_date"):
|
||||||
|
conditions.append("tabTimesheet.start_date>='{0}'".format(filters.get("start_date")))
|
||||||
|
|
||||||
|
if filters.get("end_date"):
|
||||||
|
conditions.append("tabTimesheet.end_date<='{0}'".format(filters.get("end_date")))
|
||||||
|
|
||||||
|
if filters.get("customer_name"):
|
||||||
|
conditions.append("si.customer_name={0}".format(frappe.db.escape(filters.get("customer_name"))))
|
||||||
|
|
||||||
|
if filters.get("employee"):
|
||||||
|
conditions.append("tabTimesheet.employee={0}".format(frappe.db.escape(filters.get("employee"))))
|
||||||
|
|
||||||
|
if filters.get("project"):
|
||||||
|
conditions.append("tabTimesheet.parent_project={0}".format(frappe.db.escape(filters.get("project"))))
|
||||||
|
|
||||||
|
conditions = " and ".join(conditions)
|
||||||
|
return conditions
|
||||||
|
|
||||||
|
def get_chart_data(data):
|
||||||
|
if not data:
|
||||||
|
return None
|
||||||
|
|
||||||
|
labels = []
|
||||||
|
utilization = []
|
||||||
|
|
||||||
|
for entry in data:
|
||||||
|
labels.append(entry.get("employee_name") + " - " + str(entry.get("end_date")))
|
||||||
|
utilization.append(entry.get("utilization"))
|
||||||
|
|
||||||
|
charts = {
|
||||||
|
"data": {
|
||||||
|
"labels": labels,
|
||||||
|
"datasets": [
|
||||||
|
{
|
||||||
|
"name": "Utilization",
|
||||||
|
"values": utilization
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": "bar",
|
||||||
|
"colors": ["#84BDD5"]
|
||||||
|
}
|
||||||
|
return charts
|
||||||
|
|
||||||
|
def get_columns():
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"fieldname": "customer_name",
|
||||||
|
"label": _("Customer"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Customer",
|
||||||
|
"width": 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "employee",
|
||||||
|
"label": _("Employee"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Employee",
|
||||||
|
"width": 130
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "employee_name",
|
||||||
|
"label": _("Employee Name"),
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "voucher_no",
|
||||||
|
"label": _("Sales Invoice"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Sales Invoice",
|
||||||
|
"width": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "timesheet",
|
||||||
|
"label": _("Timesheet"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Timesheet",
|
||||||
|
"width": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "project",
|
||||||
|
"label": _("Project"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Project",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "base_grand_total",
|
||||||
|
"label": _("Bill Amount"),
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "currency",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "base_gross_pay",
|
||||||
|
"label": _("Cost"),
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "currency",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "profit",
|
||||||
|
"label": _("Profit"),
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "currency",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "utilization",
|
||||||
|
"label": _("Utilization"),
|
||||||
|
"fieldtype": "Percentage",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fractional_cost",
|
||||||
|
"label": _("Fractional Cost"),
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"width": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "total_billed_hours",
|
||||||
|
"label": _("Total Billed Hours"),
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"width": 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "start_date",
|
||||||
|
"label": _("Start Date"),
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "end_date",
|
||||||
|
"label": _("End Date"),
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Currency"),
|
||||||
|
"fieldname": "currency",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Currency",
|
||||||
|
"width": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
import unittest
|
||||||
|
import frappe
|
||||||
|
from frappe.utils import getdate, nowdate
|
||||||
|
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||||
|
from erpnext.projects.doctype.timesheet.test_timesheet import make_salary_structure_for_timesheet, make_timesheet
|
||||||
|
from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice
|
||||||
|
from erpnext.projects.report.project_profitability.project_profitability import execute
|
||||||
|
|
||||||
|
class TestProjectProfitability(unittest.TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUp(self):
|
||||||
|
emp = make_employee('test_employee_9@salary.com', company='_Test Company')
|
||||||
|
if not frappe.db.exists('Salary Component', 'Timesheet Component'):
|
||||||
|
frappe.get_doc({'doctype': 'Salary Component', 'salary_component': 'Timesheet Component'}).insert()
|
||||||
|
make_salary_structure_for_timesheet(emp, company='_Test Company')
|
||||||
|
self.timesheet = make_timesheet(emp, simulate = True, billable=1)
|
||||||
|
self.salary_slip = make_salary_slip(self.timesheet.name)
|
||||||
|
self.salary_slip.submit()
|
||||||
|
self.sales_invoice = make_sales_invoice(self.timesheet.name, '_Test Item', '_Test Customer')
|
||||||
|
self.sales_invoice.due_date = nowdate()
|
||||||
|
self.sales_invoice.submit()
|
||||||
|
|
||||||
|
frappe.db.set_value("HR Settings", "HR Settings", "standard_working_hours", 8)
|
||||||
|
|
||||||
|
def test_project_profitability(self):
|
||||||
|
filters = {
|
||||||
|
'company': '_Test Company',
|
||||||
|
'start_date': getdate(),
|
||||||
|
'end_date': getdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
report = execute(filters)
|
||||||
|
|
||||||
|
row = report[1][0]
|
||||||
|
timesheet = frappe.get_doc("Timesheet", self.timesheet.name)
|
||||||
|
|
||||||
|
self.assertEqual(self.sales_invoice.customer, row.customer_name)
|
||||||
|
self.assertEqual(timesheet.title, row.employee_name)
|
||||||
|
self.assertEqual(self.sales_invoice.base_grand_total, row.base_grand_total)
|
||||||
|
self.assertEqual(self.salary_slip.base_gross_pay, row.base_gross_pay)
|
||||||
|
self.assertEqual(timesheet.total_billed_hours, row.total_billed_hours)
|
||||||
|
self.assertEqual(self.salary_slip.total_working_days, row.total_working_days)
|
||||||
|
|
||||||
|
standard_working_hours = frappe.db.get_single_value("HR Settings", "standard_working_hours")
|
||||||
|
utilization = timesheet.total_billed_hours/(self.salary_slip.total_working_days * standard_working_hours)
|
||||||
|
self.assertEqual(utilization, row.utilization)
|
||||||
|
|
||||||
|
profit = self.sales_invoice.base_grand_total - self.salary_slip.base_gross_pay * utilization
|
||||||
|
self.assertEqual(profit, row.profit)
|
||||||
|
|
||||||
|
fractional_cost = self.salary_slip.base_gross_pay * utilization
|
||||||
|
self.assertEqual(fractional_cost, row.fractional_cost)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
frappe.get_doc("Sales Invoice", self.sales_invoice.name).cancel()
|
||||||
|
frappe.get_doc("Salary Slip", self.salary_slip.name).cancel()
|
||||||
|
frappe.get_doc("Timesheet", self.timesheet.name).cancel()
|
||||||
@ -131,25 +131,25 @@ def get_report_summary(data):
|
|||||||
{
|
{
|
||||||
"value": avg_completion,
|
"value": avg_completion,
|
||||||
"indicator": "Green" if avg_completion > 50 else "Red",
|
"indicator": "Green" if avg_completion > 50 else "Red",
|
||||||
"label": "Average Completion",
|
"label": _("Average Completion"),
|
||||||
"datatype": "Percent",
|
"datatype": "Percent",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"value": total,
|
"value": total,
|
||||||
"indicator": "Blue",
|
"indicator": "Blue",
|
||||||
"label": "Total Tasks",
|
"label": _("Total Tasks"),
|
||||||
"datatype": "Int",
|
"datatype": "Int",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"value": completed,
|
"value": completed,
|
||||||
"indicator": "Green",
|
"indicator": "Green",
|
||||||
"label": "Completed Tasks",
|
"label": _("Completed Tasks"),
|
||||||
"datatype": "Int",
|
"datatype": "Int",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"value": total_overdue,
|
"value": total_overdue,
|
||||||
"indicator": "Green" if total_overdue == 0 else "Red",
|
"indicator": "Green" if total_overdue == 0 else "Red",
|
||||||
"label": "Overdue Tasks",
|
"label": _("Overdue Tasks"),
|
||||||
"datatype": "Int",
|
"datatype": "Int",
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -130,6 +130,26 @@
|
|||||||
"onboard": 1,
|
"onboard": 1,
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"dependencies": "Timesheet",
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 1,
|
||||||
|
"label": "Employee Hours Utilization",
|
||||||
|
"link_to": "Employee Hours Utilization Based On Timesheet",
|
||||||
|
"link_type": "Report",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dependencies": "Timesheet, Sales Invoice, Salary Slip",
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 1,
|
||||||
|
"label": "Project Profitability",
|
||||||
|
"link_to": "Project Profitability",
|
||||||
|
"link_type": "Report",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"dependencies": "Project",
|
"dependencies": "Project",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@ -161,7 +181,7 @@
|
|||||||
"type": "Link"
|
"type": "Link"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2021-03-26 16:32:00.628561",
|
"modified": "2021-04-25 16:27:16.548780",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Projects",
|
"module": "Projects",
|
||||||
"name": "Projects",
|
"name": "Projects",
|
||||||
|
|||||||
@ -562,7 +562,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
weight_uom: item.weight_uom,
|
weight_uom: item.weight_uom,
|
||||||
manufacturer: item.manufacturer,
|
manufacturer: item.manufacturer,
|
||||||
stock_uom: item.stock_uom,
|
stock_uom: item.stock_uom,
|
||||||
pos_profile: me.frm.doc.doctype == 'Sales Invoice' ? me.frm.doc.pos_profile : '',
|
pos_profile: cint(me.frm.doc.is_pos) ? me.frm.doc.pos_profile : '',
|
||||||
cost_center: item.cost_center,
|
cost_center: item.cost_center,
|
||||||
tax_category: me.frm.doc.tax_category,
|
tax_category: me.frm.doc.tax_category,
|
||||||
item_tax_template: item.item_tax_template,
|
item_tax_template: item.item_tax_template,
|
||||||
@ -640,6 +640,10 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
let key = item.name;
|
let key = item.name;
|
||||||
me.apply_rule_on_other_items({key: item});
|
me.apply_rule_on_other_items({key: item});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
var company_currency = me.get_company_currency();
|
||||||
|
me.update_item_grid_labels(company_currency);
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -1321,11 +1325,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
change_grid_labels: function(company_currency) {
|
change_grid_labels: function(company_currency) {
|
||||||
var me = this;
|
var me = this;
|
||||||
|
|
||||||
this.frm.set_currency_labels(["base_rate", "base_net_rate", "base_price_list_rate", "base_amount", "base_net_amount", "base_rate_with_margin"],
|
this.update_item_grid_labels(company_currency);
|
||||||
company_currency, "items");
|
|
||||||
|
|
||||||
this.frm.set_currency_labels(["rate", "net_rate", "price_list_rate", "amount", "net_amount", "stock_uom_rate", "rate_with_margin"],
|
this.toggle_item_grid_columns(company_currency);
|
||||||
this.frm.doc.currency, "items");
|
|
||||||
|
|
||||||
if(this.frm.fields_dict["operations"]) {
|
if(this.frm.fields_dict["operations"]) {
|
||||||
this.frm.set_currency_labels(["operating_cost", "hour_rate"], this.frm.doc.currency, "operations");
|
this.frm.set_currency_labels(["operating_cost", "hour_rate"], this.frm.doc.currency, "operations");
|
||||||
@ -1360,6 +1362,39 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
this.frm.doc.party_account_currency, "advances");
|
this.frm.doc.party_account_currency, "advances");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.update_payment_schedule_grid_labels(company_currency);
|
||||||
|
},
|
||||||
|
|
||||||
|
update_item_grid_labels: function(company_currency) {
|
||||||
|
this.frm.set_currency_labels([
|
||||||
|
"base_rate", "base_net_rate", "base_price_list_rate",
|
||||||
|
"base_amount", "base_net_amount", "base_rate_with_margin"
|
||||||
|
], company_currency, "items");
|
||||||
|
|
||||||
|
this.frm.set_currency_labels([
|
||||||
|
"rate", "net_rate", "price_list_rate", "amount",
|
||||||
|
"net_amount", "stock_uom_rate", "rate_with_margin"
|
||||||
|
], this.frm.doc.currency, "items");
|
||||||
|
},
|
||||||
|
|
||||||
|
update_payment_schedule_grid_labels: function(company_currency) {
|
||||||
|
const me = this;
|
||||||
|
if (this.frm.fields_dict["payment_schedule"]) {
|
||||||
|
this.frm.set_currency_labels(["base_payment_amount", "base_outstanding", "base_paid_amount"],
|
||||||
|
company_currency, "payment_schedule");
|
||||||
|
this.frm.set_currency_labels(["payment_amount", "outstanding", "paid_amount"],
|
||||||
|
this.frm.doc.currency, "payment_schedule");
|
||||||
|
|
||||||
|
var schedule_grid = this.frm.fields_dict["payment_schedule"].grid;
|
||||||
|
$.each(["base_payment_amount", "base_outstanding", "base_paid_amount"], function(i, fname) {
|
||||||
|
if (frappe.meta.get_docfield(schedule_grid.doctype, fname))
|
||||||
|
schedule_grid.set_column_disp(fname, me.frm.doc.currency != company_currency);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
toggle_item_grid_columns: function(company_currency) {
|
||||||
|
const me = this;
|
||||||
// toggle columns
|
// toggle columns
|
||||||
var item_grid = this.frm.fields_dict["items"].grid;
|
var item_grid = this.frm.fields_dict["items"].grid;
|
||||||
$.each(["base_rate", "base_price_list_rate", "base_amount", "base_rate_with_margin"], function(i, fname) {
|
$.each(["base_rate", "base_price_list_rate", "base_amount", "base_rate_with_margin"], function(i, fname) {
|
||||||
@ -1379,9 +1414,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
if(frappe.meta.get_docfield(item_grid.doctype, fname))
|
if(frappe.meta.get_docfield(item_grid.doctype, fname))
|
||||||
item_grid.set_column_disp(fname, (show && (me.frm.doc.currency != company_currency)));
|
item_grid.set_column_disp(fname, (show && (me.frm.doc.currency != company_currency)));
|
||||||
});
|
});
|
||||||
|
|
||||||
// set labels
|
|
||||||
var $wrapper = $(this.frm.wrapper);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
recalculate: function() {
|
recalculate: function() {
|
||||||
@ -1995,11 +2027,14 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
terms_template: doc.payment_terms_template,
|
terms_template: doc.payment_terms_template,
|
||||||
posting_date: posting_date,
|
posting_date: posting_date,
|
||||||
grand_total: doc.rounded_total || doc.grand_total,
|
grand_total: doc.rounded_total || doc.grand_total,
|
||||||
|
base_grand_total: doc.base_rounded_total || doc.base_grand_total,
|
||||||
bill_date: doc.bill_date
|
bill_date: doc.bill_date
|
||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if(r.message && !r.exc) {
|
if(r.message && !r.exc) {
|
||||||
me.frm.set_value("payment_schedule", r.message);
|
me.frm.set_value("payment_schedule", r.message);
|
||||||
|
const company_currency = me.get_company_currency();
|
||||||
|
this.update_payment_schedule_grid_labels(company_currency);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -2007,6 +2042,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
payment_term: function(doc, cdt, cdn) {
|
payment_term: function(doc, cdt, cdn) {
|
||||||
|
const me = this;
|
||||||
var row = locals[cdt][cdn];
|
var row = locals[cdt][cdn];
|
||||||
if(row.payment_term) {
|
if(row.payment_term) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
@ -2015,12 +2051,15 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
term: row.payment_term,
|
term: row.payment_term,
|
||||||
bill_date: this.frm.doc.bill_date,
|
bill_date: this.frm.doc.bill_date,
|
||||||
posting_date: this.frm.doc.posting_date || this.frm.doc.transaction_date,
|
posting_date: this.frm.doc.posting_date || this.frm.doc.transaction_date,
|
||||||
grand_total: this.frm.doc.rounded_total || this.frm.doc.grand_total
|
grand_total: this.frm.doc.rounded_total || this.frm.doc.grand_total,
|
||||||
|
base_grand_total: this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total
|
||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if(r.message && !r.exc) {
|
if(r.message && !r.exc) {
|
||||||
for (var d in r.message) {
|
for (var d in r.message) {
|
||||||
frappe.model.set_value(cdt, cdn, d, r.message[d]);
|
frappe.model.set_value(cdt, cdn, d, r.message[d]);
|
||||||
|
const company_currency = me.get_company_currency();
|
||||||
|
me.update_payment_schedule_grid_labels(company_currency);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"creation": "2019-10-15 12:33:21.845329",
|
"creation": "2019-10-15 12:33:21.845329",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
@ -86,12 +87,14 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval:!doc.__islocal",
|
||||||
"fieldname": "upload_xml_invoices_section",
|
"fieldname": "upload_xml_invoices_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Upload XML Invoices"
|
"label": "Upload XML Invoices"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2020-05-25 21:32:49.064579",
|
"links": [],
|
||||||
|
"modified": "2021-04-24 10:33:12.250687",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Regional",
|
"module": "Regional",
|
||||||
"name": "Import Supplier Invoice",
|
"name": "Import Supplier Invoice",
|
||||||
|
|||||||
@ -28,14 +28,19 @@ class ImportSupplierInvoice(Document):
|
|||||||
self.name = "Import Invoice on " + format_datetime(self.creation)
|
self.name = "Import Invoice on " + format_datetime(self.creation)
|
||||||
|
|
||||||
def import_xml_data(self):
|
def import_xml_data(self):
|
||||||
import_file = frappe.get_doc("File", {"file_url": self.zip_file})
|
zip_file = frappe.get_doc("File", {
|
||||||
|
"file_url": self.zip_file,
|
||||||
|
"attached_to_doctype": self.doctype,
|
||||||
|
"attached_to_name": self.name
|
||||||
|
})
|
||||||
|
|
||||||
self.publish("File Import", _("Processing XML Files"), 1, 3)
|
self.publish("File Import", _("Processing XML Files"), 1, 3)
|
||||||
|
|
||||||
self.file_count = 0
|
self.file_count = 0
|
||||||
self.purchase_invoices_count = 0
|
self.purchase_invoices_count = 0
|
||||||
self.default_uom = frappe.db.get_value("Stock Settings", fieldname="stock_uom")
|
self.default_uom = frappe.db.get_value("Stock Settings", fieldname="stock_uom")
|
||||||
|
|
||||||
with zipfile.ZipFile(get_full_path(self.zip_file)) as zf:
|
with zipfile.ZipFile(zip_file.get_full_path()) as zf:
|
||||||
for file_name in zf.namelist():
|
for file_name in zf.namelist():
|
||||||
content = get_file_content(file_name, zf)
|
content = get_file_content(file_name, zf)
|
||||||
file_content = bs(content, "xml")
|
file_content = bs(content, "xml")
|
||||||
@ -124,9 +129,9 @@ class ImportSupplierInvoice(Document):
|
|||||||
if disc_line.find("Percentuale"):
|
if disc_line.find("Percentuale"):
|
||||||
invoices_args["total_discount"] += flt((flt(disc_line.Percentuale.text) / 100) * (rate * qty))
|
invoices_args["total_discount"] += flt((flt(disc_line.Percentuale.text) / 100) * (rate * qty))
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def process_file_data(self):
|
def process_file_data(self):
|
||||||
self.status = "Processing File Data"
|
self.db_set("status", "Processing File Data", notify=True, commit=True)
|
||||||
self.save()
|
|
||||||
frappe.enqueue_doc(self.doctype, self.name, "import_xml_data", queue="long", timeout=3600)
|
frappe.enqueue_doc(self.doctype, self.name, "import_xml_data", queue="long", timeout=3600)
|
||||||
|
|
||||||
def publish(self, title, message, count, total):
|
def publish(self, title, message, count, total):
|
||||||
@ -380,24 +385,3 @@ def create_uom(uom):
|
|||||||
new_uom.uom_name = uom
|
new_uom.uom_name = uom
|
||||||
new_uom.save()
|
new_uom.save()
|
||||||
return new_uom.uom_name
|
return new_uom.uom_name
|
||||||
|
|
||||||
def get_full_path(file_name):
|
|
||||||
"""Returns file path from given file name"""
|
|
||||||
file_path = file_name
|
|
||||||
|
|
||||||
if "/" not in file_path:
|
|
||||||
file_path = "/files/" + file_path
|
|
||||||
|
|
||||||
if file_path.startswith("/private/files/"):
|
|
||||||
file_path = get_files_path(*file_path.split("/private/files/", 1)[1].split("/"), is_private=1)
|
|
||||||
|
|
||||||
elif file_path.startswith("/files/"):
|
|
||||||
file_path = get_files_path(*file_path.split("/files/", 1)[1].split("/"))
|
|
||||||
|
|
||||||
elif file_path.startswith("http"):
|
|
||||||
pass
|
|
||||||
|
|
||||||
elif not self.file_url:
|
|
||||||
frappe.throw(_("There is some problem with the file url: {0}").format(file_path))
|
|
||||||
|
|
||||||
return file_path
|
|
||||||
@ -115,17 +115,19 @@ erpnext.setup_einvoice_actions = (doctype) => {
|
|||||||
message += '<br><br>';
|
message += '<br><br>';
|
||||||
message += __('You must first use the portal to cancel the e-way bill and then update the cancelled status in the ERPNext system.');
|
message += __('You must first use the portal to cancel the e-way bill and then update the cancelled status in the ERPNext system.');
|
||||||
|
|
||||||
frappe.msgprint({
|
const dialog = frappe.msgprint({
|
||||||
title: __('Update E-Way Bill Cancelled Status?'),
|
title: __('Update E-Way Bill Cancelled Status?'),
|
||||||
message: message,
|
message: message,
|
||||||
indicator: 'orange',
|
indicator: 'orange',
|
||||||
primary_action: function() {
|
primary_action: {
|
||||||
|
action: function() {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill',
|
method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill',
|
||||||
args: { doctype, docname: name },
|
args: { doctype, docname: name },
|
||||||
freeze: true,
|
freeze: true,
|
||||||
callback: () => frm.reload_doc()
|
callback: () => frm.reload_doc() || dialog.hide()
|
||||||
});
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
primary_action_label: __('Yes')
|
primary_action_label: __('Yes')
|
||||||
});
|
});
|
||||||
|
|||||||
@ -340,8 +340,6 @@ def get_eway_bill_details(invoice):
|
|||||||
frappe.throw(_('E-Way Bill cannot be generated for Credit Notes & Debit Notes. Please clear fields in the Transporter Section of the invoice.'),
|
frappe.throw(_('E-Way Bill cannot be generated for Credit Notes & Debit Notes. Please clear fields in the Transporter Section of the invoice.'),
|
||||||
title=_('Invalid Fields'))
|
title=_('Invalid Fields'))
|
||||||
|
|
||||||
if not invoice.distance:
|
|
||||||
frappe.throw(_('Distance is mandatory for generating e-way bill for an e-invoice.'), title=_('Missing Field'))
|
|
||||||
|
|
||||||
mode_of_transport = { '': '', 'Road': '1', 'Air': '2', 'Rail': '3', 'Ship': '4' }
|
mode_of_transport = { '': '', 'Road': '1', 'Air': '2', 'Rail': '3', 'Ship': '4' }
|
||||||
vehicle_type = { 'Regular': 'R', 'Over Dimensional Cargo (ODC)': 'O' }
|
vehicle_type = { 'Regular': 'R', 'Over Dimensional Cargo (ODC)': 'O' }
|
||||||
@ -450,7 +448,7 @@ def make_einvoice(invoice):
|
|||||||
if invoice.is_return:
|
if invoice.is_return:
|
||||||
prev_doc_details = get_return_doc_reference(invoice)
|
prev_doc_details = get_return_doc_reference(invoice)
|
||||||
|
|
||||||
if invoice.transporter and flt(invoice.distance) and not invoice.is_return:
|
if invoice.transporter and not invoice.is_return:
|
||||||
eway_bill_details = get_eway_bill_details(invoice)
|
eway_bill_details = get_eway_bill_details(invoice)
|
||||||
|
|
||||||
# not yet implemented
|
# not yet implemented
|
||||||
@ -1027,12 +1025,12 @@ def generate_eway_bill(doctype, docname, **kwargs):
|
|||||||
gsp_connector.generate_eway_bill(**kwargs)
|
gsp_connector.generate_eway_bill(**kwargs)
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def cancel_eway_bill(doctype, docname, eway_bill, reason, remark):
|
def cancel_eway_bill(doctype, docname):
|
||||||
# TODO: uncomment when eway_bill api from Adequare is enabled
|
# TODO: uncomment when eway_bill api from Adequare is enabled
|
||||||
# gsp_connector = GSPConnector(doctype, docname)
|
# gsp_connector = GSPConnector(doctype, docname)
|
||||||
# gsp_connector.cancel_eway_bill(eway_bill, reason, remark)
|
# gsp_connector.cancel_eway_bill(eway_bill, reason, remark)
|
||||||
|
|
||||||
# update cancelled status only, to be able to cancel irn next
|
frappe.db.set_value(doctype, docname, 'ewaybill', '')
|
||||||
frappe.db.set_value(doctype, docname, 'eway_bill_cancelled', 1)
|
frappe.db.set_value(doctype, docname, 'eway_bill_cancelled', 1)
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
|||||||
@ -199,7 +199,7 @@ class Gstr1Report(object):
|
|||||||
self.item_tax_rate = frappe._dict()
|
self.item_tax_rate = frappe._dict()
|
||||||
|
|
||||||
items = frappe.db.sql("""
|
items = frappe.db.sql("""
|
||||||
select item_code, parent, taxable_value, item_tax_rate
|
select item_code, parent, taxable_value, base_net_amount, item_tax_rate
|
||||||
from `tab%s Item`
|
from `tab%s Item`
|
||||||
where parent in (%s)
|
where parent in (%s)
|
||||||
""" % (self.doctype, ', '.join(['%s']*len(self.invoices))), tuple(self.invoices), as_dict=1)
|
""" % (self.doctype, ', '.join(['%s']*len(self.invoices))), tuple(self.invoices), as_dict=1)
|
||||||
@ -207,7 +207,7 @@ class Gstr1Report(object):
|
|||||||
for d in items:
|
for d in items:
|
||||||
if d.item_code not in self.invoice_items.get(d.parent, {}):
|
if d.item_code not in self.invoice_items.get(d.parent, {}):
|
||||||
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code,
|
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code,
|
||||||
sum(i.get('taxable_value', 0) for i in items
|
sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in items
|
||||||
if i.item_code == d.item_code and i.parent == d.parent))
|
if i.item_code == d.item_code and i.parent == d.parent))
|
||||||
|
|
||||||
item_tax_rate = {}
|
item_tax_rate = {}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ from frappe.model.document import Document
|
|||||||
from frappe.core.doctype.sms_settings.sms_settings import send_sms
|
from frappe.core.doctype.sms_settings.sms_settings import send_sms
|
||||||
|
|
||||||
class SMSCenter(Document):
|
class SMSCenter(Document):
|
||||||
|
@frappe.whitelist()
|
||||||
def create_receiver_list(self):
|
def create_receiver_list(self):
|
||||||
rec, where_clause = '', ''
|
rec, where_clause = '', ''
|
||||||
if self.send_to == 'All Customer Contact':
|
if self.send_to == 'All Customer Contact':
|
||||||
@ -73,6 +74,7 @@ class SMSCenter(Document):
|
|||||||
|
|
||||||
return receiver_nos
|
return receiver_nos
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def send_sms(self):
|
def send_sms(self):
|
||||||
receiver_list = []
|
receiver_list = []
|
||||||
if not self.message:
|
if not self.message:
|
||||||
|
|||||||
@ -139,8 +139,24 @@ def get_conditions(item_code, serial_no, batch_no, barcode):
|
|||||||
if serial_no or batch_no or barcode:
|
if serial_no or batch_no or barcode:
|
||||||
return "item.name = {0}".format(frappe.db.escape(item_code))
|
return "item.name = {0}".format(frappe.db.escape(item_code))
|
||||||
|
|
||||||
return """(item.name like {item_code}
|
return make_condition(item_code)
|
||||||
or item.item_name like {item_code})""".format(item_code = frappe.db.escape('%' + item_code + '%'))
|
|
||||||
|
def make_condition(item_code):
|
||||||
|
condition = "("
|
||||||
|
condition += """item.name like {item_code}
|
||||||
|
or item.item_name like {item_code}""".format(item_code = frappe.db.escape('%' + item_code + '%'))
|
||||||
|
condition += add_search_fields_condition(item_code)
|
||||||
|
condition += ")"
|
||||||
|
|
||||||
|
return condition
|
||||||
|
|
||||||
|
def add_search_fields_condition(item_code):
|
||||||
|
condition = ''
|
||||||
|
search_fields = frappe.get_all('POS Search Fields', fields = ['fieldname'])
|
||||||
|
if search_fields:
|
||||||
|
for field in search_fields:
|
||||||
|
condition += " or item.{0} like {1}".format(field['fieldname'], frappe.db.escape('%' + item_code + '%'))
|
||||||
|
return condition
|
||||||
|
|
||||||
def get_item_group_condition(pos_profile):
|
def get_item_group_condition(pos_profile):
|
||||||
cond = "and 1=1"
|
cond = "and 1=1"
|
||||||
|
|||||||
@ -168,6 +168,7 @@ erpnext.PointOfSale.ItemSelector = class {
|
|||||||
case (iCode >= 160 && iCode <= 164) || iCode == 170: // ^ ! # $ *
|
case (iCode >= 160 && iCode <= 164) || iCode == 170: // ^ ! # $ *
|
||||||
case iCode >= 186 && iCode <= 194: // (; = , - . / `)
|
case iCode >= 186 && iCode <= 194: // (; = , - . / `)
|
||||||
case iCode >= 219 && iCode <= 222: // ([ \ ] ')
|
case iCode >= 219 && iCode <= 222: // ([ \ ] ')
|
||||||
|
case iCode == 32: // spacebar
|
||||||
if (oEvent.key !== undefined && oEvent.key !== '') {
|
if (oEvent.key !== undefined && oEvent.key !== '') {
|
||||||
return oEvent.key;
|
return oEvent.key;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -159,6 +159,7 @@ class NamingSeries(Document):
|
|||||||
if frappe.db.get_value('Series', series, 'name', order_by="name") == None:
|
if frappe.db.get_value('Series', series, 'name', order_by="name") == None:
|
||||||
frappe.db.sql("insert into tabSeries (name, current) values (%s, 0)", (series))
|
frappe.db.sql("insert into tabSeries (name, current) values (%s, 0)", (series))
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def update_series_start(self):
|
def update_series_start(self):
|
||||||
if self.prefix:
|
if self.prefix:
|
||||||
prefix = self.parse_naming_series()
|
prefix = self.parse_naming_series()
|
||||||
|
|||||||
@ -46,9 +46,6 @@ frappe.ui.form.on("Item", {
|
|||||||
}, __("View"));
|
}, __("View"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!frm.doc.is_fixed_asset) {
|
|
||||||
erpnext.item.make_dashboard(frm);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frm.doc.is_fixed_asset) {
|
if (frm.doc.is_fixed_asset) {
|
||||||
frm.trigger('is_fixed_asset');
|
frm.trigger('is_fixed_asset');
|
||||||
@ -97,6 +94,10 @@ frappe.ui.form.on("Item", {
|
|||||||
erpnext.item.edit_prices_button(frm);
|
erpnext.item.edit_prices_button(frm);
|
||||||
erpnext.item.toggle_attributes(frm);
|
erpnext.item.toggle_attributes(frm);
|
||||||
|
|
||||||
|
if (!frm.doc.is_fixed_asset) {
|
||||||
|
erpnext.item.make_dashboard(frm);
|
||||||
|
}
|
||||||
|
|
||||||
frm.add_custom_button(__('Duplicate'), function() {
|
frm.add_custom_button(__('Duplicate'), function() {
|
||||||
var new_item = frappe.model.copy_doc(frm.doc);
|
var new_item = frappe.model.copy_doc(frm.doc);
|
||||||
if(new_item.item_name===new_item.item_code) {
|
if(new_item.item_name===new_item.item_code) {
|
||||||
@ -473,11 +474,15 @@ $.extend(erpnext.item, {
|
|||||||
me.multiple_variant_dialog.get_primary_btn().html(__('Create Variants'));
|
me.multiple_variant_dialog.get_primary_btn().html(__('Create Variants'));
|
||||||
me.multiple_variant_dialog.disable_primary_action();
|
me.multiple_variant_dialog.disable_primary_action();
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
let no_of_combinations = lengths.reduce((a, b) => a * b, 1);
|
let no_of_combinations = lengths.reduce((a, b) => a * b, 1);
|
||||||
me.multiple_variant_dialog.get_primary_btn()
|
let msg;
|
||||||
.html(__(
|
if (no_of_combinations === 1) {
|
||||||
`Make ${no_of_combinations} Variant${no_of_combinations === 1 ? '' : 's'}`
|
msg = __("Make {0} Variant", [no_of_combinations]);
|
||||||
));
|
} else {
|
||||||
|
msg = __("Make {0} Variants", [no_of_combinations]);
|
||||||
|
}
|
||||||
|
me.multiple_variant_dialog.get_primary_btn().html(msg);
|
||||||
me.multiple_variant_dialog.enable_primary_action();
|
me.multiple_variant_dialog.enable_primary_action();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -63,7 +63,7 @@ class Item(WebsiteGenerator):
|
|||||||
if self.variant_of:
|
if self.variant_of:
|
||||||
if not self.item_code:
|
if not self.item_code:
|
||||||
template_item_name = frappe.db.get_value("Item", self.variant_of, "item_name")
|
template_item_name = frappe.db.get_value("Item", self.variant_of, "item_name")
|
||||||
self.item_code = make_variant_item_code(self.variant_of, template_item_name, self)
|
make_variant_item_code(self.variant_of, template_item_name, self)
|
||||||
else:
|
else:
|
||||||
from frappe.model.naming import set_name_by_naming_series
|
from frappe.model.naming import set_name_by_naming_series
|
||||||
set_name_by_naming_series(self)
|
set_name_by_naming_series(self)
|
||||||
|
|||||||
@ -13,10 +13,11 @@ class ItemVariantSettings(Document):
|
|||||||
def set_default_fields(self):
|
def set_default_fields(self):
|
||||||
self.fields = []
|
self.fields = []
|
||||||
fields = frappe.get_meta('Item').fields
|
fields = frappe.get_meta('Item').fields
|
||||||
exclude_fields = ["naming_series", "item_code", "item_name", "show_in_website",
|
exclude_fields = {"naming_series", "item_code", "item_name", "show_in_website",
|
||||||
"show_variant_in_website", "standard_rate", "opening_stock", "image", "description",
|
"show_variant_in_website", "standard_rate", "opening_stock", "image", "description",
|
||||||
"variant_of", "valuation_rate", "description", "barcodes",
|
"variant_of", "valuation_rate", "description", "barcodes",
|
||||||
"website_image", "thumbnail", "website_specifiations", "web_long_description"]
|
"website_image", "thumbnail", "website_specifiations", "web_long_description",
|
||||||
|
"has_variants", "attributes"}
|
||||||
|
|
||||||
for d in fields:
|
for d in fields:
|
||||||
if not d.no_copy and d.fieldname not in exclude_fields and \
|
if not d.no_copy and d.fieldname not in exclude_fields and \
|
||||||
|
|||||||
@ -433,13 +433,21 @@ erpnext.buying.MaterialRequestController = erpnext.buying.BuyingController.exten
|
|||||||
if (doc.material_request_type == "Customer Provided") {
|
if (doc.material_request_type == "Customer Provided") {
|
||||||
return{
|
return{
|
||||||
query: "erpnext.controllers.queries.item_query",
|
query: "erpnext.controllers.queries.item_query",
|
||||||
filters:{ 'customer': me.frm.doc.customer }
|
filters:{
|
||||||
|
'customer': me.frm.doc.customer,
|
||||||
|
'is_stock_item':1
|
||||||
}
|
}
|
||||||
} else if (doc.material_request_type != "Manufacture") {
|
}
|
||||||
|
} else if (doc.material_request_type == "Purchase") {
|
||||||
return{
|
return{
|
||||||
query: "erpnext.controllers.queries.item_query",
|
query: "erpnext.controllers.queries.item_query",
|
||||||
filters: {'is_purchase_item': 1}
|
filters: {'is_purchase_item': 1}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return{
|
||||||
|
query: "erpnext.controllers.queries.item_query",
|
||||||
|
filters: {'is_stock_item': 1}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@ -73,6 +73,34 @@ frappe.ui.form.on("Purchase Receipt", {
|
|||||||
})
|
})
|
||||||
}, __('Create'));
|
}, __('Create'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
frm.events.add_custom_buttons(frm);
|
||||||
|
},
|
||||||
|
|
||||||
|
add_custom_buttons: function(frm) {
|
||||||
|
if (frm.doc.docstatus == 0) {
|
||||||
|
frm.add_custom_button(__('Purchase Invoice'), function () {
|
||||||
|
if (!frm.doc.supplier) {
|
||||||
|
frappe.throw({
|
||||||
|
title: __("Mandatory"),
|
||||||
|
message: __("Please Select a Supplier")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
erpnext.utils.map_current_doc({
|
||||||
|
method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_purchase_receipt",
|
||||||
|
source_doctype: "Purchase Invoice",
|
||||||
|
target: frm,
|
||||||
|
setters: {
|
||||||
|
supplier: frm.doc.supplier,
|
||||||
|
},
|
||||||
|
get_query_filters: {
|
||||||
|
docstatus: 1,
|
||||||
|
per_received: ["<", 100],
|
||||||
|
company: frm.doc.company
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, __("Get Items From"));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
company: function(frm) {
|
company: function(frm) {
|
||||||
|
|||||||
@ -53,7 +53,20 @@ class PurchaseReceipt(BuyingController):
|
|||||||
'target_ref_field': 'stock_qty',
|
'target_ref_field': 'stock_qty',
|
||||||
'source_field': 'stock_qty',
|
'source_field': 'stock_qty',
|
||||||
'percent_join_field': 'material_request'
|
'percent_join_field': 'material_request'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'source_dt': 'Purchase Receipt Item',
|
||||||
|
'target_dt': 'Purchase Invoice Item',
|
||||||
|
'join_field': 'purchase_invoice_item',
|
||||||
|
'target_field': 'received_qty',
|
||||||
|
'target_parent_dt': 'Purchase Invoice',
|
||||||
|
'target_parent_field': 'per_received',
|
||||||
|
'target_ref_field': 'qty',
|
||||||
|
'source_field': 'received_qty',
|
||||||
|
'percent_join_field': 'purchase_invoice',
|
||||||
|
'overflow_type': 'receipt'
|
||||||
}]
|
}]
|
||||||
|
|
||||||
if cint(self.is_return):
|
if cint(self.is_return):
|
||||||
self.status_updater.extend([
|
self.status_updater.extend([
|
||||||
{
|
{
|
||||||
@ -221,6 +234,7 @@ class PurchaseReceipt(BuyingController):
|
|||||||
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
|
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
|
||||||
self.delete_auto_created_batches()
|
self.delete_auto_created_batches()
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def get_current_stock(self):
|
def get_current_stock(self):
|
||||||
for d in self.get('supplied_items'):
|
for d in self.get('supplied_items'):
|
||||||
if self.supplier_warehouse:
|
if self.supplier_warehouse:
|
||||||
@ -513,7 +527,9 @@ class PurchaseReceipt(BuyingController):
|
|||||||
def update_billing_status(self, update_modified=True):
|
def update_billing_status(self, update_modified=True):
|
||||||
updated_pr = [self.name]
|
updated_pr = [self.name]
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
if d.purchase_order_item:
|
if d.purchase_invoice and d.purchase_invoice_item:
|
||||||
|
d.db_set('billed_amt', d.amount, update_modified=update_modified)
|
||||||
|
elif d.purchase_order_item:
|
||||||
updated_pr += update_billed_amount_based_on_po(d.purchase_order_item, update_modified)
|
updated_pr += update_billed_amount_based_on_po(d.purchase_order_item, update_modified)
|
||||||
|
|
||||||
for pr in set(updated_pr):
|
for pr in set(updated_pr):
|
||||||
|
|||||||
@ -72,16 +72,18 @@
|
|||||||
"warehouse",
|
"warehouse",
|
||||||
"rejected_warehouse",
|
"rejected_warehouse",
|
||||||
"from_warehouse",
|
"from_warehouse",
|
||||||
"purchase_order",
|
|
||||||
"material_request",
|
"material_request",
|
||||||
|
"purchase_order",
|
||||||
|
"purchase_invoice",
|
||||||
"column_break_40",
|
"column_break_40",
|
||||||
"is_fixed_asset",
|
"is_fixed_asset",
|
||||||
"asset_location",
|
"asset_location",
|
||||||
"asset_category",
|
"asset_category",
|
||||||
"schedule_date",
|
"schedule_date",
|
||||||
"quality_inspection",
|
"quality_inspection",
|
||||||
"purchase_order_item",
|
|
||||||
"material_request_item",
|
"material_request_item",
|
||||||
|
"purchase_order_item",
|
||||||
|
"purchase_invoice_item",
|
||||||
"purchase_receipt_item",
|
"purchase_receipt_item",
|
||||||
"delivery_note_item",
|
"delivery_note_item",
|
||||||
"putaway_rule",
|
"putaway_rule",
|
||||||
@ -937,7 +939,21 @@
|
|||||||
"fieldname": "base_rate_with_margin",
|
"fieldname": "base_rate_with_margin",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Rate With Margin (Company Currency)",
|
"label": "Rate With Margin (Company Currency)",
|
||||||
"options": "Company:company:default_currency",
|
"options": "Company:company:default_currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "purchase_invoice",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Purchase Invoice",
|
||||||
|
"options": "Purchase Invoice",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "purchase_invoice_item",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Purchase Invoice Item",
|
||||||
|
"no_copy": 1,
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
}
|
}
|
||||||
@ -945,7 +961,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-02-23 00:59:14.360847",
|
"modified": "2021-03-29 04:17:00.336298",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Purchase Receipt Item",
|
"name": "Purchase Receipt Item",
|
||||||
|
|||||||
@ -398,8 +398,12 @@ class StockEntry(StockController):
|
|||||||
and item_code = %s
|
and item_code = %s
|
||||||
and ifnull(s_warehouse,'')='' """ % (", ".join(["%s" * len(other_ste)]), "%s"), args)[0][0]
|
and ifnull(s_warehouse,'')='' """ % (", ".join(["%s" * len(other_ste)]), "%s"), args)[0][0]
|
||||||
if fg_qty_already_entered and fg_qty_already_entered >= qty:
|
if fg_qty_already_entered and fg_qty_already_entered >= qty:
|
||||||
frappe.throw(_("Stock Entries already created for Work Order ")
|
frappe.throw(
|
||||||
+ self.work_order + ":" + ", ".join(other_ste), DuplicateEntryForWorkOrderError)
|
_("Stock Entries already created for Work Order {0}: {1}").format(
|
||||||
|
self.work_order, ", ".join(other_ste)
|
||||||
|
),
|
||||||
|
DuplicateEntryForWorkOrderError,
|
||||||
|
)
|
||||||
|
|
||||||
def set_actual_qty(self):
|
def set_actual_qty(self):
|
||||||
allow_negative_stock = cint(frappe.db.get_value("Stock Settings", None, "allow_negative_stock"))
|
allow_negative_stock = cint(frappe.db.get_value("Stock Settings", None, "allow_negative_stock"))
|
||||||
@ -435,6 +439,7 @@ class StockEntry(StockController):
|
|||||||
if transferred_serial_no:
|
if transferred_serial_no:
|
||||||
d.serial_no = transferred_serial_no
|
d.serial_no = transferred_serial_no
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def get_stock_and_rate(self):
|
def get_stock_and_rate(self):
|
||||||
"""
|
"""
|
||||||
Updates rate and availability of all the items.
|
Updates rate and availability of all the items.
|
||||||
|
|||||||
@ -469,7 +469,7 @@ class StockReconciliation(StockController):
|
|||||||
def submit(self):
|
def submit(self):
|
||||||
if len(self.items) > 100:
|
if len(self.items) > 100:
|
||||||
msgprint(_("The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Draft stage"))
|
msgprint(_("The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Draft stage"))
|
||||||
self.queue_action('submit')
|
self.queue_action('submit', timeout=2000)
|
||||||
else:
|
else:
|
||||||
self._submit()
|
self._submit()
|
||||||
|
|
||||||
|
|||||||
@ -86,7 +86,7 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru
|
|||||||
out.update(get_bin_details(args.item_code, args.get("from_warehouse")))
|
out.update(get_bin_details(args.item_code, args.get("from_warehouse")))
|
||||||
|
|
||||||
elif out.get("warehouse"):
|
elif out.get("warehouse"):
|
||||||
out.update(get_bin_details(args.item_code, out.warehouse))
|
out.update(get_bin_details(args.item_code, out.warehouse, args.company))
|
||||||
|
|
||||||
# update args with out, if key or value not exists
|
# update args with out, if key or value not exists
|
||||||
for key, value in iteritems(out):
|
for key, value in iteritems(out):
|
||||||
|
|||||||
@ -416,7 +416,7 @@ class update_entries_after(object):
|
|||||||
frappe.db.set_value("Stock Entry Detail", sle.voucher_detail_no, "basic_rate", outgoing_rate)
|
frappe.db.set_value("Stock Entry Detail", sle.voucher_detail_no, "basic_rate", outgoing_rate)
|
||||||
|
|
||||||
# Update outgoing item's rate, recalculate FG Item's rate and total incoming/outgoing amount
|
# Update outgoing item's rate, recalculate FG Item's rate and total incoming/outgoing amount
|
||||||
stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no)
|
stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no, for_update=True)
|
||||||
stock_entry.calculate_rate_and_amount(reset_outgoing_rate=False, raise_error_if_no_rate=False)
|
stock_entry.calculate_rate_and_amount(reset_outgoing_rate=False, raise_error_if_no_rate=False)
|
||||||
stock_entry.db_update()
|
stock_entry.db_update()
|
||||||
for d in stock_entry.items:
|
for d in stock_entry.items:
|
||||||
|
|||||||
@ -172,7 +172,7 @@ def get_bin(item_code, warehouse):
|
|||||||
bin_obj.flags.ignore_permissions = 1
|
bin_obj.flags.ignore_permissions = 1
|
||||||
bin_obj.insert()
|
bin_obj.insert()
|
||||||
else:
|
else:
|
||||||
bin_obj = frappe.get_cached_doc('Bin', bin)
|
bin_obj = frappe.get_doc('Bin', bin, for_update=True)
|
||||||
bin_obj.flags.ignore_permissions = True
|
bin_obj.flags.ignore_permissions = True
|
||||||
return bin_obj
|
return bin_obj
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
"url": "https://github.com/frappe/erpnext/issues"
|
"url": "https://github.com/frappe/erpnext/issues"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"snyk": "^1.290.1"
|
"snyk": "^1.518.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"onscan.js": "^1.5.2"
|
"onscan.js": "^1.5.2"
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
frappe
|
# frappe # https://github.com/frappe/frappe is installed during bench-init
|
||||||
gocardless-pro~=1.22.0
|
gocardless-pro~=1.22.0
|
||||||
googlemaps # used in ERPNext, but dependency is defined in Frappe
|
googlemaps # used in ERPNext, but dependency is defined in Frappe
|
||||||
pandas~=1.1.5
|
pandas~=1.1.5
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user