Merge branch 'develop' of https://github.com/frappe/erpnext into psoa_fixes

This commit is contained in:
Deepesh Garg
2021-04-26 14:51:20 +05:30
70 changed files with 1283 additions and 609 deletions

View File

@@ -85,10 +85,9 @@ jobs:
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==2.2.0 pip install coveralls==3.0.1
pip install coverage==4.5.4 pip install coverage==5.5
coveralls 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 }}

View File

@@ -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.

View File

@@ -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.0.0-dev' __version__ = '13.1.0'
def get_default_company(user=None): def get_default_company(user=None):
'''Get default company for user''' '''Get default company for user'''

View File

@@ -12,6 +12,7 @@
"frozen_accounts_modifier", "frozen_accounts_modifier",
"determine_address_tax_category_from", "determine_address_tax_category_from",
"over_billing_allowance", "over_billing_allowance",
"role_allowed_to_over_bill",
"column_break_4", "column_break_4",
"credit_controller", "credit_controller",
"check_supplier_invoice_uniqueness", "check_supplier_invoice_uniqueness",
@@ -226,6 +227,13 @@
"fieldname": "delete_linked_ledger_entries", "fieldname": "delete_linked_ledger_entries",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Delete Accounting and Stock Ledger Entries on deletion of Transaction" "label": "Delete Accounting and Stock Ledger Entries on deletion of Transaction"
},
{
"description": "Users with this role are allowed to over bill above the allowance percentage",
"fieldname": "role_allowed_to_over_bill",
"fieldtype": "Link",
"label": "Role Allowed to Over Bill ",
"options": "Role"
} }
], ],
"icon": "icon-cog", "icon": "icon-cog",
@@ -233,7 +241,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2021-01-05 13:04:00.118892", "modified": "2021-03-11 18:52:05.601996",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounts Settings", "name": "Accounts Settings",

View File

@@ -78,8 +78,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
if ( if (
frm.doc.bank_account && frm.doc.bank_account &&
frm.doc.bank_statement_from_date && frm.doc.bank_statement_from_date &&
frm.doc.bank_statement_to_date && frm.doc.bank_statement_to_date
frm.doc.bank_statement_closing_balance
) { ) {
frm.trigger("render_chart"); frm.trigger("render_chart");
frm.trigger("render"); frm.trigger("render");

View File

@@ -39,13 +39,13 @@
"depends_on": "eval: doc.bank_account", "depends_on": "eval: doc.bank_account",
"fieldname": "bank_statement_from_date", "fieldname": "bank_statement_from_date",
"fieldtype": "Date", "fieldtype": "Date",
"label": "Bank Statement From Date" "label": "From Date"
}, },
{ {
"depends_on": "eval: doc.bank_statement_from_date", "depends_on": "eval: doc.bank_statement_from_date",
"fieldname": "bank_statement_to_date", "fieldname": "bank_statement_to_date",
"fieldtype": "Date", "fieldtype": "Date",
"label": "Bank Statement To Date" "label": "To Date"
}, },
{ {
"fieldname": "column_break_2", "fieldname": "column_break_2",
@@ -63,11 +63,10 @@
"depends_on": "eval: doc.bank_statement_to_date", "depends_on": "eval: doc.bank_statement_to_date",
"fieldname": "bank_statement_closing_balance", "fieldname": "bank_statement_closing_balance",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Bank Statement Closing Balance", "label": "Closing Balance",
"options": "Currency" "options": "Currency"
}, },
{ {
"depends_on": "eval: doc.bank_statement_closing_balance",
"fieldname": "section_break_1", "fieldname": "section_break_1",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Reconcile" "label": "Reconcile"
@@ -90,7 +89,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2021-02-02 01:35:53.043578", "modified": "2021-04-21 11:13:49.831769",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Bank Reconciliation Tool", "name": "Bank Reconciliation Tool",

View File

@@ -21,21 +21,17 @@ frappe.ui.form.on('Exchange Rate Revaluation', {
refresh: function(frm) { refresh: function(frm) {
if(frm.doc.docstatus==1) { if(frm.doc.docstatus==1) {
frappe.db.get_value("Journal Entry Account", { frappe.call({
'reference_type': 'Exchange Rate Revaluation', method: 'check_journal_entry_condition',
'reference_name': frm.doc.name, doc: frm.doc,
'docstatus': 1 callback: function(r) {
}, "sum(debit) as sum", (r) =>{ if (r.message) {
let total_amt = 0; frm.add_custom_button(__('Journal Entry'), function() {
frm.doc.accounts.forEach(d=> { return frm.events.make_jv(frm);
total_amt = total_amt + d['new_balance_in_base_currency']; }, __('Create'));
}); }
if(total_amt !== r.sum) {
frm.add_custom_button(__('Journal Entry'), function() {
return frm.events.make_jv(frm);
}, __('Create'));
} }
}, 'Journal Entry'); });
} }
}, },

View File

@@ -27,6 +27,23 @@ class ExchangeRateRevaluation(Document):
if not (self.company and self.posting_date): if not (self.company and self.posting_date):
frappe.throw(_("Please select Company and Posting Date to getting entries")) frappe.throw(_("Please select Company and Posting Date to getting entries"))
@frappe.whitelist()
def check_journal_entry_condition(self):
total_debit = frappe.db.get_value("Journal Entry Account", {
'reference_type': 'Exchange Rate Revaluation',
'reference_name': self.name,
'docstatus': 1
}, "sum(debit) as sum")
total_amt = 0
for d in self.accounts:
total_amt = total_amt + d.new_balance_in_base_currency
if total_amt != total_debit:
return True
return False
@frappe.whitelist() @frappe.whitelist()
def get_accounts_data(self, account=None): def get_accounts_data(self, account=None):
accounts = [] accounts = []

View File

@@ -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

View File

@@ -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);
}, },
@@ -606,9 +606,9 @@ frappe.ui.form.on('Payment Entry', {
{fieldtype:"Float", label: __("Less Than Amount"), fieldname:"outstanding_amt_less_than"}, {fieldtype:"Float", label: __("Less Than Amount"), fieldname:"outstanding_amt_less_than"},
{fieldtype:"Section Break"}, {fieldtype:"Section Break"},
{fieldtype:"Link", label:__("Cost Center"), fieldname:"cost_center", options:"Cost Center", {fieldtype:"Link", label:__("Cost Center"), fieldname:"cost_center", options:"Cost Center",
"get_query": function() { "get_query": function() {
return { return {
"filters": {"company": frm.doc.company} "filters": {"company": frm.doc.company}
} }
} }
}, },
@@ -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));
} }
} }

View File

@@ -16,28 +16,8 @@ class POSClosingEntry(StatusUpdater):
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open": if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry")) frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
self.validate_pos_closing()
self.validate_pos_invoices() self.validate_pos_invoices()
def validate_pos_closing(self):
user = frappe.db.sql("""
SELECT name FROM `tabPOS Closing Entry`
WHERE
user = %(user)s AND docstatus = 1 AND pos_profile = %(profile)s AND
(period_start_date between %(start)s and %(end)s OR period_end_date between %(start)s and %(end)s)
""", {
'user': self.user,
'profile': self.pos_profile,
'start': self.period_start_date,
'end': self.period_end_date
})
if user:
bold_already_exists = frappe.bold(_("already exists"))
bold_user = frappe.bold(self.user)
frappe.throw(_("POS Closing Entry {} against {} between selected period")
.format(bold_already_exists, bold_user), title=_("Invalid Period"))
def validate_pos_invoices(self): def validate_pos_invoices(self):
invalid_rows = [] invalid_rows = []
for d in self.pos_transactions: for d in self.pos_transactions:
@@ -89,8 +69,8 @@ class POSClosingEntry(StatusUpdater):
@frappe.whitelist() @frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs @frappe.validate_and_sanitize_search_inputs
def get_cashiers(doctype, txt, searchfield, start, page_len, filters): def get_cashiers(doctype, txt, searchfield, start, page_len, filters):
cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user']) cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user'], as_list=1)
return [c['user'] for c in cashiers_list] return [c for c in cashiers_list]
@frappe.whitelist() @frappe.whitelist()
def get_pos_invoices(start, end, pos_profile, user): def get_pos_invoices(start, end, pos_profile, user):

View File

@@ -96,30 +96,45 @@ class POSInvoice(SalesInvoice):
if paid_amt and pay.amount != paid_amt: if paid_amt and pay.amount != paid_amt:
return frappe.throw(_("Payment related to {0} is not completed").format(pay.mode_of_payment)) return frappe.throw(_("Payment related to {0} is not completed").format(pay.mode_of_payment))
def validate_pos_reserved_serial_nos(self, item):
serial_nos = get_serial_nos(item.serial_no)
filters = {"item_code": item.item_code, "warehouse": item.warehouse}
if item.batch_no:
filters["batch_no"] = item.batch_no
reserved_serial_nos = get_pos_reserved_serial_nos(filters)
invalid_serial_nos = [s for s in serial_nos if s in reserved_serial_nos]
bold_invalid_serial_nos = frappe.bold(', '.join(invalid_serial_nos))
if len(invalid_serial_nos) == 1:
frappe.throw(_("Row #{}: Serial No. {} has already been transacted into another POS Invoice. Please select valid serial no.")
.format(item.idx, bold_invalid_serial_nos), title=_("Item Unavailable"))
elif invalid_serial_nos:
frappe.throw(_("Row #{}: Serial Nos. {} has already been transacted into another POS Invoice. Please select valid serial no.")
.format(item.idx, bold_invalid_serial_nos), title=_("Item Unavailable"))
def validate_delivered_serial_nos(self, item):
serial_nos = get_serial_nos(item.serial_no)
delivered_serial_nos = frappe.db.get_list('Serial No', {
'item_code': item.item_code,
'name': ['in', serial_nos],
'sales_invoice': ['is', 'set']
}, pluck='name')
if delivered_serial_nos:
bold_delivered_serial_nos = frappe.bold(', '.join(delivered_serial_nos))
frappe.throw(_("Row #{}: Serial No. {} has already been transacted into another Sales Invoice. Please select valid serial no.")
.format(item.idx, bold_delivered_serial_nos), title=_("Item Unavailable"))
def validate_stock_availablility(self): def validate_stock_availablility(self):
if self.is_return: if self.is_return:
return return
allow_negative_stock = frappe.db.get_value('Stock Settings', None, 'allow_negative_stock') allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock')
error_msg = []
for d in self.get('items'): for d in self.get('items'):
msg = ""
if d.serial_no: if d.serial_no:
filters = { "item_code": d.item_code, "warehouse": d.warehouse } self.validate_pos_reserved_serial_nos(d)
if d.batch_no: self.validate_delivered_serial_nos(d)
filters["batch_no"] = d.batch_no
reserved_serial_nos = get_pos_reserved_serial_nos(filters)
serial_nos = get_serial_nos(d.serial_no)
invalid_serial_nos = [s for s in serial_nos if s in reserved_serial_nos]
bold_invalid_serial_nos = frappe.bold(', '.join(invalid_serial_nos))
if len(invalid_serial_nos) == 1:
msg = (_("Row #{}: Serial No. {} has already been transacted into another POS Invoice. Please select valid serial no.")
.format(d.idx, bold_invalid_serial_nos))
elif invalid_serial_nos:
msg = (_("Row #{}: Serial Nos. {} has already been transacted into another POS Invoice. Please select valid serial no.")
.format(d.idx, bold_invalid_serial_nos))
else: else:
if allow_negative_stock: if allow_negative_stock:
return return
@@ -127,15 +142,11 @@ class POSInvoice(SalesInvoice):
available_stock = get_stock_availability(d.item_code, d.warehouse) available_stock = get_stock_availability(d.item_code, d.warehouse)
item_code, warehouse, qty = frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty) item_code, warehouse, qty = frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty)
if flt(available_stock) <= 0: if flt(available_stock) <= 0:
msg = (_('Row #{}: Item Code: {} is not available under warehouse {}.').format(d.idx, item_code, warehouse)) frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.')
.format(d.idx, item_code, warehouse), title=_("Item Unavailable"))
elif flt(available_stock) < flt(d.qty): elif flt(available_stock) < flt(d.qty):
msg = (_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}.') frappe.throw(_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}.')
.format(d.idx, item_code, warehouse, qty)) .format(d.idx, item_code, warehouse, available_stock), title=_("Item Unavailable"))
if msg:
error_msg.append(msg)
if error_msg:
frappe.throw(error_msg, title=_("Item Unavailable"), as_list=True)
def validate_serialised_or_batched_item(self): def validate_serialised_or_batched_item(self):
error_msg = [] error_msg = []
@@ -202,9 +213,8 @@ class POSInvoice(SalesInvoice):
for d in self.get("items"): for d in self.get("items"):
is_stock_item = frappe.get_cached_value("Item", d.get("item_code"), "is_stock_item") is_stock_item = frappe.get_cached_value("Item", d.get("item_code"), "is_stock_item")
if not is_stock_item: if not is_stock_item:
frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice. ").format( frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice. ")
d.idx, frappe.bold(d.item_code) .format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
), title=_("Invalid Item"))
def validate_mode_of_payment(self): def validate_mode_of_payment(self):
if len(self.payments) == 0: if len(self.payments) == 0:

View File

@@ -10,10 +10,12 @@ from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.item.test_item import make_item
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
class TestPOSInvoice(unittest.TestCase): class TestPOSInvoice(unittest.TestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item", qty=800, basic_rate=100)
frappe.db.sql("delete from `tabTax Rule`") frappe.db.sql("delete from `tabTax Rule`")
def tearDown(self): def tearDown(self):
@@ -320,6 +322,34 @@ class TestPOSInvoice(unittest.TestCase):
self.assertRaises(frappe.ValidationError, pos2.insert) self.assertRaises(frappe.ValidationError, pos2.insert)
def test_delivered_serialized_item_transaction(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
se = make_serialized_item(company='_Test Company',
target_warehouse="Stores - _TC", cost_center='Main - _TC', expense_account='Cost of Goods Sold - _TC')
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
si = create_sales_invoice(company='_Test Company', debit_to='Debtors - _TC',
account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC',
expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC',
item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
si.get("items")[0].serial_no = serial_nos[0]
si.insert()
si.submit()
pos2 = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC',
account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC',
expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC',
item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
pos2.get("items")[0].serial_no = serial_nos[0]
pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000})
self.assertRaises(frappe.ValidationError, pos2.insert)
def test_loyalty_points(self): def test_loyalty_points(self):
from erpnext.accounts.doctype.loyalty_program.test_loyalty_program import create_records from erpnext.accounts.doctype.loyalty_program.test_loyalty_program import create_records
from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points

View File

@@ -173,7 +173,7 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True):
if parenttype in ["Customer Group", "Item Group", "Territory"]: if parenttype in ["Customer Group", "Item Group", "Territory"]:
parent_field = "parent_{0}".format(frappe.scrub(parenttype)) parent_field = "parent_{0}".format(frappe.scrub(parenttype))
root_name = frappe.db.get_list(parenttype, root_name = frappe.db.get_list(parenttype,
{"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1) {"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1, ignore_permissions=True)
if root_name and root_name[0][0]: if root_name and root_name[0][0]:
parent_groups.append(root_name[0][0]) parent_groups.append(root_name[0][0])

View File

@@ -0,0 +1,129 @@
# Version 13.1.0 Release Notes
### Features
- Recursive pricing rule ([#24922](https://github.com/frappe/erpnext/pull/24922))
- Discount configuration on early payments ([#24586](https://github.com/frappe/erpnext/pull/24586))
- Bulk e-invoice generation ([#24969](https://github.com/frappe/erpnext/pull/24969))
- Employee Self Service ([#24408](https://github.com/frappe/erpnext/pull/24408))
- Share doc with employee approvers if they don't have access ([#25190](https://github.com/frappe/erpnext/pull/25190))
- Price margin in buying ([#24685](https://github.com/frappe/erpnext/pull/24685))
- Allow changing Work Stations in Work Order & Job Card ([#24897](https://github.com/frappe/erpnext/pull/24897))
- Add document type field for e-invoicing (Italy) ([#25256](https://github.com/frappe/erpnext/pull/25256))
- Add checkbox for disabling leave notification in HR Settings ([#24877](https://github.com/frappe/erpnext/pull/24877))
- Enhancements in Material Request Plan Item in Production Plan ([#25025](https://github.com/frappe/erpnext/pull/25025))
### Fixes and Enhancements
- Mode of payments disappear on loading draft pos invoice ([#24917](https://github.com/frappe/erpnext/pull/24917))
- Sales order not saving due type mismatch in promo scheme (#24748) ([#25222](https://github.com/frappe/erpnext/pull/25222))
- Zero amount completed delivery notes being shown in Sales Invoice get items ([#25317](https://github.com/frappe/erpnext/pull/25317))
- Incorrect status creating PR from PO after creating PI ([#25109](https://github.com/frappe/erpnext/pull/25109))
- Precision and formatted document for stock level in item dashboard. ([#24921](https://github.com/frappe/erpnext/pull/24921))
- Precision issues while allocating advance amount ([#25086](https://github.com/frappe/erpnext/pull/25086))
- Round off final tax amount instead of current tax amount ([#25188](https://github.com/frappe/erpnext/pull/25188))
- Redesign fixes ([#24896](https://github.com/frappe/erpnext/pull/24896))
- TDS check getting checked after reload ([#24972](https://github.com/frappe/erpnext/pull/24972))
- Github Action not failing when tests fail ([#24867](https://github.com/frappe/erpnext/pull/24867))
- Calculate 80g certificate amount on validate for memberships ([#24925](https://github.com/frappe/erpnext/pull/24925))
- Purchase from registered composition dealer ([#25040](https://github.com/frappe/erpnext/pull/25040))
- Reduce number of queries for checking if future SL entry exists ([#24881](https://github.com/frappe/erpnext/pull/24881))
- Remove unwanted parameter in calculate_rate_and_amount ([#24883](https://github.com/frappe/erpnext/pull/24883))
- Membership renewal validation ([#24963](https://github.com/frappe/erpnext/pull/24963))
- Not able to save material request ([#25112](https://github.com/frappe/erpnext/pull/25112))
- POS print receipt ([#25330](https://github.com/frappe/erpnext/pull/25330))
- Supplier was not able to Submit RFQ due to insufficient permission ([#24622](https://github.com/frappe/erpnext/pull/24622))
- Unequal debit and credit issue on RCM Invoice ([#24836](https://github.com/frappe/erpnext/pull/24836))
- Picked Qty conversion from Stock Qty to Qty while creating DN from Pick List ([#25105](https://github.com/frappe/erpnext/pull/25105))
- Salary Structure object has no attribute set_totals ([#25113](https://github.com/frappe/erpnext/pull/25113))
- Incorrect Nil Exempt and Non GST amount in GSTR3B report ([#24916](https://github.com/frappe/erpnext/pull/24916))
- Add method for regional round off account back ([#24893](https://github.com/frappe/erpnext/pull/24893))
- Employee profile pic upload access for erpnext user ([#25022](https://github.com/frappe/erpnext/pull/25022))
- Make filters for payroll entry ([#25386](https://github.com/frappe/erpnext/pull/25386))
- Fix dynamically changing grid properties ([#25310](https://github.com/frappe/erpnext/pull/25310))
- Consider paid repayment entries in subsequent loan repayments ([#25271](https://github.com/frappe/erpnext/pull/25271))
- Allow duplicate additional salaries ([#24842](https://github.com/frappe/erpnext/pull/24842))
- Object referencing the same address issue ([#25159](https://github.com/frappe/erpnext/pull/25159))
- Validating party currency with doc currency ([#24318](https://github.com/frappe/erpnext/pull/24318))
- Non Profit fixes ([#25060](https://github.com/frappe/erpnext/pull/25060))
- Additional Salary component amount not getting set ([#25356](https://github.com/frappe/erpnext/pull/25356))
- Allow user to update exchange rate in Multi-currency LCV ([#24912](https://github.com/frappe/erpnext/pull/24912))
- Allow creating stock entry based on work order for customer provided items ([#24885](https://github.com/frappe/erpnext/pull/24885))
- Create property setters for shorter naming series on setup ([#25128](https://github.com/frappe/erpnext/pull/25128))
- Add GST category field in Delivery Note ([#25053](https://github.com/frappe/erpnext/pull/25053))
- Ignore Permission for Leave Ledger Entry ([#25172](https://github.com/frappe/erpnext/pull/25172))
- Pending shortfall update on processing loan security shortfall ([#24971](https://github.com/frappe/erpnext/pull/24971))
- Added flag for dont_fetch_price_list_rate in transaction ([#25041](https://github.com/frappe/erpnext/pull/25041))
- Exchange Rate not getting set in Salary Slip ([#25004](https://github.com/frappe/erpnext/pull/25004))
- Repost not completed backdated transactions ([#24980](https://github.com/frappe/erpnext/pull/24980))
- frappe.whitelist for doc methods ([#25230](https://github.com/frappe/erpnext/pull/25230))
- Opportunity-quotation mapping order status ([#25001](https://github.com/frappe/erpnext/pull/25001))
- GST on freight charge in e-invoicing ([#25000](https://github.com/frappe/erpnext/pull/25000))
- Role to override maintain same rate check in transactions ([#25193](https://github.com/frappe/erpnext/pull/25193))
- Added blank option for status in report related to issue ([#25082](https://github.com/frappe/erpnext/pull/25082))
- Cashier query in POS Opening/Closing Entry ([#25399](https://github.com/frappe/erpnext/pull/25399))
- Lead Source's module ([#24583](https://github.com/frappe/erpnext/pull/24583))
- Hide alt tag if item is not shown in website ([#24937](https://github.com/frappe/erpnext/pull/24937))
- Ignore Customer Group Perm on All Products page ([#25397](https://github.com/frappe/erpnext/pull/25397))
- Give first preference to loan security on repayment ([#25212](https://github.com/frappe/erpnext/pull/25212))
- Add shortfall ratio in Loan Security Shortfall ([#25138](https://github.com/frappe/erpnext/pull/25138))
- Condition for SLA status banner ([#25261](https://github.com/frappe/erpnext/pull/25261))
- Component amount calculation based on formula with abbr not working ([#25117](https://github.com/frappe/erpnext/pull/25117))
- Remove gst name validation for purchase Invoice ([#25235](https://github.com/frappe/erpnext/pull/25235))
- Do not fetch stopped MR in production plan ([#25063](https://github.com/frappe/erpnext/pull/25063))
- Backport missing commits to develop branch ([#25305](https://github.com/frappe/erpnext/pull/25305))
- UOM length unit in global setup list is empty ([#24855](https://github.com/frappe/erpnext/pull/24855))
- Round total quantity in job card ([#25240](https://github.com/frappe/erpnext/pull/25240))
- Default total_estimated_cost to zero ([#24939](https://github.com/frappe/erpnext/pull/24939))
- Serial no refresh issue ([#25127](https://github.com/frappe/erpnext/pull/25127))
- Correct calculation for discount amount when margin is set ([#25179](https://github.com/frappe/erpnext/pull/25179))
- Get correct holiday list when calculating dates; test fixes ([#24901](https://github.com/frappe/erpnext/pull/24901))
- POS print receipt ([#24924](https://github.com/frappe/erpnext/pull/24924))
- Condition for setting agreement status ([#25255](https://github.com/frappe/erpnext/pull/25255))
- Loan Repayment entry cancellation on salary slip cancel ([#24879](https://github.com/frappe/erpnext/pull/24879))
- Add company validation for e-invoicing ([#25349](https://github.com/frappe/erpnext/pull/25349))
- Query values incorrectly escaped while back updating Quality Inspection ([#25118](https://github.com/frappe/erpnext/pull/25118))
- Update Bin via Update Item on Purchase/Sales Order ([#23509](https://github.com/frappe/erpnext/pull/23509))
- Declare data before assigning ([#25287](https://github.com/frappe/erpnext/pull/25287))
- Do not set standard link in Sales Invoice as custom ([#25096](https://github.com/frappe/erpnext/pull/25096))
- Hide serial and batch selector in Stock Entry ([#25107](https://github.com/frappe/erpnext/pull/25107))
- Taxable value including Freight and Forwarding charges in GSTR-1 Report ([#25290](https://github.com/frappe/erpnext/pull/25290))
- Remove nonexistent method from pick list ([#25279](https://github.com/frappe/erpnext/pull/25279))
- Allow zero valuation in stock reconciliation ([#24888](https://github.com/frappe/erpnext/pull/24888))
- Place of supply of e-invoicing ([#25148](https://github.com/frappe/erpnext/pull/25148))
- Delivery note print error ([#25080](https://github.com/frappe/erpnext/pull/25080))
- Fix Payment references from disappearing on adding Cost Center in Payment Entry ([#24831](https://github.com/frappe/erpnext/pull/24831))
- Company field in Warehouse ([#25196](https://github.com/frappe/erpnext/pull/25196))
- Available employee for selection ([#25378](https://github.com/frappe/erpnext/pull/25378))
- Cannot set qty to less than zero ([#25258](https://github.com/frappe/erpnext/pull/25258))
- Don't delete mode of payment account details while deleting comp… ([#25217](https://github.com/frappe/erpnext/pull/25217))
- Exclude current doc while validation. ([#24914](https://github.com/frappe/erpnext/pull/24914))
- POS Opening Entry with empty balance detail rows ([#24876](https://github.com/frappe/erpnext/pull/24876))
- Unable to submit stock entry ([#25033](https://github.com/frappe/erpnext/pull/25033))
- BOM cost test case ([#25242](https://github.com/frappe/erpnext/pull/25242))
- Filter for employees in salary slip ([#25361](https://github.com/frappe/erpnext/pull/25361))
- Added correct path in hooks ([#24862](https://github.com/frappe/erpnext/pull/24862))
- Patch regional fields for old companies ([#24988](https://github.com/frappe/erpnext/pull/24988))
- consolidated sales invoice posting date ([#25119](https://github.com/frappe/erpnext/pull/25119))
- Don't set "Company:company:default_currency" as default for currency link fields ([#25095](https://github.com/frappe/erpnext/pull/25095))
- Healthcare lab module rename fields ([#25276](https://github.com/frappe/erpnext/pull/25276))
- Error message compensatory leave request ([#25206](https://github.com/frappe/erpnext/pull/25206))
- Adding company link to e invoice settings patch condition ([#25301](https://github.com/frappe/erpnext/pull/25301))
- Membership and Donation API fixes ([#24900](https://github.com/frappe/erpnext/pull/24900))
- Set correct ack no. on irn generation ([#25251](https://github.com/frappe/erpnext/pull/25251))
- Report Issue Summary fix for zero issues ([#24934](https://github.com/frappe/erpnext/pull/24934))
- Validation msg for TransDocNo e-invoicing ([#25121](https://github.com/frappe/erpnext/pull/25121))
- Correct state code for 'Other Territory' ([#24993](https://github.com/frappe/erpnext/pull/24993))
- Commit individual SLE rename for large datasets (develop) ([#25084](https://github.com/frappe/erpnext/pull/25084))
- Remove shipping address GSTIN validation for e-invoice ([#25153](https://github.com/frappe/erpnext/pull/25153))
- Period list for exponential smoothing forecasting report ([#24982](https://github.com/frappe/erpnext/pull/24982))
- Customer creation from shopping cart ([#25136](https://github.com/frappe/erpnext/pull/25136))
- Simplified logic for additional salary ([#24824](https://github.com/frappe/erpnext/pull/24824))
- Item wise tax rate for consolidated POS invoice ([#25029](https://github.com/frappe/erpnext/pull/25029))
- Column width in Recruitment analytics report ([#25003](https://github.com/frappe/erpnext/pull/25003))
- Filter Bank Account drop-down list in Bank Reconciliation Tool ([#24873](https://github.com/frappe/erpnext/pull/24873))
- Payroll issues ([#24540](https://github.com/frappe/erpnext/pull/24540))
- PO not created against all selected suppliers (drop shipping) ([#24863](https://github.com/frappe/erpnext/pull/24863))
- Can't multiply sequence by non-int of type 'float' ([#25092](https://github.com/frappe/erpnext/pull/25092))
- Make Discharge Schedule Date as Datetime ([#24940](https://github.com/frappe/erpnext/pull/24940))
- Serial no trim issue ([#24949](https://github.com/frappe/erpnext/pull/24949))

View File

@@ -717,7 +717,9 @@ class AccountsController(TransactionBase):
total_billed_amt = abs(total_billed_amt) total_billed_amt = abs(total_billed_amt)
max_allowed_amt = abs(max_allowed_amt) max_allowed_amt = abs(max_allowed_amt)
if total_billed_amt - max_allowed_amt > 0.01: role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
if total_billed_amt - max_allowed_amt > 0.01 and role_allowed_to_over_bill not in frappe.get_roles():
frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings") frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
.format(item.item_code, item.idx, max_allowed_amt)) .format(item.item_code, item.idx, max_allowed_amt))

View File

@@ -201,10 +201,14 @@ class StatusUpdater(Document):
get_allowance_for(item['item_code'], self.item_allowance, get_allowance_for(item['item_code'], self.item_allowance,
self.global_qty_allowance, self.global_amount_allowance, qty_or_amount) self.global_qty_allowance, self.global_amount_allowance, qty_or_amount)
overflow_percent = ((item[args['target_field']] - item[args['target_ref_field']]) / role_allowed_to_over_deliver_receive = frappe.db.get_single_value('Stock Settings', 'role_allowed_to_over_deliver_receive')
item[args['target_ref_field']]) * 100 role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
role = role_allowed_to_over_deliver_receive if qty_or_amount == 'qty' else role_allowed_to_over_bill
if overflow_percent - allowance > 0.01: overflow_percent = ((item[args['target_field']] - item[args['target_ref_field']]) /
item[args['target_ref_field']]) * 100
if overflow_percent - allowance > 0.01 and role not in frappe.get_roles():
item['max_allowed'] = flt(item[args['target_ref_field']] * (100+allowance)/100) item['max_allowed'] = flt(item[args['target_ref_field']] * (100+allowance)/100)
item['reduce_by'] = item[args['target_field']] - item['max_allowed'] item['reduce_by'] = item[args['target_field']] - item['max_allowed']

View File

@@ -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")
@@ -40,4 +42,4 @@ def setup_custom_fields():
fieldtype='Data', insert_after='title', read_only=1, print_hide=1)] fieldtype='Data', insert_after='title', read_only=1, print_hide=1)]
} }
create_custom_fields(custom_fields) create_custom_fields(custom_fields)

View File

@@ -1,206 +1,64 @@
{ {
"allow_copy": 0, "actions": [],
"allow_events_in_timeline": 0, "creation": "2018-07-12 12:07:36.932333",
"allow_guest_to_view": 0, "doctype": "DocType",
"allow_import": 0, "editable_grid": 1,
"allow_rename": 0, "engine": "InnoDB",
"beta": 0, "field_order": [
"creation": "2018-07-12 12:07:36.932333", "service_unit",
"custom": 0, "check_in",
"docstatus": 0, "left",
"doctype": "DocType", "check_out",
"document_type": "", "invoiced"
"editable_grid": 1, ],
"engine": "InnoDB",
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "service_unit",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Healthcare Service Unit",
"collapsible": 0, "options": "Healthcare Service Unit",
"columns": 0, "reqd": 1
"fieldname": "service_unit", },
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Healthcare Service Unit",
"length": 0,
"no_copy": 0,
"options": "Healthcare Service Unit",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "check_in",
"allow_in_quick_entry": 0, "fieldtype": "Datetime",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Check In"
"collapsible": 0, },
"columns": 0,
"fieldname": "check_in",
"fieldtype": "Datetime",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Check In",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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, "default": "0",
"allow_in_quick_entry": 0, "fieldname": "left",
"allow_on_submit": 0, "fieldtype": "Check",
"bold": 0, "label": "Left",
"collapsible": 0, "read_only": 1,
"columns": 0, "search_index": 1
"fieldname": "left", },
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Left",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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": 1,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "check_out",
"allow_in_quick_entry": 0, "fieldtype": "Datetime",
"allow_on_submit": 0, "label": "Check Out"
"bold": 0, },
"collapsible": 0,
"columns": 0,
"fieldname": "check_out",
"fieldtype": "Datetime",
"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": "Check Out",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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, "default": "0",
"allow_in_quick_entry": 0, "fieldname": "invoiced",
"allow_on_submit": 0, "fieldtype": "Check",
"bold": 0, "label": "Invoiced",
"collapsible": 0, "read_only": 1
"columns": 0,
"default": "0",
"fieldname": "invoiced",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Invoiced",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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
} }
], ],
"has_web_view": 0, "index_web_pages_for_search": 1,
"hide_heading": 0, "istable": 1,
"hide_toolbar": 0, "links": [],
"idx": 0, "modified": "2021-03-18 15:08:54.634132",
"image_view": 0, "modified_by": "Administrator",
"in_create": 0, "module": "Healthcare",
"is_submittable": 0, "name": "Inpatient Occupancy",
"issingle": 0, "owner": "Administrator",
"istable": 1, "permissions": [],
"max_attachments": 0, "quick_entry": 1,
"modified": "2018-11-04 03:33:26.958713", "restrict_to_domain": "Healthcare",
"modified_by": "Administrator", "sort_field": "modified",
"module": "Healthcare", "sort_order": "DESC",
"name": "Inpatient Occupancy", "track_changes": 1
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"restrict_to_domain": "Healthcare",
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
} }

View File

@@ -185,7 +185,7 @@
"fieldtype": "Datetime", "fieldtype": "Datetime",
"in_list_view": 1, "in_list_view": 1,
"label": "Admitted Datetime", "label": "Admitted Datetime",
"read_only": 1 "permlevel": 2
}, },
{ {
"depends_on": "eval:(doc.expected_length_of_stay > 0)", "depends_on": "eval:(doc.expected_length_of_stay > 0)",
@@ -312,7 +312,7 @@
"fieldname": "inpatient_occupancies", "fieldname": "inpatient_occupancies",
"fieldtype": "Table", "fieldtype": "Table",
"options": "Inpatient Occupancy", "options": "Inpatient Occupancy",
"read_only": 1 "permlevel": 2
}, },
{ {
"fieldname": "btn_transfer", "fieldname": "btn_transfer",
@@ -407,12 +407,12 @@
"fieldname": "discharge_datetime", "fieldname": "discharge_datetime",
"fieldtype": "Datetime", "fieldtype": "Datetime",
"label": "Discharge Date", "label": "Discharge Date",
"read_only": 1 "permlevel": 2
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2021-03-18 14:44:11.689956", "modified": "2021-03-18 15:59:17.318988",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Inpatient Record", "name": "Inpatient Record",
@@ -465,6 +465,37 @@
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Nursing User" "role": "Nursing User"
},
{
"email": 1,
"export": 1,
"permlevel": 2,
"print": 1,
"read": 1,
"report": 1,
"role": "Healthcare Administrator",
"share": 1,
"write": 1
},
{
"email": 1,
"export": 1,
"permlevel": 2,
"print": 1,
"read": 1,
"report": 1,
"role": "Physician",
"share": 1
},
{
"email": 1,
"export": 1,
"permlevel": 2,
"print": 1,
"read": 1,
"report": 1,
"role": "Nursing User",
"share": 1
} }
], ],
"restrict_to_domain": "Healthcare", "restrict_to_domain": "Healthcare",

View File

@@ -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,])

View File

@@ -66,7 +66,7 @@ class CompensatoryLeaveRequest(Document):
else: else:
leave_allocation = self.create_leave_allocation(leave_period, date_difference) leave_allocation = self.create_leave_allocation(leave_period, date_difference)
self.leave_allocation=leave_allocation.name self.db_set("leave_allocation", leave_allocation.name)
else: else:
frappe.throw(_("There is no leave period in between {0} and {1}").format(format_date(self.work_from_date), format_date(self.work_end_date))) frappe.throw(_("There is no leave period in between {0} and {1}").format(format_date(self.work_from_date), format_date(self.work_end_date)))
@@ -124,4 +124,4 @@ class CompensatoryLeaveRequest(Document):
)) ))
allocation.insert(ignore_permissions=True) allocation.insert(ignore_permissions=True)
allocation.submit() allocation.submit()
return allocation return allocation

View File

@@ -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', [])

View File

@@ -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,19 @@
"fieldname": "send_leave_notification", "fieldname": "send_leave_notification",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Send Leave Notification" "label": "Send Leave Notification"
},
{
"default": "8",
"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-16 15:45:18.467699",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "HR Settings", "name": "HR Settings",

View File

@@ -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 = []

View File

@@ -360,13 +360,14 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-04-10 09:28:21.946972", "modified": "2021-04-19 18:10:32.360818",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Loan Management", "module": "Loan Management",
"name": "Loan", "name": "Loan",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 1,
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,

View File

@@ -212,15 +212,17 @@
"read_only": 1 "read_only": 1
} }
], ],
"index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-03-01 10:21:44.413353", "modified": "2021-04-19 18:24:40.119647",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Loan Management", "module": "Loan Management",
"name": "Loan Application", "name": "Loan Application",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 1,
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
@@ -235,6 +237,7 @@
"write": 1 "write": 1
}, },
{ {
"amend": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,

View File

@@ -154,13 +154,14 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-04-10 10:03:41.502210", "modified": "2021-04-19 18:09:32.175355",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Loan Management", "module": "Loan Management",
"name": "Loan Disbursement", "name": "Loan Disbursement",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 1,
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
@@ -175,6 +176,7 @@
"write": 1 "write": 1
}, },
{ {
"amend": 1,
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,

View File

@@ -185,13 +185,14 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-01-10 00:15:21.544140", "modified": "2021-04-19 18:26:38.871889",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Loan Management", "module": "Loan Management",
"name": "Loan Interest Accrual", "name": "Loan Interest Accrual",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 1,
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
@@ -206,6 +207,7 @@
"write": 1 "write": 1
}, },
{ {
"amend": 1,
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,

View File

@@ -248,13 +248,14 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-04-10 10:00:31.859076", "modified": "2021-04-19 18:10:00.935364",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Loan Management", "module": "Loan Management",
"name": "Loan Repayment", "name": "Loan Repayment",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 1,
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
@@ -269,6 +270,7 @@
"write": 1 "write": 1
}, },
{ {
"amend": 1,
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,

View File

@@ -160,13 +160,14 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-09-04 22:38:19.894488", "modified": "2021-04-19 18:23:16.953305",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Loan Management", "module": "Loan Management",
"name": "Loan Security Pledge", "name": "Loan Security Pledge",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 1,
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
@@ -181,6 +182,7 @@
"write": 1 "write": 1
}, },
{ {
"amend": 1,
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,

View File

@@ -126,13 +126,14 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-09-04 22:39:57.756146", "modified": "2021-04-19 18:12:01.401744",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Loan Management", "module": "Loan Management",
"name": "Loan Security Unpledge", "name": "Loan Security Unpledge",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 1,
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
@@ -147,6 +148,7 @@
"write": 1 "write": 1
}, },
{ {
"amend": 1,
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,

View File

@@ -154,13 +154,14 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-01-17 06:51:26.082879", "modified": "2021-04-19 18:10:57.368490",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Loan Management", "module": "Loan Management",
"name": "Loan Type", "name": "Loan Type",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 1,
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,

View File

@@ -116,13 +116,14 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-10-26 07:13:43.663924", "modified": "2021-04-19 18:11:27.759862",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Loan Management", "module": "Loan Management",
"name": "Loan Write Off", "name": "Loan Write Off",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 1,
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
@@ -137,6 +138,7 @@
"write": 1 "write": 1
}, },
{ {
"amend": 1,
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,

View File

@@ -38,16 +38,37 @@ def execute():
""".format(doctype), {'parentfield': parentfield}) """.format(doctype), {'parentfield': parentfield})
# copy renamed child table fields (fields were already renamed in old doctype json, hence sql) # copy renamed child table fields (fields were already renamed in old doctype json, hence sql)
frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_name = test_name""") rename_fields = {
frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_event = test_event""") 'lab_test_name': 'test_name',
frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_uom = test_uom""") 'lab_test_event': 'test_event',
frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_comment = test_comment""") 'lab_test_uom': 'test_uom',
frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_event = test_event""") 'lab_test_comment': 'test_comment'
frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_uom = test_uom""") }
frappe.db.sql("""UPDATE `tabDescriptive Test Result` SET lab_test_particulars = test_particulars""")
frappe.db.sql("""UPDATE `tabLab Test Group Template` SET lab_test_template = test_template""") for new, old in rename_fields.items():
frappe.db.sql("""UPDATE `tabLab Test Group Template` SET lab_test_description = test_description""") if frappe.db.has_column('Normal Test Result', old):
frappe.db.sql("""UPDATE `tabLab Test Group Template` SET lab_test_rate = test_rate""") frappe.db.sql("""UPDATE `tabNormal Test Result` SET {} = {}"""
.format(new, old))
if frappe.db.has_column('Normal Test Template', 'test_event'):
frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_event = test_event""")
if frappe.db.has_column('Normal Test Template', 'test_uom'):
frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_uom = test_uom""")
if frappe.db.has_column('Descriptive Test Result', 'test_particulars'):
frappe.db.sql("""UPDATE `tabDescriptive Test Result` SET lab_test_particulars = test_particulars""")
rename_fields = {
'lab_test_template': 'test_template',
'lab_test_description': 'test_description',
'lab_test_rate': 'test_rate'
}
for new, old in rename_fields.items():
if frappe.db.has_column('Lab Test Group Template', old):
frappe.db.sql("""UPDATE `tabLab Test Group Template` SET {} = {}"""
.format(new, old))
# rename field # rename field
frappe.reload_doc('healthcare', 'doctype', 'lab_test') frappe.reload_doc('healthcare', 'doctype', 'lab_test')

View File

@@ -151,6 +151,10 @@ frappe.ui.form.on('Payroll Entry', {
filters['company'] = frm.doc.company; filters['company'] = frm.doc.company;
filters['start_date'] = frm.doc.start_date; filters['start_date'] = frm.doc.start_date;
filters['end_date'] = frm.doc.end_date; filters['end_date'] = frm.doc.end_date;
filters['salary_slip_based_on_timesheet'] = frm.doc.salary_slip_based_on_timesheet;
filters['payroll_frequency'] = frm.doc.payroll_frequency;
filters['payroll_payable_account'] = frm.doc.payroll_payable_account;
filters['currency'] = frm.doc.currency;
if (frm.doc.department) { if (frm.doc.department) {
filters['department'] = frm.doc.department; filters['department'] = frm.doc.department;

View File

@@ -52,49 +52,32 @@ class PayrollEntry(Document):
Returns list of active employees based on selected criteria Returns list of active employees based on selected criteria
and for which salary structure exists and for which salary structure exists
""" """
cond = self.get_filter_condition() self.check_mandatory()
cond += self.get_joining_relieving_condition() filters = self.make_filters()
cond = get_filter_condition(filters)
cond += get_joining_relieving_condition(self.start_date, self.end_date)
condition = '' condition = ''
if self.payroll_frequency: if self.payroll_frequency:
condition = """and payroll_frequency = '%(payroll_frequency)s'"""% {"payroll_frequency": self.payroll_frequency} condition = """and payroll_frequency = '%(payroll_frequency)s'"""% {"payroll_frequency": self.payroll_frequency}
sal_struct = frappe.db.sql_list(""" sal_struct = get_sal_struct(self.company, self.currency, self.salary_slip_based_on_timesheet, condition)
select
name from `tabSalary Structure`
where
docstatus = 1 and
is_active = 'Yes'
and company = %(company)s
and currency = %(currency)s and
ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s
{condition}""".format(condition=condition),
{"company": self.company, "currency": self.currency, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet})
if sal_struct: if sal_struct:
cond += "and t2.salary_structure IN %(sal_struct)s " cond += "and t2.salary_structure IN %(sal_struct)s "
cond += "and t2.payroll_payable_account = %(payroll_payable_account)s " cond += "and t2.payroll_payable_account = %(payroll_payable_account)s "
cond += "and %(from_date)s >= t2.from_date" cond += "and %(from_date)s >= t2.from_date"
emp_list = frappe.db.sql(""" emp_list = get_emp_list(sal_struct, cond, self.end_date, self.payroll_payable_account)
select emp_list = remove_payrolled_employees(emp_list, self.start_date, self.end_date)
distinct t1.name as employee, t1.employee_name, t1.department, t1.designation
from
`tabEmployee` t1, `tabSalary Structure Assignment` t2
where
t1.name = t2.employee
and t2.docstatus = 1
%s order by t2.from_date desc
""" % cond, {"sal_struct": tuple(sal_struct), "from_date": self.end_date, "payroll_payable_account": self.payroll_payable_account}, as_dict=True)
emp_list = self.remove_payrolled_employees(emp_list)
return emp_list return emp_list
def remove_payrolled_employees(self, emp_list): def make_filters(self):
for employee_details in emp_list: filters = frappe._dict()
if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": self.start_date, "end_date": self.end_date, "docstatus": 1}): filters['company'] = self.company
emp_list.remove(employee_details) filters['branch'] = self.branch
filters['department'] = self.department
filters['designation'] = self.designation
return emp_list return filters
@frappe.whitelist() @frappe.whitelist()
def fill_employee_details(self): def fill_employee_details(self):
@@ -122,23 +105,6 @@ class PayrollEntry(Document):
if self.validate_attendance: if self.validate_attendance:
return self.validate_employee_attendance() return self.validate_employee_attendance()
def get_filter_condition(self):
self.check_mandatory()
cond = ''
for f in ['company', 'branch', 'department', 'designation']:
if self.get(f):
cond += " and t1." + f + " = " + frappe.db.escape(self.get(f))
return cond
def get_joining_relieving_condition(self):
cond = """
and ifnull(t1.date_of_joining, '0000-00-00') <= '%(end_date)s'
and ifnull(t1.relieving_date, '2199-12-31') >= '%(start_date)s'
""" % {"start_date": self.start_date, "end_date": self.end_date}
return cond
def check_mandatory(self): def check_mandatory(self):
for fieldname in ['company', 'start_date', 'end_date']: for fieldname in ['company', 'start_date', 'end_date']:
if not self.get(fieldname): if not self.get(fieldname):
@@ -451,6 +417,53 @@ class PayrollEntry(Document):
marked_days = attendances[0][0] marked_days = attendances[0][0]
return marked_days return marked_days
def get_sal_struct(company, currency, salary_slip_based_on_timesheet, condition):
return frappe.db.sql_list("""
select
name from `tabSalary Structure`
where
docstatus = 1 and
is_active = 'Yes'
and company = %(company)s
and currency = %(currency)s and
ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s
{condition}""".format(condition=condition),
{"company": company, "currency": currency, "salary_slip_based_on_timesheet": salary_slip_based_on_timesheet})
def get_filter_condition(filters):
cond = ''
for f in ['company', 'branch', 'department', 'designation']:
if filters.get(f):
cond += " and t1." + f + " = " + frappe.db.escape(filters.get(f))
return cond
def get_joining_relieving_condition(start_date, end_date):
cond = """
and ifnull(t1.date_of_joining, '0000-00-00') <= '%(end_date)s'
and ifnull(t1.relieving_date, '2199-12-31') >= '%(start_date)s'
""" % {"start_date": start_date, "end_date": end_date}
return cond
def get_emp_list(sal_struct, cond, end_date, payroll_payable_account):
return frappe.db.sql("""
select
distinct t1.name as employee, t1.employee_name, t1.department, t1.designation
from
`tabEmployee` t1, `tabSalary Structure Assignment` t2
where
t1.name = t2.employee
and t2.docstatus = 1
%s order by t2.from_date desc
""" % cond, {"sal_struct": tuple(sal_struct), "from_date": end_date, "payroll_payable_account": payroll_payable_account}, as_dict=True)
def remove_payrolled_employees(emp_list, start_date, end_date):
for employee_details in emp_list:
if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": start_date, "end_date": end_date, "docstatus": 1}):
emp_list.remove(employee_details)
return emp_list
@frappe.whitelist() @frappe.whitelist()
def get_start_end_dates(payroll_frequency, start_date=None, company=None): def get_start_end_dates(payroll_frequency, start_date=None, company=None):
'''Returns dict of start and end dates for given payroll frequency based on start_date''' '''Returns dict of start and end dates for given payroll frequency based on start_date'''
@@ -639,39 +652,41 @@ def get_payroll_entries_for_jv(doctype, txt, searchfield, start, page_len, filte
'start': start, 'page_len': page_len 'start': start, 'page_len': page_len
}) })
def get_employee_with_existing_salary_slip(start_date, end_date, company): def get_employee_list(filters):
return frappe.db.sql_list(""" cond = get_filter_condition(filters)
select employee from `tabSalary Slip` cond += get_joining_relieving_condition(filters.start_date, filters.end_date)
where condition = """and payroll_frequency = '%(payroll_frequency)s'"""% {"payroll_frequency": filters.payroll_frequency}
(start_date between %(start_date)s and %(end_date)s sal_struct = get_sal_struct(filters.company, filters.currency, filters.salary_slip_based_on_timesheet, condition)
or if sal_struct:
end_date between %(start_date)s and %(end_date)s cond += "and t2.salary_structure IN %(sal_struct)s "
or cond += "and t2.payroll_payable_account = %(payroll_payable_account)s "
%(start_date)s between start_date and end_date) cond += "and %(from_date)s >= t2.from_date"
and company = %(company)s emp_list = get_emp_list(sal_struct, cond, filters.end_date, filters.payroll_payable_account)
and docstatus = 1 emp_list = remove_payrolled_employees(emp_list, filters.start_date, filters.end_date)
""", {'start_date': start_date, 'end_date': end_date, 'company': company}) return emp_list
@frappe.whitelist() @frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs @frappe.validate_and_sanitize_search_inputs
def employee_query(doctype, txt, searchfield, start, page_len, filters): def employee_query(doctype, txt, searchfield, start, page_len, filters):
filters = frappe._dict(filters) filters = frappe._dict(filters)
conditions = [] conditions = []
exclude_employees = [] include_employees = []
emp_cond = '' emp_cond = ''
if filters.start_date and filters.end_date: if filters.start_date and filters.end_date:
employee_list = get_employee_with_existing_salary_slip(filters.start_date, filters.end_date, filters.company) employee_list = get_employee_list(filters)
emp = filters.get('employees') emp = filters.get('employees')
include_employees = [employee.employee for employee in employee_list if employee.employee not in emp]
filters.pop('start_date') filters.pop('start_date')
filters.pop('end_date') filters.pop('end_date')
filters.pop('salary_slip_based_on_timesheet')
filters.pop('payroll_frequency')
filters.pop('payroll_payable_account')
filters.pop('currency')
if filters.employees is not None: if filters.employees is not None:
filters.pop('employees') filters.pop('employees')
if employee_list:
exclude_employees.extend(employee_list) if include_employees:
if emp: emp_cond += 'and employee in %(include_employees)s'
exclude_employees.extend(emp)
if exclude_employees:
emp_cond += 'and employee not in %(exclude_employees)s'
return frappe.db.sql("""select name, employee_name from `tabEmployee` return frappe.db.sql("""select name, employee_name from `tabEmployee`
where status = 'Active' where status = 'Active'
@@ -695,4 +710,4 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters):
'_txt': txt.replace("%", ""), '_txt': txt.replace("%", ""),
'start': start, 'start': start,
'page_len': page_len, 'page_len': page_len,
'exclude_employees': exclude_employees}) 'include_employees': include_employees})

View File

@@ -133,8 +133,6 @@ frappe.ui.form.on('Salary Structure', {
title: __("Assign to Employees"), title: __("Assign to Employees"),
fields: [ fields: [
{fieldname: "sec_break", fieldtype: "Section Break", label: __("Filter Employees By (Optional)")}, {fieldname: "sec_break", fieldtype: "Section Break", label: __("Filter Employees By (Optional)")},
{fieldname: "company", fieldtype: "Link", options: "Company", label: __("Company"), default: frm.doc.company, read_only:1},
{fieldname: "currency", fieldtype: "Link", options: "Currency", label: __("Currency"), default: frm.doc.currency, read_only:1},
{fieldname: "grade", fieldtype: "Link", options: "Employee Grade", label: __("Employee Grade")}, {fieldname: "grade", fieldtype: "Link", options: "Employee Grade", label: __("Employee Grade")},
{fieldname:'department', fieldtype:'Link', options: 'Department', label: __('Department')}, {fieldname:'department', fieldtype:'Link', options: 'Department', label: __('Department')},
{fieldname:'designation', fieldtype:'Link', options: 'Designation', label: __('Designation')}, {fieldname:'designation', fieldtype:'Link', options: 'Designation', label: __('Designation')},

View File

@@ -88,7 +88,7 @@ class SalaryStructure(Document):
return employees return employees
@frappe.whitelist() @frappe.whitelist()
def assign_salary_structure(self, grade=None, department=None, designation=None,employee=None, def assign_salary_structure(self, grade=None, department=None, designation=None, employee=None,
payroll_payable_account=None, from_date=None, base=None, variable=None, income_tax_slab=None): payroll_payable_account=None, from_date=None, base=None, variable=None, income_tax_slab=None):
employees = self.get_employees(company= self.company, grade= grade,department= department,designation= designation,name=employee) employees = self.get_employees(company= self.company, grade= grade,department= department,designation= designation,name=employee)

View File

@@ -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 {

View File

@@ -11,15 +11,16 @@
"project", "project",
"issue", "issue",
"type", "type",
"color",
"is_group", "is_group",
"is_template", "is_template",
"column_break0", "column_break0",
"status", "status",
"priority", "priority",
"task_weight", "task_weight",
"completed_by",
"color",
"parent_task", "parent_task",
"completed_by",
"completed_on",
"sb_timeline", "sb_timeline",
"exp_start_date", "exp_start_date",
"expected_time", "expected_time",
@@ -358,6 +359,7 @@
"read_only": 1 "read_only": 1
}, },
{ {
"depends_on": "eval: doc.status == \"Completed\"",
"fieldname": "completed_by", "fieldname": "completed_by",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Completed By", "label": "Completed By",
@@ -381,6 +383,13 @@
"fieldname": "duration", "fieldname": "duration",
"fieldtype": "Int", "fieldtype": "Int",
"label": "Duration (Days)" "label": "Duration (Days)"
},
{
"depends_on": "eval: doc.status == \"Completed\"",
"fieldname": "completed_on",
"fieldtype": "Date",
"label": "Completed On",
"mandatory_depends_on": "eval: doc.status == \"Completed\""
} }
], ],
"icon": "fa fa-check", "icon": "fa fa-check",
@@ -388,7 +397,7 @@
"is_tree": 1, "is_tree": 1,
"links": [], "links": [],
"max_attachments": 5, "max_attachments": 5,
"modified": "2020-12-28 11:32:58.714991", "modified": "2021-04-16 12:46:51.556741",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Projects", "module": "Projects",
"name": "Task", "name": "Task",

View File

@@ -36,6 +36,7 @@ class Task(NestedSet):
self.validate_status() self.validate_status()
self.update_depends_on() self.update_depends_on()
self.validate_dependencies_for_template_task() self.validate_dependencies_for_template_task()
self.validate_completed_on()
def validate_dates(self): def validate_dates(self):
if self.exp_start_date and self.exp_end_date and getdate(self.exp_start_date) > getdate(self.exp_end_date): if self.exp_start_date and self.exp_end_date and getdate(self.exp_start_date) > getdate(self.exp_end_date):
@@ -100,6 +101,10 @@ class Task(NestedSet):
dependent_task_format = """<a href="#Form/Task/{0}">{0}</a>""".format(task.task) dependent_task_format = """<a href="#Form/Task/{0}">{0}</a>""".format(task.task)
frappe.throw(_("Dependent Task {0} is not a Template Task").format(dependent_task_format)) frappe.throw(_("Dependent Task {0} is not a Template Task").format(dependent_task_format))
def validate_completed_on(self):
if self.completed_on and getdate(self.completed_on) > getdate():
frappe.throw(_("Completed On cannot be greater than Today"))
def update_depends_on(self): def update_depends_on(self):
depends_on_tasks = self.depends_on_tasks or "" depends_on_tasks = self.depends_on_tasks or ""
for d in self.depends_on: for d in self.depends_on:

View File

@@ -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

View File

@@ -0,0 +1,41 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Delayed Tasks Summary"] = {
"filters": [
{
"fieldname": "from_date",
"label": __("From Date"),
"fieldtype": "Date"
},
{
"fieldname": "to_date",
"label": __("To Date"),
"fieldtype": "Date"
},
{
"fieldname": "priority",
"label": __("Priority"),
"fieldtype": "Select",
"options": ["", "Low", "Medium", "High", "Urgent"]
},
{
"fieldname": "status",
"label": __("Status"),
"fieldtype": "Select",
"options": ["", "Open", "Working","Pending Review","Overdue","Completed"]
},
],
"formatter": function(value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
if (column.id == "delay") {
if (data["delay"] > 0) {
value = `<p style="color: red; font-weight: bold">${value}</p>`;
} else {
value = `<p style="color: green; font-weight: bold">${value}</p>`;
}
}
return value
}
};

View File

@@ -0,0 +1,29 @@
{
"add_total_row": 0,
"columns": [],
"creation": "2021-03-25 15:03:19.857418",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 0,
"is_standard": "Yes",
"modified": "2021-04-15 15:49:35.432486",
"modified_by": "Administrator",
"module": "Projects",
"name": "Delayed Tasks Summary",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Task",
"report_name": "Delayed Tasks Summary",
"report_type": "Script Report",
"roles": [
{
"role": "Projects User"
},
{
"role": "Projects Manager"
}
]
}

View File

@@ -0,0 +1,133 @@
# 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.utils import date_diff, nowdate
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):
conditions = get_conditions(filters)
tasks = frappe.get_all("Task",
filters = conditions,
fields = ["name", "subject", "exp_start_date", "exp_end_date",
"status", "priority", "completed_on", "progress"],
order_by="creation"
)
for task in tasks:
if task.exp_end_date:
if task.completed_on:
task.delay = date_diff(task.completed_on, task.exp_end_date)
elif task.status == "Completed":
# task is completed but completed on is not set (for older tasks)
task.delay = 0
else:
# task not completed
task.delay = date_diff(nowdate(), task.exp_end_date)
else:
# task has no end date, hence no delay
task.delay = 0
# Sort by descending order of delay
tasks.sort(key=lambda x: x["delay"], reverse=True)
return tasks
def get_conditions(filters):
conditions = frappe._dict()
keys = ["priority", "status"]
for key in keys:
if filters.get(key):
conditions[key] = filters.get(key)
if filters.get("from_date"):
conditions.exp_end_date = [">=", filters.get("from_date")]
if filters.get("to_date"):
conditions.exp_start_date = ["<=", filters.get("to_date")]
return conditions
def get_chart_data(data):
delay, on_track = 0, 0
for entry in data:
if entry.get("delay") > 0:
delay = delay + 1
else:
on_track = on_track + 1
charts = {
"data": {
"labels": ["On Track", "Delayed"],
"datasets": [
{
"name": "Delayed",
"values": [on_track, delay]
}
]
},
"type": "percentage",
"colors": ["#84D5BA", "#CB4B5F"]
}
return charts
def get_columns():
columns = [
{
"fieldname": "name",
"fieldtype": "Link",
"label": "Task",
"options": "Task",
"width": 150
},
{
"fieldname": "subject",
"fieldtype": "Data",
"label": "Subject",
"width": 200
},
{
"fieldname": "status",
"fieldtype": "Data",
"label": "Status",
"width": 100
},
{
"fieldname": "priority",
"fieldtype": "Data",
"label": "Priority",
"width": 80
},
{
"fieldname": "progress",
"fieldtype": "Data",
"label": "Progress (%)",
"width": 120
},
{
"fieldname": "exp_start_date",
"fieldtype": "Date",
"label": "Expected Start Date",
"width": 150
},
{
"fieldname": "exp_end_date",
"fieldtype": "Date",
"label": "Expected End Date",
"width": 150
},
{
"fieldname": "completed_on",
"fieldtype": "Date",
"label": "Actual End Date",
"width": 130
},
{
"fieldname": "delay",
"fieldtype": "Data",
"label": "Delay (In Days)",
"width": 120
}
]
return columns

View File

@@ -0,0 +1,54 @@
from __future__ import unicode_literals
import unittest
import frappe
from frappe.utils import nowdate, add_days, add_months
from erpnext.projects.doctype.task.test_task import create_task
from erpnext.projects.report.delayed_tasks_summary.delayed_tasks_summary import execute
class TestDelayedTasksSummary(unittest.TestCase):
@classmethod
def setUp(self):
task1 = create_task("_Test Task 98", add_days(nowdate(), -10), nowdate())
create_task("_Test Task 99", add_days(nowdate(), -10), add_days(nowdate(), -1))
task1.status = "Completed"
task1.completed_on = add_days(nowdate(), -1)
task1.save()
def test_delayed_tasks_summary(self):
filters = frappe._dict({
"from_date": add_months(nowdate(), -1),
"to_date": nowdate(),
"priority": "Low",
"status": "Open"
})
expected_data = [
{
"subject": "_Test Task 99",
"status": "Open",
"priority": "Low",
"delay": 1
},
{
"subject": "_Test Task 98",
"status": "Completed",
"priority": "Low",
"delay": -1
}
]
report = execute(filters)
data = list(filter(lambda x: x.subject == "_Test Task 99", report[1]))[0]
for key in ["subject", "status", "priority", "delay"]:
self.assertEqual(expected_data[0].get(key), data.get(key))
filters.status = "Completed"
report = execute(filters)
data = list(filter(lambda x: x.subject == "_Test Task 98", report[1]))[0]
for key in ["subject", "status", "priority", "delay"]:
self.assertEqual(expected_data[1].get(key), data.get(key))
def tearDown(self):
for task in ["_Test Task 98", "_Test Task 99"]:
frappe.get_doc("Task", {"subject": task}).delete()

View File

@@ -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"
}
]
};

View File

@@ -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"
}
]
}

View File

@@ -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
}
]

View File

@@ -0,0 +1,56 @@
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()
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()

View File

@@ -15,6 +15,7 @@
"hide_custom": 0, "hide_custom": 0,
"icon": "project", "icon": "project",
"idx": 0, "idx": 0,
"is_default": 0,
"is_standard": 1, "is_standard": 1,
"label": "Projects", "label": "Projects",
"links": [ "links": [
@@ -129,6 +130,16 @@
"onboard": 1, "onboard": 1,
"type": "Link" "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,
@@ -148,9 +159,19 @@
"link_type": "Report", "link_type": "Report",
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
},
{
"dependencies": "Task",
"hidden": 0,
"is_query_report": 1,
"label": "Delayed Tasks Summary",
"link_to": "Delayed Tasks Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
} }
], ],
"modified": "2020-12-01 13:38:37.856224", "modified": "2021-04-16 16:27:16.548780",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Projects", "module": "Projects",
"name": "Projects", "name": "Projects",

View File

@@ -1103,6 +1103,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
to_currency: to_currency, to_currency: to_currency,
args: args args: args
}, },
freeze: true,
freeze_message: __("Fetching exchange rates ..."),
callback: function(r) { callback: function(r) {
callback(flt(r.message)); callback(flt(r.message));
} }

View File

@@ -712,7 +712,7 @@ erpnext.utils.map_current_doc = function(opts) {
} }
frappe.form.link_formatters['Item'] = function(value, doc) { frappe.form.link_formatters['Item'] = function(value, doc) {
if (doc && value && doc.item_name && doc.item_name !== value) { if (doc && value && doc.item_name && doc.item_name !== value && doc.item_code === value) {
return value + ': ' + doc.item_name; return value + ': ' + doc.item_name;
} else if (!value && doc.doctype && doc.item_name) { } else if (!value && doc.doctype && doc.item_name) {
// format blank value in child table // format blank value in child table

View File

@@ -124,6 +124,7 @@ 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.status = "Processing File Data"
self.save() self.save()
@@ -400,4 +401,4 @@ def get_full_path(file_name):
elif not self.file_url: elif not self.file_url:
frappe.throw(_("There is some problem with the file url: {0}").format(file_path)) frappe.throw(_("There is some problem with the file url: {0}").format(file_path))
return file_path return file_path

View File

@@ -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:

View File

@@ -159,6 +159,31 @@ erpnext.PointOfSale.ItemSelector = class {
bind_events() { bind_events() {
const me = this; const me = this;
window.onScan = onScan; window.onScan = onScan;
onScan.decodeKeyEvent = function (oEvent) {
var iCode = this._getNormalizedKeyNum(oEvent);
switch (true) {
case iCode >= 48 && iCode <= 90: // numbers and letters
case iCode >= 106 && iCode <= 111: // operations on numeric keypad (+, -, etc.)
case (iCode >= 160 && iCode <= 164) || iCode == 170: // ^ ! # $ *
case iCode >= 186 && iCode <= 194: // (; = , - . / `)
case iCode >= 219 && iCode <= 222: // ([ \ ] ')
if (oEvent.key !== undefined && oEvent.key !== '') {
return oEvent.key;
}
var sDecoded = String.fromCharCode(iCode);
switch (oEvent.shiftKey) {
case false: sDecoded = sDecoded.toLowerCase(); break;
case true: sDecoded = sDecoded.toUpperCase(); break;
}
return sDecoded;
case iCode >= 96 && iCode <= 105: // numbers on numeric keypad
return 0 + (iCode - 96);
}
return '';
};
onScan.attachTo(document, { onScan.attachTo(document, {
onScan: (sScancode) => { onScan: (sScancode) => {
if (this.search_field && this.$component.is(':visible')) { if (this.search_field && this.$component.is(':visible')) {

View File

@@ -105,7 +105,7 @@ erpnext.PointOfSale.PastOrderList = class {
<svg class="mr-2" width="12" height="12" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"> <svg class="mr-2" width="12" height="12" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/> <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>
</svg> </svg>
${invoice.customer} ${frappe.ellipsis(invoice.customer, 20)}
</div> </div>
</div> </div>
<div class="invoice-total-status"> <div class="invoice-total-status">

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,5 @@
{ {
"absolute_value": 0,
"align_labels_right": 0, "align_labels_right": 0,
"creation": "2011-12-21 11:08:55", "creation": "2011-12-21 11:08:55",
"custom_format": 1, "custom_format": 1,
@@ -6,10 +7,10 @@
"doc_type": "POS Invoice", "doc_type": "POS Invoice",
"docstatus": 0, "docstatus": 0,
"doctype": "Print Format", "doctype": "Print Format",
"html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Tahoma, sans-serif;\n\t\tline-height: 150%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n\n<p class=\"text-center\" style=\"margin-bottom: 1rem\">\n\t{{ doc.company }}<br>\n\t{{ doc.select_print_heading or _(\"Invoice\") }}<br>\n</p>\n<p>\n\t<b>{{ _(\"Receipt No\") }}:</b> {{ doc.name }}<br>\n\t<b>{{ _(\"Date\") }}:</b> {{ doc.get_formatted(\"posting_date\") }}<br>\n\t<b>{{ _(\"Customer\") }}:</b> {{ doc.customer_name }}\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ _(\"Item\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{%- for item in doc.items -%}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t<br>{{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t<br><b>{{ _(\"SR.No\") }}:</b><br>\n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ item.qty }}<br>@ {{ item.get_formatted(\"rate\") }}</td>\n\t\t\t<td class=\"text-right\">{{ item.get_formatted(\"amount\") }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% else %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% endif %}\n\t\t</tr>\n\t\t{%- for row in doc.taxes -%}\n\t\t {%- if not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t {% if '%' in row.description %}\n\t\t\t\t\t {{ row.description }}\n\t\t\t\t\t{% else %}\n\t\t\t\t\t {{ row.description }}@{{ row.rate }}%\n\t\t\t\t\t{% endif %}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t {%- endif -%}\n\t\t{%- endfor -%}\n\n\t\t{%- if doc.discount_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.rounded_total -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Rounded Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.change_amount -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t\t<b>{{ _(\"Change Amount\") }}</b>\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t\t</td>\n\t\t\t</tr>\n\t\t{%- endif -%}\n\t</tbody>\n</table>\n<hr>\n<p>{{ doc.terms or \"\" }}</p>\n<p class=\"text-center\">{{ _(\"Thank you, please visit again.\") }}</p>", "html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tline-height: 150%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n\n<p class=\"text-center\" style=\"margin-bottom: 1rem\">\n\t{{ doc.company }}<br>\n\t<b>{{ doc.select_print_heading or _(\"Invoice\") }}</b><br>\n</p>\n<p>\n\t<b>{{ _(\"Receipt No\") }}:</b> {{ doc.name }}<br>\n\t<b>{{ _(\"Cashier\") }}:</b> {{ doc.owner }}<br>\n\t<b>{{ _(\"Customer\") }}:</b> {{ doc.customer_name }}<br>\n\t<b>{{ _(\"Date\") }}:</b> {{ doc.get_formatted(\"posting_date\") }}<br>\n\t<b>{{ _(\"Time\") }}:</b> {{ doc.get_formatted(\"posting_time\") }}<br>\n</p>\n\n<hr>\n<table class=\"table table-condensed\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ _(\"Item\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{%- for item in doc.items -%}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t<br>{{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t<br><b>{{ _(\"SR.No\") }}:</b><br>\n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ item.qty }}<br>@ {{ item.get_formatted(\"rate\") }}</td>\n\t\t\t<td class=\"text-right\">{{ item.get_formatted(\"amount\") }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% else %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% endif %}\n\t\t</tr>\n\t\t{%- for row in doc.taxes -%}\n\t\t {%- if not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t {% if '%' in row.description %}\n\t\t\t\t\t {{ row.description }}\n\t\t\t\t\t{% else %}\n\t\t\t\t\t {{ row.description }}@{{ row.rate }}%\n\t\t\t\t\t{% endif %}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t {%- endif -%}\n\t\t{%- endfor -%}\n\n\t\t{%- if doc.discount_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.rounded_total -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Rounded Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t{%- for row in doc.payments -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t {{ row.mode_of_payment }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"amount\", doc) }}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t{%- endfor -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.change_amount -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t\t<b>{{ _(\"Change Amount\") }}</b>\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t\t</td>\n\t\t\t</tr>\n\t\t{%- endif -%}\n\t</tbody>\n</table>\n<hr>\n<p>{{ doc.terms or \"\" }}</p>\n<p class=\"text-center\">{{ _(\"Thank you, please visit again.\") }}</p>",
"idx": 1, "idx": 1,
"line_breaks": 0, "line_breaks": 0,
"modified": "2020-04-29 16:45:58.942375", "modified": "2021-04-15 15:23:28.867135",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "POS Invoice", "name": "POS Invoice",

View File

@@ -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()

View File

@@ -248,177 +248,9 @@
"link_type": "DocType", "link_type": "DocType",
"onboard": 1, "onboard": 1,
"type": "Link" "type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Healthcare",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Patient",
"link_to": "Patient",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Diagnosis",
"link_to": "Diagnosis",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Education",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Student",
"link_to": "Student",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Instructor",
"link_to": "Instructor",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Course",
"link_to": "Course",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Room",
"link_to": "Room",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Non Profit",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Donor",
"link_to": "Donor",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Member",
"link_to": "Member",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Volunteer",
"link_to": "Volunteer",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Chapter",
"link_to": "Chapter",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Agriculture",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Location",
"link_to": "Location",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Crop",
"link_to": "Crop",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Crop Cycle",
"link_to": "Crop Cycle",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Fertilizer",
"link_to": "Fertilizer",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
} }
], ],
"modified": "2021-03-16 15:59:58.416154", "modified": "2021-04-19 15:48:44.089927",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Setup", "module": "Setup",
"name": "Home", "name": "Home",

View File

@@ -221,6 +221,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:

View File

@@ -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.

View File

@@ -13,6 +13,7 @@
"column_break_4", "column_break_4",
"valuation_method", "valuation_method",
"over_delivery_receipt_allowance", "over_delivery_receipt_allowance",
"role_allowed_to_over_deliver_receive",
"action_if_quality_inspection_is_not_submitted", "action_if_quality_inspection_is_not_submitted",
"show_barcode_field", "show_barcode_field",
"clean_description_html", "clean_description_html",
@@ -234,6 +235,13 @@
"fieldname": "disable_serial_no_and_batch_selector", "fieldname": "disable_serial_no_and_batch_selector",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Disable Serial No And Batch Selector" "label": "Disable Serial No And Batch Selector"
},
{
"description": "Users with this role are allowed to over deliver/receive against orders above the allowance percentage",
"fieldname": "role_allowed_to_over_deliver_receive",
"fieldtype": "Link",
"label": "Role Allowed to Over Deliver/Receive",
"options": "Role"
} }
], ],
"icon": "icon-cog", "icon": "icon-cog",
@@ -241,7 +249,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2021-01-18 13:15:38.352796", "modified": "2021-03-11 18:48:14.513055",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Settings", "name": "Stock Settings",

View File

@@ -609,8 +609,12 @@ def get_price_list_rate(args, item_doc, out):
meta = frappe.get_meta(args.parenttype or args.doctype) meta = frappe.get_meta(args.parenttype or args.doctype)
if meta.get_field("currency") or args.get('currency'): if meta.get_field("currency") or args.get('currency'):
pl_details = get_price_list_currency_and_exchange_rate(args) if not args.get("price_list_currency") or not args.get("plc_conversion_rate"):
args.update(pl_details) # if currency and plc_conversion_rate exist then
# `get_price_list_currency_and_exchange_rate` has already been called
pl_details = get_price_list_currency_and_exchange_rate(args)
args.update(pl_details)
if meta.get_field("currency"): if meta.get_field("currency"):
validate_conversion_rate(args, meta) validate_conversion_rate(args, meta)
@@ -1000,6 +1004,8 @@ def apply_price_list(args, as_doc=False):
args = process_args(args) args = process_args(args)
parent = get_price_list_currency_and_exchange_rate(args) parent = get_price_list_currency_and_exchange_rate(args)
args.update(parent)
children = [] children = []
if "items" in args: if "items" in args:
@@ -1064,7 +1070,7 @@ def get_price_list_currency_and_exchange_rate(args):
return frappe._dict({ return frappe._dict({
"price_list_currency": price_list_currency, "price_list_currency": price_list_currency,
"price_list_uom_dependant": price_list_uom_dependant, "price_list_uom_dependant": price_list_uom_dependant,
"plc_conversion_rate": plc_conversion_rate "plc_conversion_rate": plc_conversion_rate or 1
}) })
@frappe.whitelist() @frappe.whitelist()

View File

@@ -55,19 +55,31 @@ def get_item_info(filters):
def get_consumed_items(condition): def get_consumed_items(condition):
purpose_to_exclude = [
"Material Transfer for Manufacture",
"Material Transfer",
"Send to Subcontractor"
]
condition += """
and (
purpose is NULL
or purpose not in ({})
)
""".format(', '.join([f"'{p}'" for p in purpose_to_exclude]))
condition = condition.replace("posting_date", "sle.posting_date")
consumed_items = frappe.db.sql(""" consumed_items = frappe.db.sql("""
select item_code, abs(sum(actual_qty)) as consumed_qty select item_code, abs(sum(actual_qty)) as consumed_qty
from `tabStock Ledger Entry` from `tabStock Ledger Entry` as sle left join `tabStock Entry` as se
where actual_qty < 0 on sle.voucher_no = se.name
where
actual_qty < 0
and voucher_type not in ('Delivery Note', 'Sales Invoice') and voucher_type not in ('Delivery Note', 'Sales Invoice')
%s %s
group by item_code group by item_code""" % condition, as_dict=1)
""" % condition, as_dict=1)
consumed_items_map = {}
for item in consumed_items:
consumed_items_map.setdefault(item.item_code, item.consumed_qty)
consumed_items_map = {item.item_code : item.consumed_qty for item in consumed_items}
return consumed_items_map return consumed_items_map
def get_delivered_items(condition): def get_delivered_items(condition):

View File

@@ -1,15 +1,13 @@
braintree==3.57.1
frappe frappe
gocardless-pro==1.11.0 gocardless-pro~=1.22.0
googlemaps==3.1.1 googlemaps # used in ERPNext, but dependency is defined in Frappe
pandas>=1.0.5 pandas~=1.1.5
plaid-python>=7.0.0 plaid-python~=7.2.1
pycountry==19.8.18 pycountry~=20.7.3
PyGithub==1.44.1 PyGithub~=1.54.1
python-stdnum==1.12 python-stdnum~=1.16
python-youtube==0.6.0 python-youtube~=0.8.0
taxjar==1.9.0 taxjar~=1.9.2
tweepy==3.8.0 tweepy~=3.10.0
Unidecode==1.1.1 Unidecode~=1.2.0
WooCommerce==2.1.1 WooCommerce~=3.0.0
pycryptodome==3.9.8