Merge branch 'develop' into feat-added-balance-serial-no-column-develop

This commit is contained in:
Nabin Hait 2020-10-22 21:40:29 +05:30 committed by GitHub
commit 9ff4a2ebd8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
165 changed files with 5227 additions and 3271 deletions

48
.github/helper/documentation.py vendored Normal file
View File

@ -0,0 +1,48 @@
import sys
import requests
from urllib.parse import urlparse
docs_repos = [
"frappe_docs",
"erpnext_documentation",
"erpnext_com",
"frappe_io",
]
def uri_validator(x):
result = urlparse(x)
return all([result.scheme, result.netloc, result.path])
def docs_link_exists(body):
for line in body.splitlines():
for word in line.split():
if word.startswith('http') and uri_validator(word):
parsed_url = urlparse(word)
if parsed_url.netloc == "github.com":
_, org, repo, _type, ref = parsed_url.path.split('/')
if org == "frappe" and repo in docs_repos:
return True
if __name__ == "__main__":
pr = sys.argv[1]
response = requests.get("https://api.github.com/repos/frappe/erpnext/pulls/{}".format(pr))
if response.ok:
payload = response.json()
title = payload.get("title", "").lower()
head_sha = payload.get("head", {}).get("sha")
body = payload.get("body", "").lower()
if title.startswith("feat") and head_sha and "no-docs" not in body:
if docs_link_exists(body):
print("Documentation Link Found. You're Awesome! 🎉")
else:
print("Documentation Link Not Found! ⚠️")
sys.exit(1)
else:
print("Skipping documentation checks... 🏃")

60
.github/helper/translation.py vendored Normal file
View File

@ -0,0 +1,60 @@
import re
import sys
errors_encounter = 0
pattern = re.compile(r"_\(([\"']{,3})(?P<message>((?!\1).)*)\1(\s*,\s*context\s*=\s*([\"'])(?P<py_context>((?!\5).)*)\5)*(\s*,\s*(.)*?\s*(,\s*([\"'])(?P<js_context>((?!\11).)*)\11)*)*\)")
words_pattern = re.compile(r"_{1,2}\([\"'`]{1,3}.*?[a-zA-Z]")
start_pattern = re.compile(r"_{1,2}\([f\"'`]{1,3}")
f_string_pattern = re.compile(r"_\(f[\"']")
starts_with_f_pattern = re.compile(r"_\(f")
# skip first argument
files = sys.argv[1:]
files_to_scan = [_file for _file in files if _file.endswith(('.py', '.js'))]
for _file in files_to_scan:
with open(_file, 'r') as f:
print(f'Checking: {_file}')
file_lines = f.readlines()
for line_number, line in enumerate(file_lines, 1):
if 'frappe-lint: disable-translate' in line:
continue
start_matches = start_pattern.search(line)
if start_matches:
starts_with_f = starts_with_f_pattern.search(line)
if starts_with_f:
has_f_string = f_string_pattern.search(line)
if has_f_string:
errors_encounter += 1
print(f'\nF-strings are not supported for translations at line number {line_number + 1}\n{line.strip()[:100]}')
continue
else:
continue
match = pattern.search(line)
error_found = False
if not match and line.endswith(',\n'):
# concat remaining text to validate multiline pattern
line = "".join(file_lines[line_number - 1:])
line = line[start_matches.start() + 1:]
match = pattern.match(line)
if not match:
error_found = True
print(f'\nTranslation syntax error at line number {line_number + 1}\n{line.strip()[:100]}')
if not error_found and not words_pattern.search(line):
error_found = True
print(f'\nTranslation is useless because it has no words at line number {line_number + 1}\n{line.strip()[:100]}')
if error_found:
errors_encounter += 1
if errors_encounter > 0:
print('\nVisit "https://frappeframework.com/docs/user/en/translations" to learn about valid translation strings.')
sys.exit(1)
else:
print('\nGood To Go!')

24
.github/workflows/docs-checker.yml vendored Normal file
View File

@ -0,0 +1,24 @@
name: 'Documentation Required'
on:
pull_request:
types: [ opened, synchronize, reopened, edited ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: 'Setup Environment'
uses: actions/setup-python@v2
with:
python-version: 3.6
- name: 'Clone repo'
uses: actions/checkout@v2
- name: Validate Docs
env:
PR_NUMBER: ${{ github.event.number }}
run: |
pip install requests --quiet
python $GITHUB_WORKSPACE/.github/helper/documentation.py $PR_NUMBER

View File

@ -0,0 +1,22 @@
name: Frappe Linter
on:
pull_request:
branches:
- develop
- version-12-hotfix
- version-11-hotfix
jobs:
check_translation:
name: Translation Syntax Check
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Setup python3
uses: actions/setup-python@v1
with:
python-version: 3.6
- name: Validating Translation Syntax
run: |
git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q
files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF)
python $GITHUB_WORKSPACE/.github/helper/translation.py $files

View File

@ -5,6 +5,7 @@
"_comments": null,
"_liked_by": null,
"_user_tags": null,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -17,17 +18,24 @@
"docstatus": 0,
"dt": "Address",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "tax_category",
"fieldtype": "Link",
"hidden": 0,
"idx": 14,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"idx": 15,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "fax",
"label": "Tax Category",
"length": 0,
"mandatory_depends_on": null,
"modified": "2018-12-28 22:29:21.828090",
"modified_by": "Administrator",
"name": "Address-tax_category",
@ -43,6 +51,66 @@
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"translatable": 0,
"unique": 0,
"width": null
},
{
"_assign": null,
"_comments": null,
"_liked_by": null,
"_user_tags": null,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"creation": "2020-10-14 17:41:40.878179",
"default": "0",
"depends_on": null,
"description": null,
"docstatus": 0,
"dt": "Address",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "is_your_company_address",
"fieldtype": "Check",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"idx": 20,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "linked_with",
"label": "Is Your Company Address",
"length": 0,
"mandatory_depends_on": null,
"modified": "2020-10-14 17:41:40.878179",
"modified_by": "Administrator",
"name": "Address-is_your_company_address",
"no_copy": 0,
"options": null,
"owner": "Administrator",
"parent": null,
"parentfield": null,
"parenttype": null,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,

View File

@ -0,0 +1,42 @@
import frappe
from frappe import _
from frappe.contacts.doctype.address.address import Address
from frappe.contacts.doctype.address.address import get_address_templates
class ERPNextAddress(Address):
def validate(self):
self.validate_reference()
super(ERPNextAddress, self).validate()
def link_address(self):
"""Link address based on owner"""
if self.is_your_company_address:
return
return super(ERPNextAddress, self).link_address()
def validate_reference(self):
if self.is_your_company_address and not [
row for row in self.links if row.link_doctype == "Company"
]:
frappe.throw(_("Address needs to be linked to a Company. Please add a row for Company in the Links table."),
title=_("Company Not Linked"))
@frappe.whitelist()
def get_shipping_address(company, address = None):
filters = [
["Dynamic Link", "link_doctype", "=", "Company"],
["Dynamic Link", "link_name", "=", company],
["Address", "is_your_company_address", "=", 1]
]
fields = ["*"]
if address and frappe.db.get_value('Dynamic Link',
{'parent': address, 'link_name': company}):
filters.append(["Address", "name", "=", address])
address = frappe.get_all("Address", filters=filters, fields=fields) or {}
if address:
address_as_dict = address[0]
name, address_template = get_address_templates(address_as_dict)
return address_as_dict.get("name"), frappe.render_template(address_template, address_as_dict)

View File

@ -108,7 +108,7 @@
"pin_to_top": 0,
"shortcuts": [
{
"label": "Chart Of Accounts",
"label": "Chart of Accounts",
"link_to": "Account",
"type": "DocType"
},

View File

@ -2,7 +2,7 @@ frappe.provide("frappe.treeview_settings")
frappe.treeview_settings["Account"] = {
breadcrumb: "Accounts",
title: __("Chart Of Accounts"),
title: __("Chart of Accounts"),
get_tree_root: false,
filters: [
{
@ -97,7 +97,7 @@ frappe.treeview_settings["Account"] = {
treeview.page.add_inner_button(__("Journal Entry"), function() {
frappe.new_doc('Journal Entry', {company: get_company()});
}, __('Create'));
treeview.page.add_inner_button(__("New Company"), function() {
treeview.page.add_inner_button(__("Company"), function() {
frappe.new_doc('Company');
}, __('Create'));

View File

@ -195,7 +195,7 @@ def build_response_as_excel(writer):
reader = csv.reader(f)
from frappe.utils.xlsxutils import make_xlsx
xlsx_file = make_xlsx(reader, "Chart Of Accounts Importer Template")
xlsx_file = make_xlsx(reader, "Chart of Accounts Importer Template")
f.close()
os.remove(filename)

View File

@ -210,7 +210,7 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
$.each(this.frm.doc.accounts || [], function(i, jvd) {
frappe.model.set_default_values(jvd);
});
var posting_date = this.frm.posting_date;
var posting_date = this.frm.doc.posting_date;
if(!this.frm.doc.amended_from) this.frm.set_value('posting_date', posting_date || frappe.datetime.get_today());
}
},

View File

@ -195,88 +195,91 @@ def create_sales_invoice_record(qty=1):
def create_records():
# create a new loyalty Account
if frappe.db.exists("Account", "Loyalty - _TC"):
return
frappe.get_doc({
"doctype": "Account",
"account_name": "Loyalty",
"parent_account": "Direct Expenses - _TC",
"company": "_Test Company",
"is_group": 0,
"account_type": "Expense Account",
}).insert()
if not frappe.db.exists("Account", "Loyalty - _TC"):
frappe.get_doc({
"doctype": "Account",
"account_name": "Loyalty",
"parent_account": "Direct Expenses - _TC",
"company": "_Test Company",
"is_group": 0,
"account_type": "Expense Account",
}).insert()
# create a new loyalty program Single tier
frappe.get_doc({
"doctype": "Loyalty Program",
"loyalty_program_name": "Test Single Loyalty",
"auto_opt_in": 1,
"from_date": today(),
"loyalty_program_type": "Single Tier Program",
"conversion_factor": 1,
"expiry_duration": 10,
"company": "_Test Company",
"cost_center": "Main - _TC",
"expense_account": "Loyalty - _TC",
"collection_rules": [{
'tier_name': 'Silver',
'collection_factor': 1000,
'min_spent': 1000
}]
}).insert()
# create a new customer
frappe.get_doc({
"customer_group": "_Test Customer Group",
"customer_name": "Test Loyalty Customer",
"customer_type": "Individual",
"doctype": "Customer",
"territory": "_Test Territory"
}).insert()
# create a new loyalty program Multiple tier
frappe.get_doc({
"doctype": "Loyalty Program",
"loyalty_program_name": "Test Multiple Loyalty",
"auto_opt_in": 1,
"from_date": today(),
"loyalty_program_type": "Multiple Tier Program",
"conversion_factor": 1,
"expiry_duration": 10,
"company": "_Test Company",
"cost_center": "Main - _TC",
"expense_account": "Loyalty - _TC",
"collection_rules": [
{
if not frappe.db.exists("Loyalty Program","Test Single Loyalty"):
frappe.get_doc({
"doctype": "Loyalty Program",
"loyalty_program_name": "Test Single Loyalty",
"auto_opt_in": 1,
"from_date": today(),
"loyalty_program_type": "Single Tier Program",
"conversion_factor": 1,
"expiry_duration": 10,
"company": "_Test Company",
"cost_center": "Main - _TC",
"expense_account": "Loyalty - _TC",
"collection_rules": [{
'tier_name': 'Silver',
'collection_factor': 1000,
'min_spent': 10000
},
{
'tier_name': 'Gold',
'collection_factor': 1000,
'min_spent': 19000
}
]
}).insert()
'min_spent': 1000
}]
}).insert()
# create a new customer
if not frappe.db.exists("Customer","Test Loyalty Customer"):
frappe.get_doc({
"customer_group": "_Test Customer Group",
"customer_name": "Test Loyalty Customer",
"customer_type": "Individual",
"doctype": "Customer",
"territory": "_Test Territory"
}).insert()
# create a new loyalty program Multiple tier
if not frappe.db.exists("Loyalty Program","Test Multiple Loyalty"):
frappe.get_doc({
"doctype": "Loyalty Program",
"loyalty_program_name": "Test Multiple Loyalty",
"auto_opt_in": 1,
"from_date": today(),
"loyalty_program_type": "Multiple Tier Program",
"conversion_factor": 1,
"expiry_duration": 10,
"company": "_Test Company",
"cost_center": "Main - _TC",
"expense_account": "Loyalty - _TC",
"collection_rules": [
{
'tier_name': 'Silver',
'collection_factor': 1000,
'min_spent': 10000
},
{
'tier_name': 'Gold',
'collection_factor': 1000,
'min_spent': 19000
}
]
}).insert()
# create an item
item = frappe.get_doc({
"doctype": "Item",
"item_code": "Loyal Item",
"item_name": "Loyal Item",
"item_group": "All Item Groups",
"company": "_Test Company",
"is_stock_item": 1,
"opening_stock": 100,
"valuation_rate": 10000,
}).insert()
if not frappe.db.exists("Item", "Loyal Item"):
frappe.get_doc({
"doctype": "Item",
"item_code": "Loyal Item",
"item_name": "Loyal Item",
"item_group": "All Item Groups",
"company": "_Test Company",
"is_stock_item": 1,
"opening_stock": 100,
"valuation_rate": 10000,
}).insert()
# create item price
frappe.get_doc({
"doctype": "Item Price",
"price_list": "Standard Selling",
"item_code": item.item_code,
"price_list_rate": 10000
}).insert()
if not frappe.db.exists("Item Price", {"price_list": "Standard Selling", "item_code": "Loyal Item"}):
frappe.get_doc({
"doctype": "Item Price",
"price_list": "Standard Selling",
"item_code": "Loyal Item",
"price_list_rate": 10000
}).insert()

View File

@ -37,6 +37,11 @@ frappe.ui.form.on("Payment Reconciliation Payment", {
erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.extend({
onload: function() {
var me = this;
this.frm.set_query("party", function() {
check_mandatory(me.frm);
});
this.frm.set_query("party_type", function() {
return {
"filters": {
@ -46,37 +51,39 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
});
this.frm.set_query('receivable_payable_account', function() {
if(!me.frm.doc.company || !me.frm.doc.party_type) {
frappe.msgprint(__("Please select Company and Party Type first"));
} else {
return{
filters: {
"company": me.frm.doc.company,
"is_group": 0,
"account_type": frappe.boot.party_account_types[me.frm.doc.party_type]
}
};
}
check_mandatory(me.frm);
return {
filters: {
"company": me.frm.doc.company,
"is_group": 0,
"account_type": frappe.boot.party_account_types[me.frm.doc.party_type]
}
};
});
this.frm.set_query('bank_cash_account', function() {
if(!me.frm.doc.company) {
frappe.msgprint(__("Please select Company first"));
} else {
return{
filters:[
['Account', 'company', '=', me.frm.doc.company],
['Account', 'is_group', '=', 0],
['Account', 'account_type', 'in', ['Bank', 'Cash']]
]
};
}
check_mandatory(me.frm, true);
return {
filters:[
['Account', 'company', '=', me.frm.doc.company],
['Account', 'is_group', '=', 0],
['Account', 'account_type', 'in', ['Bank', 'Cash']]
]
};
});
this.frm.set_value('party_type', '');
this.frm.set_value('party', '');
this.frm.set_value('receivable_payable_account', '');
var check_mandatory = (frm, only_company=false) => {
var title = __("Mandatory");
if (only_company && !frm.doc.company) {
frappe.throw({message: __("Please Select a Company First"), title: title});
} else if (!frm.doc.company || !frm.doc.party_type) {
frappe.throw({message: __("Please Select Both Company and Party Type First"), title: title});
}
};
},
refresh: function() {
@ -90,7 +97,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
party: function() {
var me = this
if(!me.frm.doc.receivable_payable_account && me.frm.doc.party_type && me.frm.doc.party) {
if (!me.frm.doc.receivable_payable_account && me.frm.doc.party_type && me.frm.doc.party) {
return frappe.call({
method: "erpnext.accounts.party.get_party_account",
args: {
@ -99,7 +106,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
party: me.frm.doc.party
},
callback: function(r) {
if(!r.exc && r.message) {
if (!r.exc && r.message) {
me.frm.set_value("receivable_payable_account", r.message);
}
}

View File

@ -99,6 +99,7 @@ class PaymentReconciliation(Document):
and `tabGL Entry`.against_voucher_type = %(voucher_type)s
and `tab{doc}`.docstatus = 1 and `tabGL Entry`.party = %(party)s
and `tabGL Entry`.party_type = %(party_type)s and `tabGL Entry`.account = %(account)s
and `tabGL Entry`.is_cancelled = 0
GROUP BY `tab{doc}`.name
Having
amount > 0

View File

@ -45,6 +45,7 @@
"unique": 0
},
{
"description": "Provide the invoice portion in percent",
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 1,
@ -170,6 +171,7 @@
"unique": 0
},
{
"description": "Give number of days according to prior selection",
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 1,
@ -305,7 +307,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-03-08 10:47:32.830478",
"modified": "2020-10-14 10:47:32.830478",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Term",

View File

@ -45,7 +45,7 @@ class TestPOSClosingEntry(unittest.TestCase):
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
def init_user_and_profile():
def init_user_and_profile(**args):
user = 'test@example.com'
test_user = frappe.get_doc('User', user)
@ -53,7 +53,7 @@ def init_user_and_profile():
test_user.add_roles(*roles)
frappe.set_user(user)
pos_profile = make_pos_profile()
pos_profile = make_pos_profile(**args)
pos_profile.append('applicable_for_users', {
'default': 1,
'user': user

View File

@ -92,21 +92,19 @@ class POSInvoice(SalesInvoice):
if len(invalid_serial_nos):
multiple_nos = 's' if len(invalid_serial_nos) > 1 else ''
frappe.throw(_("Row #{}: Serial No{}. {} has already been transacted into another POS Invoice. \
Please select valid serial no.".format(d.idx, multiple_nos,
frappe.bold(', '.join(invalid_serial_nos)))), title=_("Not Available"))
frappe.throw(_("Row #{}: Serial No{}. {} has already been transacted into another POS Invoice. Please select valid serial no.").format(
d.idx, multiple_nos, frappe.bold(', '.join(invalid_serial_nos))), title=_("Not Available"))
else:
if allow_negative_stock:
return
available_stock = get_stock_availability(d.item_code, d.warehouse)
if not (flt(available_stock) > 0):
frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.'
.format(d.idx, frappe.bold(d.item_code), frappe.bold(d.warehouse))), title=_("Not Available"))
frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.').format(
d.idx, frappe.bold(d.item_code), frappe.bold(d.warehouse)), title=_("Not Available"))
elif flt(available_stock) < flt(d.qty):
frappe.msgprint(_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. \
Available quantity {}.'.format(d.idx, frappe.bold(d.item_code),
frappe.bold(d.warehouse), frappe.bold(d.qty))), title=_("Not Available"))
frappe.msgprint(_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}.').format(
d.idx, frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty)), title=_("Not Available"))
def validate_serialised_or_batched_item(self):
for d in self.get("items"):
@ -117,14 +115,14 @@ class POSInvoice(SalesInvoice):
if serialized and batched and (no_batch_selected or no_serial_selected):
frappe.throw(_('Row #{}: Please select a serial no and batch against item: {} or remove it to complete transaction.'
.format(d.idx, frappe.bold(d.item_code))), title=_("Invalid Item"))
frappe.throw(_('Row #{}: Please select a serial no and batch against item: {} or remove it to complete transaction.').format(
d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
if serialized and no_serial_selected:
frappe.throw(_('Row #{}: No serial number selected against item: {}. Please select one or remove it to complete transaction.'
.format(d.idx, frappe.bold(d.item_code))), title=_("Invalid Item"))
frappe.throw(_('Row #{}: No serial number selected against item: {}. Please select one or remove it to complete transaction.').format(
d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
if batched and no_batch_selected:
frappe.throw(_('Row #{}: No batch selected against item: {}. Please select a batch or remove it to complete transaction.'
.format(d.idx, frappe.bold(d.item_code))), title=_("Invalid Item"))
frappe.throw(_('Row #{}: No batch selected against item: {}. Please select a batch or remove it to complete transaction.').format(
d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
def validate_return_items(self):
if not self.get("is_return"): return
@ -139,7 +137,8 @@ class POSInvoice(SalesInvoice):
frappe.throw(_("At least one mode of payment is required for POS invoice."))
def validate_change_account(self):
if frappe.db.get_value("Account", self.account_for_change_amount, "company") != self.company:
if self.change_amount and self.account_for_change_amount and \
frappe.db.get_value("Account", self.account_for_change_amount, "company") != self.company:
frappe.throw(_("The selected change account {} doesn't belongs to Company {}.").format(self.account_for_change_amount, self.company))
def validate_change_amount(self):
@ -150,7 +149,7 @@ class POSInvoice(SalesInvoice):
self.base_change_amount = flt(self.base_paid_amount - base_grand_total + flt(self.base_write_off_amount))
if flt(self.change_amount) and not self.account_for_change_amount:
msgprint(_("Please enter Account for Change Amount"), raise_exception=1)
frappe.msgprint(_("Please enter Account for Change Amount"), raise_exception=1)
def verify_payment_amount(self):
for entry in self.payments:
@ -166,7 +165,7 @@ class POSInvoice(SalesInvoice):
total_amount_in_payments += payment.amount
invoice_total = self.rounded_total or self.grand_total
if total_amount_in_payments < invoice_total:
frappe.throw(_("Total payments amount can't be greater than {}".format(-invoice_total)))
frappe.throw(_("Total payments amount can't be greater than {}").format(-invoice_total))
def validate_loyalty_transaction(self):
if self.redeem_loyalty_points and (not self.loyalty_redemption_account or not self.loyalty_redemption_cost_center):

View File

@ -8,6 +8,7 @@ import unittest, copy, time
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
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.purchase_receipt.test_purchase_receipt import make_purchase_receipt
class TestPOSInvoice(unittest.TestCase):
def test_timestamp_change(self):
@ -222,29 +223,29 @@ class TestPOSInvoice(unittest.TestCase):
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 with perpetual inventory',
target_warehouse="Stores - TCP1", cost_center='Main - TCP1', expense_account='Cost of Goods Sold - TCP1')
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)
pos = create_pos_invoice(company='_Test Company with perpetual inventory', debit_to='Debtors - TCP1',
account_for_change_amount='Cash - TCP1', warehouse='Stores - TCP1', income_account='Sales - TCP1',
expense_account='Cost of Goods Sold - TCP1', cost_center='Main - TCP1',
pos = 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)
pos.get("items")[0].serial_no = serial_nos[0]
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 1000})
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000})
pos.insert()
pos.submit()
pos2 = create_pos_invoice(company='_Test Company with perpetual inventory', debit_to='Debtors - TCP1',
account_for_change_amount='Cash - TCP1', warehouse='Stores - TCP1', income_account='Sales - TCP1',
expense_account='Cost of Goods Sold - TCP1', cost_center='Main - TCP1',
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 - TCP1', 'amount': 1000})
pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000})
self.assertRaises(frappe.ValidationError, pos2.insert)
@ -295,7 +296,7 @@ class TestPOSInvoice(unittest.TestCase):
test_user, pos_profile = init_user_and_profile()
pos_inv = create_pos_invoice(rate=300, additional_discount_percentage=10, do_not_submit=1)
pos_inv.append('payments', {
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 270
})
pos_inv.submit()
@ -309,7 +310,7 @@ class TestPOSInvoice(unittest.TestCase):
pos_inv.load_from_db()
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
self.assertEqual(rounded_total, 3500)
self.assertEqual(rounded_total, 3470)
frappe.set_user("Administrator")
def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self):
@ -361,7 +362,7 @@ class TestPOSInvoice(unittest.TestCase):
if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 1)
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=1, basic_rate=300)
make_purchase_receipt(item_code="_Test Item", warehouse="_Test Warehouse - _TC", qty=1, rate=300)
frappe.db.sql("delete from `tabPOS Invoice`")
test_user, pos_profile = init_user_and_profile()
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
@ -413,8 +414,6 @@ def create_pos_invoice(**args):
pos_inv.is_pos = 1
pos_inv.pos_profile = args.pos_profile or pos_profile.name
pos_inv.set_missing_values()
if args.posting_date:
pos_inv.set_posting_time = 1
pos_inv.posting_date = args.posting_date or frappe.utils.nowdate()
@ -428,6 +427,8 @@ def create_pos_invoice(**args):
pos_inv.conversion_rate = args.conversion_rate or 1
pos_inv.account_for_change_amount = args.account_for_change_amount or "Cash - _TC"
pos_inv.set_missing_values()
pos_inv.append("items", {
"item_code": args.item or args.item_code or "_Test Item",
"warehouse": args.warehouse or "_Test Warehouse - _TC",

View File

@ -361,6 +361,7 @@
"fieldname": "bill_date",
"fieldtype": "Date",
"label": "Supplier Invoice Date",
"no_copy": 1,
"oldfieldname": "bill_date",
"oldfieldtype": "Date",
"print_hide": 1
@ -1333,8 +1334,7 @@
"icon": "fa fa-file-text",
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2020-08-03 23:20:04.466153",
"modified": "2020-09-21 12:22:09.164068",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@ -572,7 +572,8 @@ class SalesInvoice(SellingController):
def validate_pos(self):
if self.is_return:
if flt(self.paid_amount) + flt(self.write_off_amount) - flt(self.grand_total) > \
invoice_total = self.rounded_total or self.grand_total
if flt(self.paid_amount) + flt(self.write_off_amount) - flt(invoice_total) > \
1.0/(10.0**(self.precision("grand_total") + 1.0)):
frappe.throw(_("Paid amount + Write Off Amount can not be greater than Grand Total"))

View File

@ -171,7 +171,7 @@ def validate_account_for_perpetual_inventory(gl_map):
frappe.throw(_("Account: {0} can only be updated via Stock Transactions")
.format(account), StockAccountInvalidTransaction)
elif account_bal != stock_bal:
elif abs(account_bal - stock_bal) > 0.1:
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency"))

View File

@ -20,7 +20,7 @@
"owner": "Administrator",
"steps": [
{
"step": "Chart Of Accounts"
"step": "Chart of Accounts"
},
{
"step": "Setup Taxes"

View File

@ -10,11 +10,11 @@
"is_skipped": 0,
"modified": "2020-05-14 17:40:28.410447",
"modified_by": "Administrator",
"name": "Chart Of Accounts",
"name": "Chart of Accounts",
"owner": "Administrator",
"path": "Tree/Account",
"reference_document": "Account",
"show_full_form": 0,
"title": "Review Chart Of Accounts",
"title": "Review Chart of Accounts",
"validate_action": 0
}

View File

@ -19,8 +19,7 @@ def reconcile(bank_transaction, payment_doctype, payment_name):
gl_entry = frappe.get_doc("GL Entry", dict(account=account, voucher_type=payment_doctype, voucher_no=payment_name))
if payment_doctype == "Payment Entry" and payment_entry.unallocated_amount > transaction.unallocated_amount:
frappe.throw(_("The unallocated amount of Payment Entry {0} \
is greater than the Bank Transaction's unallocated amount").format(payment_name))
frappe.throw(_("The unallocated amount of Payment Entry {0} is greater than the Bank Transaction's unallocated amount").format(payment_name))
if transaction.unallocated_amount == 0:
frappe.throw(_("This bank transaction is already fully reconciled"))
@ -83,50 +82,30 @@ def check_matching_amount(bank_account, company, transaction):
"party", "party_type", "posting_date", "{0}".format(currency_field)], filters=[["paid_amount", "like", "{0}%".format(amount)],
["docstatus", "=", "1"], ["payment_type", "=", [payment_type, "Internal Transfer"]], ["ifnull(clearance_date, '')", "=", ""], ["{0}".format(account_from_to), "=", "{0}".format(bank_account)]])
if transaction.credit > 0:
journal_entries = frappe.db.sql("""
SELECT
'Journal Entry' as doctype, je.name, je.posting_date, je.cheque_no as reference_no,
je.pay_to_recd_from as party, je.cheque_date as reference_date, jea.debit_in_account_currency as paid_amount
FROM
`tabJournal Entry Account` as jea
JOIN
`tabJournal Entry` as je
ON
jea.parent = je.name
WHERE
(je.clearance_date is null or je.clearance_date='0000-00-00')
AND
jea.account = %s
AND
jea.debit_in_account_currency like %s
AND
je.docstatus = 1
""", (bank_account, amount), as_dict=True)
else:
journal_entries = frappe.db.sql("""
SELECT
'Journal Entry' as doctype, je.name, je.posting_date, je.cheque_no as reference_no,
jea.account_currency as currency, je.pay_to_recd_from as party, je.cheque_date as reference_date,
jea.credit_in_account_currency as paid_amount
FROM
`tabJournal Entry Account` as jea
JOIN
`tabJournal Entry` as je
ON
jea.parent = je.name
WHERE
(je.clearance_date is null or je.clearance_date='0000-00-00')
AND
jea.account = %(bank_account)s
AND
jea.credit_in_account_currency like %(txt)s
AND
je.docstatus = 1
""", {
'bank_account': bank_account,
'txt': '%%%s%%' % amount
}, as_dict=True)
jea_side = "debit" if transaction.credit > 0 else "credit"
journal_entries = frappe.db.sql(f"""
SELECT
'Journal Entry' as doctype, je.name, je.posting_date, je.cheque_no as reference_no,
jea.account_currency as currency, je.pay_to_recd_from as party, je.cheque_date as reference_date,
jea.{jea_side}_in_account_currency as paid_amount
FROM
`tabJournal Entry Account` as jea
JOIN
`tabJournal Entry` as je
ON
jea.parent = je.name
WHERE
(je.clearance_date is null or je.clearance_date='0000-00-00')
AND
jea.account = %(bank_account)s
AND
jea.{jea_side}_in_account_currency like %(txt)s
AND
je.docstatus = 1
""", {
'bank_account': bank_account,
'txt': '%%%s%%' % amount
}, as_dict=True)
if transaction.credit > 0:
sales_invoices = frappe.db.sql("""

View File

@ -203,7 +203,7 @@ def set_account_and_due_date(party, account, party_type, company, posting_date,
return out
@frappe.whitelist()
def get_party_account(party_type, party, company):
def get_party_account(party_type, party, company=None):
"""Returns the account for the given `party`.
Will first search in party (Customer / Supplier) record, if not found,
will search in group (Customer Group / Supplier Group),

View File

@ -3,6 +3,14 @@
frappe.query_reports["Bank Reconciliation Statement"] = {
"filters": [
{
"fieldname":"company",
"label": __("Company"),
"fieldtype": "Link",
"options": "Company",
"reqd": 1,
"default": frappe.defaults.get_user_default("Company")
},
{
"fieldname":"account",
"label": __("Bank Account"),
@ -12,11 +20,14 @@ frappe.query_reports["Bank Reconciliation Statement"] = {
locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: "",
"reqd": 1,
"get_query": function() {
var company = frappe.query_report.get_filter_value('company')
return {
"query": "erpnext.controllers.queries.get_account_list",
"filters": [
['Account', 'account_type', 'in', 'Bank, Cash'],
['Account', 'is_group', '=', 0],
['Account', 'disabled', '=', 0],
['Account', 'company', '=', company],
]
}
}

View File

@ -203,9 +203,39 @@ class TestPurchaseOrder(unittest.TestCase):
frappe.set_user("Administrator")
def test_update_child_with_tax_template(self):
"""
Test Action: Create a PO with one item having its tax account head already in the PO.
Add the same item + new item with tax template via Update Items.
Expected result: First Item's tax row is updated. New tax row is added for second Item.
"""
if not frappe.db.exists("Item", "Test Item with Tax"):
make_item("Test Item with Tax", {
'is_stock_item': 1,
})
if not frappe.db.exists("Item Tax Template", {"title": 'Test Update Items Template'}):
frappe.get_doc({
'doctype': 'Item Tax Template',
'title': 'Test Update Items Template',
'company': '_Test Company',
'taxes': [
{
'tax_type': "_Test Account Service Tax - _TC",
'tax_rate': 10,
}
]
}).insert()
new_item_with_tax = frappe.get_doc("Item", "Test Item with Tax")
new_item_with_tax.append("taxes", {
"item_tax_template": "Test Update Items Template",
"valid_from": nowdate()
})
new_item_with_tax.save()
tax_template = "_Test Account Excise Duty @ 10"
item = "_Test Item Home Desktop 100"
if not frappe.db.exists("Item Tax", {"parent":item, "item_tax_template":tax_template}):
item_doc = frappe.get_doc("Item", item)
item_doc.append("taxes", {
@ -237,17 +267,25 @@ class TestPurchaseOrder(unittest.TestCase):
items = json.dumps([
{'item_code' : item, 'rate' : 500, 'qty' : 1, 'docname': po.items[0].name},
{'item_code' : item, 'rate' : 100, 'qty' : 1} # added item
{'item_code' : item, 'rate' : 100, 'qty' : 1}, # added item whose tax account head already exists in PO
{'item_code' : new_item_with_tax.name, 'rate' : 100, 'qty' : 1} # added item whose tax account head is missing in PO
])
update_child_qty_rate('Purchase Order', items, po.name)
po.reload()
self.assertEqual(po.taxes[0].tax_amount, 60)
self.assertEqual(po.taxes[0].total, 660)
self.assertEqual(po.taxes[0].tax_amount, 70)
self.assertEqual(po.taxes[0].total, 770)
self.assertEqual(po.taxes[1].account_head, "_Test Account Service Tax - _TC")
self.assertEqual(po.taxes[1].tax_amount, 70)
self.assertEqual(po.taxes[1].total, 840)
# teardown
frappe.db.sql("""UPDATE `tabItem Tax` set valid_from = NULL
where parent = %(item)s and item_tax_template = %(tax)s""",
{"item": item, "tax": tax_template})
where parent = %(item)s and item_tax_template = %(tax)s""", {"item": item, "tax": tax_template})
po.cancel()
po.delete()
new_item_with_tax.delete()
frappe.get_doc("Item Tax Template", "Test Update Items Template").delete()
def test_update_child_uom_conv_factor_change(self):
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes")
@ -651,12 +689,12 @@ class TestPurchaseOrder(unittest.TestCase):
make_subcontracted_item(item_code)
po = create_purchase_order(item_code=item_code, qty=1,
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=1)
name = frappe.db.get_value('BOM', {'item': item_code}, 'name')
bom = frappe.get_doc('BOM', name)
exploded_items = sorted([d.item_code for d in bom.exploded_items])
exploded_items = sorted([d.item_code for d in bom.exploded_items if not d.get('sourced_by_supplier')])
supplied_items = sorted([d.rm_item_code for d in po.supplied_items])
self.assertEquals(exploded_items, supplied_items)
@ -664,7 +702,7 @@ class TestPurchaseOrder(unittest.TestCase):
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=0)
supplied_items1 = sorted([d.rm_item_code for d in po1.supplied_items])
bom_items = sorted([d.item_code for d in bom.items])
bom_items = sorted([d.item_code for d in bom.items if not d.get('sourced_by_supplier')])
self.assertEquals(supplied_items1, bom_items)

View File

@ -22,8 +22,6 @@ frappe.ui.form.on("Request for Quotation",{
},
onload: function(frm) {
frm.add_fetch('email_template', 'response', 'message_for_supplier');
if(!frm.doc.message_for_supplier) {
frm.set_value("message_for_supplier", __("Please supply the specified items at the best possible rates"))
}
@ -194,6 +192,66 @@ frappe.ui.form.on("Request for Quotation",{
});
});
dialog.show()
},
preview: (frm) => {
let dialog = new frappe.ui.Dialog({
title: __('Preview Email'),
fields: [
{
label: __('Supplier'),
fieldtype: 'Select',
fieldname: 'supplier',
options: frm.doc.suppliers.map(row => row.supplier),
reqd: 1
},
{
fieldtype: 'Column Break',
fieldname: 'col_break_1',
},
{
label: __('Subject'),
fieldtype: 'Data',
fieldname: 'subject',
read_only: 1,
depends_on: 'subject'
},
{
fieldtype: 'Section Break',
fieldname: 'sec_break_1',
hide_border: 1
},
{
label: __('Email'),
fieldtype: 'HTML',
fieldname: 'email_preview'
},
{
fieldtype: 'Section Break',
fieldname: 'sec_break_2'
},
{
label: __('Note'),
fieldtype: 'HTML',
fieldname: 'note'
}
]
});
dialog.fields_dict['supplier'].df.onchange = () => {
var supplier = dialog.get_value('supplier');
frm.call('get_supplier_email_preview', {supplier: supplier}).then(result => {
dialog.fields_dict.email_preview.$wrapper.empty();
dialog.fields_dict.email_preview.$wrapper.append(result.message);
});
}
dialog.fields_dict.note.$wrapper.append(`<p class="small text-muted">This is a preview of the email to be sent. A PDF of the document will
automatically be attached with the email.</p>`);
dialog.set_value("subject", frm.doc.subject);
dialog.show();
}
})
@ -276,7 +334,7 @@ erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.e
})
}, __("Get items from"));
// Get items from Opportunity
this.frm.add_custom_button(__('Opportunity'),
this.frm.add_custom_button(__('Opportunity'),
function() {
erpnext.utils.map_current_doc({
method: "erpnext.crm.doctype.opportunity.opportunity.make_request_for_quotation",

View File

@ -1,5 +1,5 @@
{
"actions": "",
"actions": [],
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2016-02-25 01:24:07.224790",
@ -19,7 +19,12 @@
"items",
"link_to_mrs",
"supplier_response_section",
"salutation",
"email_template",
"col_break_email_1",
"subject",
"preview",
"sec_break_email_2",
"message_for_supplier",
"terms_section_break",
"tc_name",
@ -126,8 +131,10 @@
"label": "Link to Material Requests"
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "supplier_response_section",
"fieldtype": "Section Break"
"fieldtype": "Section Break",
"label": "Email Details"
},
{
"fieldname": "email_template",
@ -137,6 +144,8 @@
"print_hide": 1
},
{
"fetch_from": "email_template.response",
"fetch_if_empty": 1,
"fieldname": "message_for_supplier",
"fieldtype": "Text Editor",
"in_list_view": 1,
@ -230,12 +239,45 @@
"options": "Request for Quotation",
"print_hide": 1,
"read_only": 1
},
{
"fetch_from": "email_template.subject",
"fetch_if_empty": 1,
"fieldname": "subject",
"fieldtype": "Data",
"label": "Subject",
"print_hide": 1
},
{
"description": "Select a greeting for the receiver. E.g. Mr., Ms., etc.",
"fieldname": "salutation",
"fieldtype": "Link",
"label": "Salutation",
"no_copy": 1,
"options": "Salutation",
"print_hide": 1
},
{
"fieldname": "col_break_email_1",
"fieldtype": "Column Break"
},
{
"depends_on": "eval:!doc.docstatus==1",
"fieldname": "preview",
"fieldtype": "Button",
"label": "Preview Email"
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "sec_break_email_2",
"fieldtype": "Section Break",
"hide_border": 1
}
],
"icon": "fa fa-shopping-cart",
"is_submittable": 1,
"links": [],
"modified": "2020-06-25 14:37:21.140194",
"modified": "2020-10-01 14:54:50.888729",
"modified_by": "Administrator",
"module": "Buying",
"name": "Request for Quotation",

View File

@ -51,7 +51,7 @@ class RequestforQuotation(BuyingController):
def validate_email_id(self, args):
if not args.email_id:
frappe.throw(_("Row {0}: For Supplier {0}, Email Address is Required to Send Email").format(args.idx, args.supplier))
frappe.throw(_("Row {0}: For Supplier {1}, Email Address is Required to send an email").format(args.idx, frappe.bold(args.supplier)))
def on_submit(self):
frappe.db.set(self, 'status', 'Submitted')
@ -62,17 +62,31 @@ class RequestforQuotation(BuyingController):
def on_cancel(self):
frappe.db.set(self, 'status', 'Cancelled')
def get_supplier_email_preview(self, supplier):
"""Returns formatted email preview as string."""
rfq_suppliers = list(filter(lambda row: row.supplier == supplier, self.suppliers))
rfq_supplier = rfq_suppliers[0]
self.validate_email_id(rfq_supplier)
message = self.supplier_rfq_mail(rfq_supplier, '', self.get_link(), True)
return message
def send_to_supplier(self):
"""Sends RFQ mail to involved suppliers."""
for rfq_supplier in self.suppliers:
if rfq_supplier.send_email:
self.validate_email_id(rfq_supplier)
# make new user if required
update_password_link = self.update_supplier_contact(rfq_supplier, self.get_link())
update_password_link, contact = self.update_supplier_contact(rfq_supplier, self.get_link())
self.update_supplier_part_no(rfq_supplier)
self.supplier_rfq_mail(rfq_supplier, update_password_link, self.get_link())
rfq_supplier.email_sent = 1
if not rfq_supplier.contact:
rfq_supplier.contact = contact
rfq_supplier.save()
def get_link(self):
@ -87,18 +101,19 @@ class RequestforQuotation(BuyingController):
def update_supplier_contact(self, rfq_supplier, link):
'''Create a new user for the supplier if not set in contact'''
update_password_link = ''
update_password_link, contact = '', ''
if frappe.db.exists("User", rfq_supplier.email_id):
user = frappe.get_doc("User", rfq_supplier.email_id)
else:
user, update_password_link = self.create_user(rfq_supplier, link)
self.update_contact_of_supplier(rfq_supplier, user)
contact = self.link_supplier_contact(rfq_supplier, user)
return update_password_link
return update_password_link, contact
def update_contact_of_supplier(self, rfq_supplier, user):
def link_supplier_contact(self, rfq_supplier, user):
"""If no Contact, create a new contact against Supplier. If Contact exists, check if email and user id set."""
if rfq_supplier.contact:
contact = frappe.get_doc("Contact", rfq_supplier.contact)
else:
@ -115,6 +130,10 @@ class RequestforQuotation(BuyingController):
contact.save(ignore_permissions=True)
if not rfq_supplier.contact:
# return contact to later update, RFQ supplier row's contact
return contact.name
def create_user(self, rfq_supplier, link):
user = frappe.get_doc({
'doctype': 'User',
@ -129,22 +148,36 @@ class RequestforQuotation(BuyingController):
return user, update_password_link
def supplier_rfq_mail(self, data, update_password_link, rfq_link):
def supplier_rfq_mail(self, data, update_password_link, rfq_link, preview=False):
full_name = get_user_fullname(frappe.session['user'])
if full_name == "Guest":
full_name = "Administrator"
# send document dict and some important data from suppliers row
# to render message_for_supplier from any template
doc_args = self.as_dict()
doc_args.update({
'supplier': data.get('supplier'),
'supplier_name': data.get('supplier_name')
})
args = {
'update_password_link': update_password_link,
'message': frappe.render_template(self.message_for_supplier, data.as_dict()),
'message': frappe.render_template(self.message_for_supplier, doc_args),
'rfq_link': rfq_link,
'user_fullname': full_name
'user_fullname': full_name,
'supplier_name' : data.get('supplier_name'),
'supplier_salutation' : self.salutation or 'Dear Mx.',
}
subject = _("Request for Quotation")
subject = self.subject or _("Request for Quotation")
template = "templates/emails/request_for_quotation.html"
sender = frappe.session.user not in STANDARD_USERS and frappe.session.user or None
message = frappe.get_template(template).render(args)
if preview:
return message
attachments = self.get_attachments()
self.send_email(data, sender, subject, message, attachments)

View File

@ -9,7 +9,7 @@ import frappe
from frappe.utils import nowdate
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.templates.pages.rfq import check_supplier_has_docname_access
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation_from_rfq
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import create_supplier_quotation
from erpnext.crm.doctype.opportunity.test_opportunity import make_opportunity
from erpnext.crm.doctype.opportunity.opportunity import make_request_for_quotation as make_rfq
@ -22,7 +22,7 @@ class TestRequestforQuotation(unittest.TestCase):
self.assertEqual(rfq.get('suppliers')[1].quote_status, 'Pending')
# Submit the first supplier quotation
sq = make_supplier_quotation(rfq.name, rfq.get('suppliers')[0].supplier)
sq = make_supplier_quotation_from_rfq(rfq.name, for_supplier=rfq.get('suppliers')[0].supplier)
sq.submit()
# No Quote first supplier quotation
@ -37,10 +37,10 @@ class TestRequestforQuotation(unittest.TestCase):
def test_make_supplier_quotation(self):
rfq = make_request_for_quotation()
sq = make_supplier_quotation(rfq.name, rfq.get('suppliers')[0].supplier)
sq = make_supplier_quotation_from_rfq(rfq.name, for_supplier=rfq.get('suppliers')[0].supplier)
sq.submit()
sq1 = make_supplier_quotation(rfq.name, rfq.get('suppliers')[1].supplier)
sq1 = make_supplier_quotation_from_rfq(rfq.name, for_supplier=rfq.get('suppliers')[1].supplier)
sq1.submit()
self.assertEqual(sq.supplier, rfq.get('suppliers')[0].supplier)
@ -62,7 +62,7 @@ class TestRequestforQuotation(unittest.TestCase):
rfq = make_request_for_quotation(supplier_data=supplier_wt_appos)
sq = make_supplier_quotation(rfq.name, supplier_wt_appos[0].get("supplier"))
sq = make_supplier_quotation_from_rfq(rfq.name, for_supplier=supplier_wt_appos[0].get("supplier"))
sq.submit()
frappe.form_dict = frappe.local("form_dict")

View File

@ -1,362 +1,112 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"actions": [],
"creation": "2016-03-29 05:59:11.896885",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"send_email",
"email_sent",
"supplier",
"contact",
"no_quote",
"quote_status",
"column_break_3",
"supplier_name",
"email_id",
"download_pdf"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"fieldname": "send_email",
"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": "Send Email",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Send Email"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"depends_on": "eval:doc.docstatus >= 1",
"fieldname": "email_sent",
"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": "Email Sent",
"length": 0,
"no_copy": 1,
"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
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 4,
"fieldname": "supplier",
"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": "Supplier",
"length": 0,
"no_copy": 0,
"options": "Supplier",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"allow_on_submit": 1,
"columns": 3,
"fieldname": "contact",
"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": "Contact",
"length": 0,
"no_copy": 1,
"options": "Contact",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"options": "Contact"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"depends_on": "eval:doc.docstatus >= 1 && doc.quote_status != 'Received'",
"fieldname": "no_quote",
"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": "No Quote",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "No Quote"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.docstatus >= 1 && !doc.no_quote",
"fieldname": "quote_status",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Quote Status",
"length": 0,
"no_copy": 0,
"options": "Pending\nReceived\nNo Quote",
"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
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 1,
"collapsible": 0,
"columns": 0,
"fetch_from": "supplier.supplier_name",
"fieldname": "supplier_name",
"fieldtype": "Read Only",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Supplier Name",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Supplier Name"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 3,
"fetch_from": "contact.email_id",
"fieldname": "email_id",
"fieldtype": "Data",
"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": "Email Id",
"length": 0,
"no_copy": 1,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"no_copy": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "download_pdf",
"fieldtype": "Button",
"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": "Download PDF",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Download PDF"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-05-16 22:43:30.212408",
"links": [],
"modified": "2020-09-28 19:31:11.855588",
"modified_by": "Administrator",
"module": "Buying",
"name": "Request for Quotation Supplier",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
"track_changes": 1
}

View File

@ -241,7 +241,7 @@
"fieldname": "rate",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Rate ",
"label": "Rate",
"oldfieldname": "import_rate",
"oldfieldtype": "Currency",
"options": "currency"
@ -560,7 +560,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-10-01 16:34:39.703033",
"modified": "2020-10-19 12:36:26.913211",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Quotation Item",

View File

@ -143,7 +143,7 @@ def get_conditions(filters):
conditions = ""
if filters.get("company"):
conditions += " AND par.company=%s" % frappe.db.escape(filters.get('company'))
conditions += " AND parent.company=%s" % frappe.db.escape(filters.get('company'))
if filters.get("cost_center") or filters.get("project"):
conditions += """
@ -151,10 +151,10 @@ def get_conditions(filters):
""" % (frappe.db.escape(filters.get('cost_center')), frappe.db.escape(filters.get('project')))
if filters.get("from_date"):
conditions += " AND par.transaction_date>='%s'" % filters.get('from_date')
conditions += " AND parent.transaction_date>='%s'" % filters.get('from_date')
if filters.get("to_date"):
conditions += " AND par.transaction_date<='%s'" % filters.get('to_date')
conditions += " AND parent.transaction_date<='%s'" % filters.get('to_date')
return conditions
def get_data(filters):
@ -198,21 +198,23 @@ def get_mapped_mr_details(conditions):
mr_records = {}
mr_details = frappe.db.sql("""
SELECT
par.transaction_date,
par.per_ordered,
par.owner,
parent.transaction_date,
parent.per_ordered,
parent.owner,
child.name,
child.parent,
child.amount,
child.qty,
child.item_code,
child.uom,
par.status
FROM `tabMaterial Request` par, `tabMaterial Request Item` child
parent.status,
child.project,
child.cost_center
FROM `tabMaterial Request` parent, `tabMaterial Request Item` child
WHERE
par.per_ordered>=0
AND par.name=child.parent
AND par.docstatus=1
parent.per_ordered>=0
AND parent.name=child.parent
AND parent.docstatus=1
{conditions}
""".format(conditions=conditions), as_dict=1) #nosec
@ -232,7 +234,9 @@ def get_mapped_mr_details(conditions):
status=record.status,
actual_cost=0,
purchase_order_amt=0,
purchase_order_amt_in_company_currency=0
purchase_order_amt_in_company_currency=0,
project = record.project,
cost_center = record.cost_center
)
procurement_record_against_mr.append(procurement_record_details)
return mr_records, procurement_record_against_mr
@ -280,16 +284,16 @@ def get_po_entries(conditions):
child.amount,
child.base_amount,
child.schedule_date,
par.transaction_date,
par.supplier,
par.status,
par.owner
FROM `tabPurchase Order` par, `tabPurchase Order Item` child
parent.transaction_date,
parent.supplier,
parent.status,
parent.owner
FROM `tabPurchase Order` parent, `tabPurchase Order Item` child
WHERE
par.docstatus = 1
AND par.name = child.parent
AND par.status not in ("Closed","Completed","Cancelled")
parent.docstatus = 1
AND parent.name = child.parent
AND parent.status not in ("Closed","Completed","Cancelled")
{conditions}
GROUP BY
par.name, child.item_code
parent.name, child.item_code
""".format(conditions=conditions), as_dict=1) #nosec

View File

@ -15,9 +15,9 @@ class CallLog(Document):
number = strip_number(self.get('from'))
self.contact = get_contact_with_phone_number(number)
self.lead = get_lead_with_phone_number(number)
contact = frappe.get_doc("Contact", self.contact)
self.customer = contact.get_link_for("Customer")
if self.contact:
contact = frappe.get_doc("Contact", self.contact)
self.customer = contact.get_link_for("Customer")
def after_insert(self):
self.trigger_call_popup()

View File

@ -63,8 +63,8 @@ def get_data():
{
"type": "doctype",
"name": "Chart of Accounts Importer",
"labe": _("Chart Of Accounts Importer"),
"description": _("Import Chart Of Accounts from CSV / Excel files"),
"label": _("Chart of Accounts Importer"),
"description": _("Import Chart of Accounts from CSV / Excel files"),
"onboard": 1
},
{

View File

@ -605,8 +605,6 @@ class AccountsController(TransactionBase):
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
if self.doctype in ["Sales Invoice", "Purchase Invoice"]:
if self.is_return: return
if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'):
unlink_ref_doc_from_payment_entries(self)
@ -1170,6 +1168,31 @@ def set_child_tax_template_and_map(item, child_item, parent_doc):
if child_item.get("item_tax_template"):
child_item.item_tax_rate = get_item_tax_map(parent_doc.get('company'), child_item.item_tax_template, as_json=True)
def add_taxes_from_tax_template(child_item, parent_doc):
add_taxes_from_item_tax_template = frappe.db.get_single_value("Accounts Settings", "add_taxes_from_item_tax_template")
if child_item.get("item_tax_rate") and add_taxes_from_item_tax_template:
tax_map = json.loads(child_item.get("item_tax_rate"))
for tax_type in tax_map:
tax_rate = flt(tax_map[tax_type])
taxes = parent_doc.get('taxes') or []
# add new row for tax head only if missing
found = any(tax.account_head == tax_type for tax in taxes)
if not found:
tax_row = parent_doc.append("taxes", {})
tax_row.update({
"description" : str(tax_type).split(' - ')[0],
"charge_type" : "On Net Total",
"account_head" : tax_type,
"rate" : tax_rate
})
if parent_doc.doctype == "Purchase Order":
tax_row.update({
"category" : "Total",
"add_deduct_tax" : "Add"
})
tax_row.db_insert()
def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, trans_item):
"""
Returns a Sales Order Item child item containing the default values
@ -1185,6 +1208,7 @@ def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname,
conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor"))
child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor
set_child_tax_template_and_map(item, child_item, p_doc)
add_taxes_from_tax_template(child_item, p_doc)
child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
if not child_item.warehouse:
frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.")
@ -1209,6 +1233,7 @@ def set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docna
child_item.base_rate = 1 # Initiallize value will update in parent validation
child_item.base_amount = 1 # Initiallize value will update in parent validation
set_child_tax_template_and_map(item, child_item, p_doc)
add_taxes_from_tax_template(child_item, p_doc)
return child_item
def validate_and_delete_children(parent, data):

View File

@ -52,7 +52,7 @@ class StockController(AccountsController):
frappe.throw(_("Row #{0}: Serial No {1} does not belong to Batch {2}")
.format(d.idx, serial_no_data.name, d.batch_no))
if d.qty > 0 and d.get("batch_no") and self.get("posting_date") and self.docstatus < 2:
if flt(d.qty) > 0.0 and d.get("batch_no") and self.get("posting_date") and self.docstatus < 2:
expiry_date = frappe.get_cached_value("Batch", d.get("batch_no"), "expiry_date")
if expiry_date and getdate(expiry_date) < getdate(self.posting_date):

View File

@ -241,6 +241,7 @@
},
{
"depends_on": "eval: doc.__islocal",
"description": "Home, Work, etc.",
"fieldname": "address_title",
"fieldtype": "Data",
"label": "Address Title"
@ -249,7 +250,8 @@
"depends_on": "eval: doc.__islocal",
"fieldname": "address_line1",
"fieldtype": "Data",
"label": "Address Line 1"
"label": "Address Line 1",
"mandatory_depends_on": "eval: doc.address_title && doc.address_type"
},
{
"depends_on": "eval: doc.__islocal",
@ -261,7 +263,8 @@
"depends_on": "eval: doc.__islocal",
"fieldname": "city",
"fieldtype": "Data",
"label": "City/Town"
"label": "City/Town",
"mandatory_depends_on": "eval: doc.address_title && doc.address_type"
},
{
"depends_on": "eval: doc.__islocal",
@ -280,6 +283,7 @@
"fieldname": "country",
"fieldtype": "Link",
"label": "Country",
"mandatory_depends_on": "eval: doc.address_title && doc.address_type",
"options": "Country"
},
{
@ -449,7 +453,7 @@
"idx": 5,
"image_field": "image",
"links": [],
"modified": "2020-06-18 14:39:41.835416",
"modified": "2020-10-13 15:24:00.094811",
"modified_by": "Administrator",
"module": "CRM",
"name": "Lead",

View File

@ -22,7 +22,8 @@ class Lead(SellingController):
load_address_and_contact(self)
def before_insert(self):
self.address_doc = self.create_address()
if self.address_title and self.address_type:
self.address_doc = self.create_address()
self.contact_doc = self.create_contact()
def after_insert(self):
@ -133,15 +134,6 @@ class Lead(SellingController):
# skipping country since the system auto-sets it from system defaults
address = frappe.new_doc("Address")
mandatory_fields = [ df.fieldname for df in address.meta.fields if df.reqd ]
if not all([self.get(field) for field in mandatory_fields]):
frappe.msgprint(_('Missing mandatory fields in address. \
{0} to create address' ).format("<a href='desk#Form/Address/New Address 1' \
> Click here </a>"),
alert=True, indicator='yellow')
return
address.update({addr_field: self.get(addr_field) for addr_field in address_fields})
address.update({info_field: self.get(info_field) for info_field in info_fields})
address.insert()
@ -190,7 +182,7 @@ class Lead(SellingController):
def update_links(self):
# update address links
if self.address_doc:
if hasattr(self, 'address_doc'):
self.address_doc.append("links", {
"link_doctype": "Lead",
"link_name": self.name,

View File

@ -11,7 +11,7 @@ from erpnext.accounts.party import get_party_account_currency
from erpnext.exceptions import InvalidCurrency
from erpnext.stock.doctype.material_request.material_request import make_request_for_quotation
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import \
make_supplier_quotation as make_quotation_from_rfq
make_supplier_quotation_from_rfq
def work():
frappe.set_user(frappe.db.get_global('demo_purchase_user'))
@ -44,7 +44,7 @@ def work():
rfq = frappe.get_doc('Request for Quotation', rfq.name)
for supplier in rfq.suppliers:
supplier_quotation = make_quotation_from_rfq(rfq.name, supplier.supplier)
supplier_quotation = make_supplier_quotation_from_rfq(rfq.name, for_supplier=supplier.supplier)
supplier_quotation.save()
supplier_quotation.submit()

View File

@ -6,9 +6,13 @@ from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import get_link_to_form
from functools import reduce
class CourseEnrollment(Document):
def validate(self):
self.validate_duplication()
def get_progress(self, student):
"""
Returns Progress of given student for a particular course enrollment
@ -27,13 +31,15 @@ class CourseEnrollment(Document):
return []
def validate_duplication(self):
enrollment = frappe.get_all("Course Enrollment", filters={
enrollment = frappe.db.exists("Course Enrollment", {
"student": self.student,
"course": self.course,
"program_enrollment": self.program_enrollment
"program_enrollment": self.program_enrollment,
"name": ("!=", self.name)
})
if enrollment:
frappe.throw(_("Student is already enrolled."))
frappe.throw(_("Student is already enrolled via Course Enrollment {0}").format(
get_link_to_form("Course Enrollment", enrollment)), title=_('Duplicate Entry'))
def add_quiz_activity(self, quiz_name, quiz_response, answers, score, status):
result = {k: ('Correct' if v else 'Wrong') for k,v in answers.items()}

View File

@ -17,8 +17,9 @@ class TestCourseEnrollment(unittest.TestCase):
setup_program()
student = create_student({"first_name": "_Test First", "last_name": "_Test Last", "email": "_test_student_1@example.com"})
program_enrollment = student.enroll_in_program("_Test Program")
course_enrollment = student.enroll_in_course("_Test Course 1", program_enrollment.name)
make_course_activity(course_enrollment.name, "Article", "_Test Article 1-1")
course_enrollment = frappe.db.get_value("Course Enrollment",
{"course": "_Test Course 1", "student": student.name, "program_enrollment": program_enrollment.name}, 'name')
make_course_activity(course_enrollment, "Article", "_Test Article 1-1")
def test_get_progress(self):
student = get_student("_test_student_1@example.com")
@ -30,5 +31,14 @@ class TestCourseEnrollment(unittest.TestCase):
self.assertTrue(finished in progress)
frappe.db.rollback()
def tearDown(self):
for entry in frappe.db.get_all("Course Enrollment"):
frappe.delete_doc("Course Enrollment", entry.name)
for entry in frappe.db.get_all("Program Enrollment"):
doc = frappe.get_doc("Program Enrollment", entry.name)
doc.cancel()
doc.delete()

View File

@ -49,6 +49,11 @@ class TestProgram(unittest.TestCase):
self.assertEqual(course[1].name, "_Test Course 2")
frappe.db.rollback()
def tearDown(self):
for dt in ["Program", "Course", "Topic", "Article"]:
for entry in frappe.get_all(dt):
frappe.delete_doc(dt, entry.program)
def make_program(name):
program = frappe.get_doc({
"doctype": "Program",
@ -68,7 +73,7 @@ def make_program_and_linked_courses(program_name, course_name_list):
program = frappe.get_doc("Program", program_name)
course_list = [make_course(course_name) for course_name in course_name_list]
for course in course_list:
program.append("courses", {"course": course})
program.append("courses", {"course": course, "required": 1})
program.save()
return program

View File

@ -17,9 +17,7 @@
"in_list_view": 1,
"label": "Course",
"options": "Course",
"reqd": 1,
"show_days": 1,
"show_seconds": 1
"reqd": 1
},
{
"fetch_from": "course.course_name",
@ -27,23 +25,19 @@
"fieldtype": "Data",
"in_list_view": 1,
"label": "Course Name",
"read_only": 1,
"show_days": 1,
"show_seconds": 1
"read_only": 1
},
{
"default": "0",
"default": "1",
"fieldname": "required",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Mandatory",
"show_days": 1,
"show_seconds": 1
"label": "Mandatory"
}
],
"istable": 1,
"links": [],
"modified": "2020-06-09 18:56:10.213241",
"modified": "2020-09-15 18:14:22.816795",
"modified_by": "Administrator",
"module": "Education",
"name": "Program Course",

View File

@ -2,16 +2,24 @@
// For license information, please see license.txt
frappe.ui.form.on("Program Enrollment", {
frappe.ui.form.on('Program Enrollment', {
setup: function(frm) {
frm.add_fetch('fee_structure', 'total_amount', 'amount');
},
onload: function(frm, cdt, cdn){
frm.set_query("academic_term", "fees", function(){
return{
"filters":{
"academic_year": (frm.doc.academic_year)
onload: function(frm) {
frm.set_query('academic_term', function() {
return {
'filters':{
'academic_year': frm.doc.academic_year
}
};
});
frm.set_query('academic_term', 'fees', function() {
return {
'filters':{
'academic_year': frm.doc.academic_year
}
};
});
@ -24,9 +32,9 @@ frappe.ui.form.on("Program Enrollment", {
};
if (frm.doc.program) {
frm.set_query("course", "courses", function(doc, cdt, cdn) {
return{
query: "erpnext.education.doctype.program_enrollment.program_enrollment.get_program_courses",
frm.set_query('course', 'courses', function() {
return {
query: 'erpnext.education.doctype.program_enrollment.program_enrollment.get_program_courses',
filters: {
'program': frm.doc.program
}
@ -34,9 +42,9 @@ frappe.ui.form.on("Program Enrollment", {
});
}
frm.set_query("student", function() {
frm.set_query('student', function() {
return{
query: "erpnext.education.doctype.program_enrollment.program_enrollment.get_students",
query: 'erpnext.education.doctype.program_enrollment.program_enrollment.get_students',
filters: {
'academic_year': frm.doc.academic_year,
'academic_term': frm.doc.academic_term
@ -49,14 +57,14 @@ frappe.ui.form.on("Program Enrollment", {
frm.events.get_courses(frm);
if (frm.doc.program) {
frappe.call({
method: "erpnext.education.api.get_fee_schedule",
method: 'erpnext.education.api.get_fee_schedule',
args: {
"program": frm.doc.program,
"student_category": frm.doc.student_category
'program': frm.doc.program,
'student_category': frm.doc.student_category
},
callback: function(r) {
if(r.message) {
frm.set_value("fees" ,r.message);
if (r.message) {
frm.set_value('fees' ,r.message);
frm.events.get_courses(frm);
}
}
@ -65,17 +73,17 @@ frappe.ui.form.on("Program Enrollment", {
},
student_category: function() {
frappe.ui.form.trigger("Program Enrollment", "program");
frappe.ui.form.trigger('Program Enrollment', 'program');
},
get_courses: function(frm) {
frm.set_value("courses",[]);
frm.set_value('courses',[]);
frappe.call({
method: "get_courses",
method: 'get_courses',
doc:frm.doc,
callback: function(r) {
if(r.message) {
frm.set_value("courses", r.message);
if (r.message) {
frm.set_value('courses', r.message);
}
}
})
@ -84,10 +92,10 @@ frappe.ui.form.on("Program Enrollment", {
frappe.ui.form.on('Program Enrollment Course', {
courses_add: function(frm){
frm.fields_dict['courses'].grid.get_field('course').get_query = function(doc){
frm.fields_dict['courses'].grid.get_field('course').get_query = function(doc) {
var course_list = [];
if(!doc.__islocal) course_list.push(doc.name);
$.each(doc.courses, function(idx, val){
$.each(doc.courses, function(_idx, val) {
if (val.course) course_list.push(val.course);
});
return { filters: [['Course', 'name', 'not in', course_list]] };

View File

@ -1,725 +1,185 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"actions": [],
"allow_import": 1,
"allow_rename": 0,
"autoname": "EDU-ENR-.YYYY.-.#####",
"beta": 0,
"creation": "2015-12-02 12:58:32.916080",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 0,
"engine": "InnoDB",
"field_order": [
"student",
"student_name",
"student_category",
"student_batch_name",
"school_house",
"column_break_4",
"program",
"academic_year",
"academic_term",
"enrollment_date",
"boarding_student",
"enrolled_courses",
"courses",
"transportation",
"mode_of_transportation",
"column_break_13",
"vehicle_no",
"section_break_7",
"fees",
"amended_from",
"image"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "student",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Student",
"length": 0,
"no_copy": 0,
"options": "Student",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "student.title",
"fieldname": "student_name",
"fieldtype": "Read Only",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Student Name",
"length": 0,
"no_copy": 0,
"options": "",
"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
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "student_category",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Student Category",
"length": 0,
"no_copy": 0,
"options": "Student Category",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"options": "Student Category"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "student_batch_name",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Student Batch",
"length": 0,
"no_copy": 0,
"options": "Student Batch Name",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"options": "Student Batch Name"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "school_house",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "School House",
"length": 0,
"no_copy": 0,
"options": "School House",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"options": "School House"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_4",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "program",
"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": 1,
"label": "Program",
"length": 0,
"no_copy": 0,
"options": "Program",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "academic_year",
"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": 1,
"label": "Academic Year",
"length": 0,
"no_copy": 0,
"options": "Academic Year",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "academic_term",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Academic Term",
"length": 0,
"no_copy": 0,
"options": "Academic Term",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"options": "Academic Term"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Today",
"fieldname": "enrollment_date",
"fieldtype": "Date",
"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": "Enrollment Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"description": "Check this if the Student is residing at the Institute's Hostel.",
"fieldname": "boarding_student",
"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": "Boarding Student",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Boarding Student"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"collapsible_depends_on": "vehicle_no",
"columns": 0,
"fieldname": "transportation",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Transportation",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Transportation"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "mode_of_transportation",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Mode of Transportation",
"length": 0,
"no_copy": 0,
"options": "\nWalking\nInstitute's Bus\nPublic Transport\nSelf-Driving Vehicle\nPick/Drop by Guardian",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"options": "\nWalking\nInstitute's Bus\nPublic Transport\nSelf-Driving Vehicle\nPick/Drop by Guardian"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_13",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "vehicle_no",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Vehicle/Bus Number",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Vehicle/Bus Number"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"columns": 0,
"fieldname": "enrolled_courses",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Enrolled courses",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Enrolled courses"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "courses",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Courses",
"length": 0,
"no_copy": 0,
"options": "Program Enrollment Course",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"options": "Program Enrollment Course"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"columns": 0,
"fieldname": "section_break_7",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Fees",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Fees"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "fees",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Fees",
"length": 0,
"no_copy": 0,
"options": "Program Fee",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": ""
"options": "Program Fee"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Amended From",
"length": 0,
"no_copy": 1,
"options": "Program Enrollment",
"permlevel": 0,
"print_hide": 1,
"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
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "image",
"fieldtype": "Attach Image",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Image",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Image"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_field": "image",
"image_view": 0,
"in_create": 0,
"is_submittable": 1,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2018-11-07 21:13:06.502279",
"links": [],
"modified": "2020-09-15 18:12:11.988565",
"modified_by": "Administrator",
"module": "Education",
"name": "Program Enrollment",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
@ -729,47 +189,30 @@
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Academics User",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 0,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "LMS User",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"restrict_to_domain": "Education",
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "student_name",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
"title_field": "student_name"
}

View File

@ -7,12 +7,15 @@ import frappe
from frappe import msgprint, _
from frappe.model.document import Document
from frappe.desk.reportview import get_match_cond, get_filters_cond
from frappe.utils import comma_and
from frappe.utils import comma_and, get_link_to_form, getdate
import erpnext.www.lms as lms
class ProgramEnrollment(Document):
def validate(self):
self.validate_duplication()
self.validate_academic_year()
if self.academic_term:
self.validate_academic_term()
if not self.student_name:
self.student_name = frappe.db.get_value("Student", self.student, "title")
if not self.courses:
@ -23,11 +26,34 @@ class ProgramEnrollment(Document):
self.make_fee_records()
self.create_course_enrollments()
def validate_academic_year(self):
start_date, end_date = frappe.db.get_value("Academic Year", self.academic_year, ["year_start_date", "year_end_date"])
if self.enrollment_date:
if start_date and getdate(self.enrollment_date) < getdate(start_date):
frappe.throw(_("Enrollment Date cannot be before the Start Date of the Academic Year {0}").format(
get_link_to_form("Academic Year", self.academic_year)))
if end_date and getdate(self.enrollment_date) > getdate(end_date):
frappe.throw(_("Enrollment Date cannot be after the End Date of the Academic Term {0}").format(
get_link_to_form("Academic Year", self.academic_year)))
def validate_academic_term(self):
start_date, end_date = frappe.db.get_value("Academic Term", self.academic_term, ["term_start_date", "term_end_date"])
if self.enrollment_date:
if start_date and getdate(self.enrollment_date) < getdate(start_date):
frappe.throw(_("Enrollment Date cannot be before the Start Date of the Academic Term {0}").format(
get_link_to_form("Academic Term", self.academic_term)))
if end_date and getdate(self.enrollment_date) > getdate(end_date):
frappe.throw(_("Enrollment Date cannot be after the End Date of the Academic Term {0}").format(
get_link_to_form("Academic Term", self.academic_term)))
def validate_duplication(self):
enrollment = frappe.get_all("Program Enrollment", filters={
"student": self.student,
"program": self.program,
"academic_year": self.academic_year,
"academic_term": self.academic_term,
"docstatus": ("<", 2),
"name": ("!=", self.name)
})
@ -70,10 +96,9 @@ class ProgramEnrollment(Document):
def create_course_enrollments(self):
student = frappe.get_doc("Student", self.student)
program = frappe.get_doc("Program", self.program)
course_list = [course.course for course in program.courses]
course_list = [course.course for course in self.courses]
for course_name in course_list:
student.enroll_in_course(course_name=course_name, program_enrollment=self.name)
student.enroll_in_course(course_name=course_name, program_enrollment=self.name, enrollment_date=self.enrollment_date)
def get_all_course_enrollments(self):
course_enrollment_names = frappe.get_list("Course Enrollment", filters={'program_enrollment': self.name})

View File

@ -24,3 +24,12 @@ class TestProgramEnrollment(unittest.TestCase):
self.assertTrue("_Test Course 1" in course_enrollments.keys())
self.assertTrue("_Test Course 2" in course_enrollments.keys())
frappe.db.rollback()
def tearDown(self):
for entry in frappe.db.get_all("Course Enrollment"):
frappe.delete_doc("Course Enrollment", entry.name)
for entry in frappe.db.get_all("Program Enrollment"):
doc = frappe.get_doc("Program Enrollment", entry.name)
doc.cancel()
doc.delete()

View File

@ -147,7 +147,7 @@ class Student(Document):
enrollment.save(ignore_permissions=True)
except frappe.exceptions.ValidationError:
enrollment_name = frappe.get_list("Course Enrollment", filters={"student": self.name, "course": course_name, "program_enrollment": program_enrollment})[0].name
return frappe.get_doc("Program Enrollment", enrollment_name)
return frappe.get_doc("Course Enrollment", enrollment_name)
else:
return enrollment

View File

@ -42,6 +42,16 @@ class TestStudent(unittest.TestCase):
self.assertTrue("_Test Course 2" in course_enrollments.keys())
frappe.db.rollback()
def tearDown(self):
for entry in frappe.db.get_all("Course Enrollment"):
frappe.delete_doc("Course Enrollment", entry.name)
for entry in frappe.db.get_all("Program Enrollment"):
doc = frappe.get_doc("Program Enrollment", entry.name)
doc.cancel()
doc.delete()
def create_student(student_dict):
student = get_student(student_dict['email'])
if not student:

View File

@ -51,12 +51,14 @@
"fieldname": "admission_start_date",
"fieldtype": "Date",
"label": "Admission Start Date",
"mandatory_depends_on": "enable_admission_application",
"no_copy": 1
},
{
"fieldname": "admission_end_date",
"fieldtype": "Date",
"label": "Admission End Date",
"mandatory_depends_on": "enable_admission_application",
"no_copy": 1
},
{
@ -83,6 +85,7 @@
},
{
"default": "0",
"depends_on": "published",
"fieldname": "enable_admission_application",
"fieldtype": "Check",
"label": "Enable Admission Application"
@ -91,7 +94,7 @@
"has_web_view": 1,
"is_published_field": "published",
"links": [],
"modified": "2020-06-15 20:18:38.591626",
"modified": "2020-09-18 00:14:54.615321",
"modified_by": "Administrator",
"module": "Education",
"name": "Student Admission",

View File

@ -19,6 +19,9 @@ class StudentAdmission(WebsiteGenerator):
if not self.route: #pylint: disable=E0203
self.route = "admissions/" + "-".join(self.title.split(" "))
if self.enable_admission_application and not self.program_details:
frappe.throw(_("Please add programs to enable admission application."))
def get_context(self, context):
context.no_cache = 1
context.show_sidebar = True

View File

@ -43,31 +43,35 @@
<thead>
<tr class="active">
<th style="width: 90px">Program/Std.</th>
<th style="width: 170px">Minumum Age</th>
<th style="width: 170px">Maximum Age</th>
<th style="width: 180px">Description</th>
<th style="width: 100px">Minumum Age</th>
<th style="width: 100px">Maximum Age</th>
<th style="width: 100px">Application Fee</th>
{%- if doc.enable_admission_application and frappe.utils.getdate(doc.admission_start_date) <= today -%}
<th style="width: 120px"></th>
{% endif %}
</tr>
</thead>
<tbody>
{% for row in program_details %}
<tr>
<td>{{ row.program }}</td>
<td><div class="text-muted">{{ row.description if row.description else '' }}</div></td>
<td>{{ row.min_age }}</td>
<td>{{ row.max_age }}</td>
<td>{{ row.application_fee }}</td>
{%- if doc.enable_admission_application and frappe.utils.getdate(doc.admission_start_date) <= today -%}
<td>
<a class='btn btn-sm btn-primary' href='/student-applicant?new=1&student_admission={{doc.name}}&program={{row.program}}&academic_year={{academic_year}}'>
{{ _("Apply Now") }}
</a>
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{%- if doc.enable_admission_application -%}
<br>
<p>
<a class='btn btn-primary'
href='/student-applicant?new=1&student_admission={{doc.name}}'>
{{ _("Apply Now") }}</a>
</p>
{% endif %}
{% endblock %}

View File

@ -1,8 +1,8 @@
<div class="web-list-item">
<div class="web-list-item transaction-list-item">
{% set today = frappe.utils.getdate(frappe.utils.nowdate()) %}
<a href = "{{ doc.route }}/">
<a href = "{{ doc.route }}/" class="no-underline">
<div class="row">
<div class="col-sm-6 text-left small bold" style="margin-top: -3px;"">
<div class="col-sm-4 bold">
<span class="indicator
{% if frappe.utils.getdate(doc.admission_end_date) == today %}
red
@ -15,6 +15,14 @@
{% endif %}
">{{ doc.title }}</span>
</div>
<div class="col-sm-2 small">
<span class="text-muted">
Academic Year
</span>
<div class="text-muted bold">
{{ doc.academic_year }}
</div>
</div>
<div class="col-sm-3 small">
<span class="text-muted">
Starts on
@ -27,7 +35,7 @@
<span class="text-muted">
Ends on
</span>
<div class="bold">
<div class=" text-muted bold">
{{ frappe.format_date(doc.admission_end_date) }}
</div>
</div>

View File

@ -8,6 +8,7 @@
"program",
"min_age",
"max_age",
"description",
"column_break_4",
"application_fee",
"applicant_naming_series"
@ -18,52 +19,47 @@
"fieldtype": "Link",
"in_list_view": 1,
"label": "Program",
"options": "Program",
"show_days": 1,
"show_seconds": 1
"options": "Program"
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
"fieldtype": "Column Break"
},
{
"fieldname": "application_fee",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Application Fee",
"show_days": 1,
"show_seconds": 1
"label": "Application Fee"
},
{
"fieldname": "applicant_naming_series",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Naming Series (for Student Applicant)",
"show_days": 1,
"show_seconds": 1
"label": "Naming Series (for Student Applicant)"
},
{
"fieldname": "min_age",
"fieldtype": "Int",
"in_list_view": 1,
"label": "Minimum Age",
"show_days": 1,
"show_seconds": 1
"label": "Minimum Age"
},
{
"fieldname": "max_age",
"fieldtype": "Int",
"in_list_view": 1,
"label": "Maximum Age",
"show_days": 1,
"show_seconds": 1
"label": "Maximum Age"
},
{
"fetch_from": "program.description",
"fetch_if_empty": 1,
"fieldname": "description",
"fieldtype": "Small Text",
"label": "Description"
}
],
"istable": 1,
"links": [],
"modified": "2020-06-10 23:06:30.037404",
"modified": "2020-10-05 13:03:42.005985",
"modified_by": "Administrator",
"module": "Education",
"name": "Student Admission Program",

View File

@ -168,6 +168,7 @@
"fieldname": "student_email_id",
"fieldtype": "Data",
"label": "Student Email Address",
"options": "Email",
"unique": 1
},
{
@ -261,7 +262,7 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
"modified": "2020-09-07 19:31:30.063563",
"modified": "2020-10-05 13:59:45.631647",
"modified_by": "Administrator",
"module": "Education",
"name": "Student Applicant",

View File

@ -70,19 +70,7 @@
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "image",
"fieldtype": "Data",
"hidden": 0,
"label": "Image",
"max_length": 0,
"max_value": 0,
"read_only": 0,
"reqd": 0,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"allow_read_on_all_link_options": 1,
"fieldname": "program",
"fieldtype": "Link",
"hidden": 0,
@ -95,7 +83,7 @@
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"allow_read_on_all_link_options": 1,
"fieldname": "academic_year",
"fieldtype": "Link",
"hidden": 0,
@ -107,6 +95,19 @@
"reqd": 0,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 1,
"fieldname": "academic_term",
"fieldtype": "Link",
"hidden": 0,
"label": "Academic Term",
"max_length": 0,
"max_value": 0,
"options": "Academic Term",
"read_only": 0,
"reqd": 0,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "date_of_birth",
@ -119,6 +120,19 @@
"reqd": 0,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 1,
"fieldname": "gender",
"fieldtype": "Link",
"hidden": 0,
"label": "Gender",
"max_length": 0,
"max_value": 0,
"options": "Gender",
"read_only": 0,
"reqd": 0,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "blood_group",
@ -141,7 +155,7 @@
"max_length": 0,
"max_value": 0,
"read_only": 0,
"reqd": 0,
"reqd": 1,
"show_in_filter": 0
},
{
@ -206,19 +220,6 @@
"reqd": 0,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "guardians",
"fieldtype": "Table",
"hidden": 0,
"label": "Guardians",
"max_length": 0,
"max_value": 0,
"options": "Student Guardian",
"read_only": 0,
"reqd": 0,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "siblings",

View File

@ -0,0 +1,30 @@
{
"cards": [
{
"hidden": 0,
"label": "Integrations Settings",
"links": "[\n\t{\n\t \"type\": \"doctype\",\n\t\t\"name\": \"Woocommerce Settings\"\n\t},\n\t{\n\t \"type\": \"doctype\",\n\t\t\"name\": \"Shopify Settings\",\n\t\t\"description\": \"Connect Shopify with ERPNext\"\n\t},\n\t{\n\t \"type\": \"doctype\",\n\t\t\"name\": \"Amazon MWS Settings\",\n\t\t\"description\": \"Connect Amazon with ERPNext\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Plaid Settings\",\n\t\t\"description\": \"Connect your bank accounts to ERPNext\"\n\t},\n {\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Exotel Settings\",\n\t\t\"description\": \"Connect your Exotel Account to ERPNext and track call logs\"\n }\n]"
}
],
"category": "Modules",
"charts": [],
"creation": "2020-07-31 10:38:54.021237",
"developer_mode_only": 0,
"disable_user_customization": 0,
"docstatus": 0,
"doctype": "Desk Page",
"extends": "Settings",
"extends_another_page": 1,
"hide_custom": 0,
"idx": 0,
"is_standard": 1,
"label": "ERPNext Integrations Settings",
"modified": "2020-07-31 10:44:39.374297",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "ERPNext Integrations Settings",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 0,
"shortcuts": []
}

View File

@ -21,11 +21,16 @@ web_include_js = "assets/js/erpnext-web.min.js"
web_include_css = "assets/css/erpnext-web.css"
doctype_js = {
"Address": "public/js/address.js",
"Communication": "public/js/communication.js",
"Event": "public/js/event.js",
"Newsletter": "public/js/newsletter.js"
}
override_doctype_class = {
'Address': 'erpnext.accounts.custom.address.ERPNextAddress'
}
welcome_email = "erpnext.setup.utils.welcome_email"
# setup wizard

View File

@ -6,18 +6,28 @@ from __future__ import unicode_literals
import frappe
import unittest
from frappe.utils import nowdate,flt, cstr,random_string
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.vehicle_log.vehicle_log import make_expense_claim
class TestVehicleLog(unittest.TestCase):
def setUp(self):
employee_id = frappe.db.sql("""select name from `tabEmployee` where name='testdriver@example.com'""")
self.employee_id = employee_id[0][0] if employee_id else None
if not self.employee_id:
self.employee_id = make_employee("testdriver@example.com", company="_Test Company")
self.license_plate = get_vehicle(self.employee_id)
def tearDown(self):
frappe.delete_doc("Vehicle", self.license_plate, force=1)
frappe.delete_doc("Employee", self.employee_id, force=1)
def test_make_vehicle_log_and_syncing_of_odometer_value(self):
employee_id = frappe.db.sql("""select name from `tabEmployee` where status='Active' order by modified desc limit 1""")
employee_id = employee_id[0][0] if employee_id else None
license_plate = get_vehicle(employee_id)
vehicle_log = frappe.get_doc({
"doctype": "Vehicle Log",
"license_plate": cstr(license_plate),
"employee":employee_id,
"license_plate": cstr(self.license_plate),
"employee": self.employee_id,
"date":frappe.utils.nowdate(),
"odometer":5010,
"fuel_qty":frappe.utils.flt(50),
@ -27,7 +37,7 @@ class TestVehicleLog(unittest.TestCase):
vehicle_log.submit()
#checking value of vehicle odometer value on submit.
vehicle = frappe.get_doc("Vehicle", license_plate)
vehicle = frappe.get_doc("Vehicle", self.license_plate)
self.assertEqual(vehicle.last_odometer, vehicle_log.odometer)
#checking value vehicle odometer on vehicle log cancellation.
@ -40,6 +50,28 @@ class TestVehicleLog(unittest.TestCase):
self.assertEqual(vehicle.last_odometer, current_odometer - distance_travelled)
vehicle_log.delete()
def test_vehicle_log_fuel_expense(self):
vehicle_log = frappe.get_doc({
"doctype": "Vehicle Log",
"license_plate": cstr(self.license_plate),
"employee": self.employee_id,
"date": frappe.utils.nowdate(),
"odometer":5010,
"fuel_qty":frappe.utils.flt(50),
"price": frappe.utils.flt(500)
})
vehicle_log.save()
vehicle_log.submit()
expense_claim = make_expense_claim(vehicle_log.name)
fuel_expense = expense_claim.expenses[0].amount
self.assertEqual(fuel_expense, 50*500)
vehicle_log.cancel()
frappe.delete_doc("Expense Claim", expense_claim.name)
frappe.delete_doc("Vehicle Log", vehicle_log.name)
def get_vehicle(employee_id):
license_plate=random_string(10).upper()

View File

@ -32,7 +32,7 @@ def make_expense_claim(docname):
vehicle_log = frappe.get_doc("Vehicle Log", docname)
service_expense = sum([flt(d.expense_amount) for d in vehicle_log.service_detail])
claim_amount = service_expense + flt(vehicle_log.price)
claim_amount = service_expense + (flt(vehicle_log.price) * flt(vehicle_log.fuel_qty) or 1)
if not claim_amount:
frappe.throw(_("No additional expenses has been added"))

View File

@ -207,7 +207,6 @@ class TestBOM(unittest.TestCase):
supplied_items = sorted([d.rm_item_code for d in po.supplied_items])
self.assertEquals(bom_items, supplied_items)
def get_default_bom(item_code="_Test FG Item 2"):
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})

View File

@ -322,12 +322,13 @@ class ProductionPlan(Document):
work_orders = []
bom_data = {}
get_sub_assembly_items(item.get("bom_no"), bom_data)
get_sub_assembly_items(item.get("bom_no"), bom_data, item.get("qty"))
for key, data in bom_data.items():
data.update({
'qty': data.get("stock_qty") * item.get("qty"),
'qty': data.get("stock_qty"),
'production_plan': self.name,
'use_multi_level_bom': item.get("use_multi_level_bom"),
'company': self.company,
'fg_warehouse': item.get("fg_warehouse"),
'update_consumed_material_cost_in_project': 0
@ -781,7 +782,7 @@ def get_item_data(item_code):
# "description": item_details.get("description")
}
def get_sub_assembly_items(bom_no, bom_data):
def get_sub_assembly_items(bom_no, bom_data, to_produce_qty):
data = get_children('BOM', parent = bom_no)
for d in data:
if d.expandable:
@ -798,6 +799,6 @@ def get_sub_assembly_items(bom_no, bom_data):
})
bom_item = bom_data.get(key)
bom_item["stock_qty"] += d.stock_qty / d.parent_bom_qty
bom_item["stock_qty"] += (d.stock_qty / d.parent_bom_qty) * flt(to_produce_qty)
get_sub_assembly_items(bom_item.get("bom_no"), bom_data)
get_sub_assembly_items(bom_item.get("bom_no"), bom_data, bom_item["stock_qty"])

View File

@ -158,6 +158,46 @@ class TestProductionPlan(unittest.TestCase):
self.assertTrue(mr.material_request_type, 'Customer Provided')
self.assertTrue(mr.customer, '_Test Customer')
def test_production_plan_with_multi_level_bom(self):
#|Item Code | Qty |
#|Test BOM 1 | 1 |
#| Test BOM 2 | 2 |
#| Test BOM 3 | 3 |
for item_code in ["Test BOM 1", "Test BOM 2", "Test BOM 3", "Test RM BOM 1"]:
create_item(item_code, is_stock_item=1)
# created bom upto 3 level
if not frappe.db.get_value('BOM', {'item': "Test BOM 3"}):
make_bom(item = "Test BOM 3", raw_materials = ["Test RM BOM 1"], rm_qty=3)
if not frappe.db.get_value('BOM', {'item': "Test BOM 2"}):
make_bom(item = "Test BOM 2", raw_materials = ["Test BOM 3"], rm_qty=3)
if not frappe.db.get_value('BOM', {'item': "Test BOM 1"}):
make_bom(item = "Test BOM 1", raw_materials = ["Test BOM 2"], rm_qty=2)
item_code = "Test BOM 1"
pln = frappe.new_doc('Production Plan')
pln.company = "_Test Company"
pln.append("po_items", {
"item_code": item_code,
"bom_no": frappe.db.get_value('BOM', {'item': "Test BOM 1"}),
"planned_qty": 3,
"make_work_order_for_sub_assembly_items": 1
})
pln.submit()
pln.make_work_order()
#last level sub-assembly work order produce qty
to_produce_qty = frappe.db.get_value("Work Order",
{"production_plan": pln.name, "production_item": "Test BOM 3"}, "qty")
self.assertEqual(to_produce_qty, 18.0)
pln.cancel()
frappe.delete_doc("Production Plan", pln.name)
def create_production_plan(**args):
args = frappe._dict(args)
@ -205,12 +245,16 @@ def make_bom(**args):
bom.append('items', {
'item_code': item,
'qty': 1,
'qty': args.rm_qty or 1.0,
'uom': item_doc.stock_uom,
'stock_uom': item_doc.stock_uom,
'rate': item_doc.valuation_rate or args.rate,
})
bom.insert(ignore_permissions=True)
bom.submit()
if not args.do_not_save:
bom.insert(ignore_permissions=True)
if not args.do_not_submit:
bom.submit()
return bom

View File

@ -407,6 +407,49 @@ class TestWorkOrder(unittest.TestCase):
ste1 = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 1))
self.assertEqual(len(ste1.items), 3)
def test_operation_time_with_batch_size(self):
fg_item = "Test Batch Size Item For BOM"
rm1 = "Test Batch Size Item RM 1 For BOM"
for item in ["Test Batch Size Item For BOM", "Test Batch Size Item RM 1 For BOM"]:
make_item(item, {
"include_item_in_manufacturing": 1,
"is_stock_item": 1
})
bom_name = frappe.db.get_value("BOM",
{"item": fg_item, "is_active": 1, "with_operations": 1}, "name")
if not bom_name:
bom = make_bom(item=fg_item, rate=1000, raw_materials = [rm1], do_not_save=True)
bom.with_operations = 1
bom.append("operations", {
"operation": "_Test Operation 1",
"workstation": "_Test Workstation 1",
"description": "Test Data",
"operating_cost": 100,
"time_in_mins": 40,
"batch_size": 5
})
bom.save()
bom.submit()
bom_name = bom.name
work_order = make_wo_order_test_record(item=fg_item,
planned_start_date=now(), qty=1, do_not_save=True)
work_order.set_work_order_operations()
work_order.save()
self.assertEqual(work_order.operations[0].time_in_mins, 8.0)
work_order1 = make_wo_order_test_record(item=fg_item,
planned_start_date=now(), qty=5, do_not_save=True)
work_order1.set_work_order_operations()
work_order1.save()
self.assertEqual(work_order1.operations[0].time_in_mins, 40.0)
def get_scrap_item_details(bom_no):
scrap_items = {}
for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item`

View File

@ -636,7 +636,7 @@ erpnext.work_order = {
description: __('Max: {0}', [max]),
default: max
}, data => {
max += (max * (frm.doc.__onload.overproduction_percentage || 0.0)) / 100;
max += (frm.doc.qty * (frm.doc.__onload.overproduction_percentage || 0.0)) / 100;
if (data.qty > max) {
frappe.msgprint(__('Quantity must not be more than {0}', [max]));

View File

@ -403,7 +403,7 @@ class WorkOrder(Document):
bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity")
for d in self.get("operations"):
d.time_in_mins = flt(d.time_in_mins) / flt(bom_qty) * math.ceil(flt(self.qty) / flt(d.batch_size))
d.time_in_mins = flt(d.time_in_mins) / flt(bom_qty) * (flt(self.qty) / flt(d.batch_size))
self.calculate_operating_cost()

View File

@ -347,8 +347,7 @@ class TestSalarySlip(unittest.TestCase):
# create additional salary of 150000
frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""", (employee))
data["additional-1"] = create_additional_salary(employee, payroll_period, 50000)
data["additional-2"] = create_additional_salary(employee, payroll_period, 100000)
data["additional-1"] = create_additional_salary(employee, payroll_period, 150000)
data["deducted_dates"] = create_salary_slips_for_payroll_period(employee,
salary_structure.name, payroll_period)

View File

@ -136,6 +136,7 @@ def get_timesheet_details(filters, timesheet_list):
return timesheet_details_map
def get_billable_and_total_duration(activity, start_time, end_time):
precision = frappe.get_precision("Timesheet Detail", "hours")
activity_duration = time_diff_in_hours(end_time, start_time)
billing_duration = 0.0
if activity.billable:
@ -143,4 +144,4 @@ def get_billable_and_total_duration(activity, start_time, end_time):
if activity_duration != activity.billing_hours:
billing_duration = activity_duration * activity.billing_hours / activity.hours
return flt(activity_duration, 2), flt(billing_duration, 2)
return flt(activity_duration, precision), flt(billing_duration, precision)

View File

@ -0,0 +1,25 @@
// Copyright (c) 2016, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on("Address", {
is_your_company_address: function(frm) {
frm.clear_table('links');
if(frm.doc.is_your_company_address) {
frm.add_child('links', {
link_doctype: 'Company',
link_name: frappe.defaults.get_user_default('Company')
});
frm.set_query('link_doctype', 'links', () => {
return {
filters: {
name: 'Company'
}
};
});
frm.refresh_field('links');
}
else {
frm.trigger('refresh');
}
}
});

View File

@ -309,7 +309,6 @@ erpnext.setup.fiscal_years = {
"Hong Kong": ["04-01", "03-31"],
"India": ["04-01", "03-31"],
"Iran": ["06-23", "06-22"],
"Italy": ["07-01", "06-30"],
"Myanmar": ["04-01", "03-31"],
"New Zealand": ["04-01", "03-31"],
"Pakistan": ["07-01", "06-30"],

View File

@ -703,9 +703,13 @@ erpnext.utils.map_current_doc = function(opts) {
}
frappe.form.link_formatters['Item'] = function(value, doc) {
if(doc && doc.item_name && doc.item_name !== value) {
return value? value + ': ' + doc.item_name: doc.item_name;
if (doc && value && doc.item_name && doc.item_name !== value) {
return value + ': ' + doc.item_name;
} else if (!value && doc.doctype && doc.item_name) {
// format blank value in child table
return doc.item_name;
} else {
// if value is blank in report view or item code and name are the same, return as is
return value;
}
}

View File

@ -277,7 +277,7 @@ erpnext.utils.validate_mandatory = function(frm, label, value, trigger_on) {
erpnext.utils.get_shipping_address = function(frm, callback){
if (frm.doc.company) {
frappe.call({
method: "frappe.contacts.doctype.address.address.get_shipping_address",
method: "erpnext.accounts.custom.address.get_shipping_address",
args: {
company: frm.doc.company,
address: frm.doc.shipping_address

View File

@ -10,5 +10,13 @@ frappe.ui.form.on('Quality Procedure', {
}
};
});
frm.set_query('parent_quality_procedure', function(){
return {
filters: {
is_group: 1
}
};
});
}
});

View File

@ -21,8 +21,7 @@
"fieldname": "parent_quality_procedure",
"fieldtype": "Link",
"label": "Parent Procedure",
"options": "Quality Procedure",
"read_only": 1
"options": "Quality Procedure"
},
{
"default": "0",
@ -73,7 +72,7 @@
],
"is_tree": 1,
"links": [],
"modified": "2020-06-17 17:25:03.434953",
"modified": "2020-10-13 11:46:07.744194",
"modified_by": "Administrator",
"module": "Quality Management",
"name": "Quality Procedure",

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe.utils.nestedset import NestedSet
from frappe.utils.nestedset import NestedSet, rebuild_tree
from frappe import _
class QualityProcedure(NestedSet):
@ -42,6 +42,8 @@ class QualityProcedure(NestedSet):
doc.save(ignore_permissions=True)
def set_parent(self):
rebuild_tree('Quality Procedure', 'parent_quality_procedure')
for process in self.processes:
# Set parent for only those children who don't have a parent
parent_quality_procedure = frappe.db.get_value("Quality Procedure", process.procedure, "parent_quality_procedure")

View File

@ -0,0 +1,4 @@
{% if address_line1 %}{{ address_line1 }}<br>{% endif -%}
{% if address_line2 %}{{ address_line2 }}<br>{% endif -%}
{% if pincode %}L-{{ pincode }}{% endif -%}{% if city %} {{ city }}{% endif %}<br>
{% if country %}{{ country | upper }}{% endif %}

View File

@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe
import json
import frappe.utils
from frappe.utils import cstr, flt, getdate, cint, nowdate, add_days, get_link_to_form
from frappe.utils import cstr, flt, getdate, cint, nowdate, add_days, get_link_to_form, strip_html
from frappe import _
from six import string_types
from frappe.model.utils import get_fetch_values
@ -993,15 +993,20 @@ def make_raw_material_request(items, company, sales_order, project=None):
))
for item in raw_materials:
item_doc = frappe.get_cached_doc('Item', item.get('item_code'))
schedule_date = add_days(nowdate(), cint(item_doc.lead_time_days))
material_request.append('items', {
'item_code': item.get('item_code'),
'qty': item.get('quantity'),
'schedule_date': schedule_date,
'warehouse': item.get('warehouse'),
'sales_order': sales_order,
'project': project
row = material_request.append('items', {
'item_code': item.get('item_code'),
'qty': item.get('quantity'),
'schedule_date': schedule_date,
'warehouse': item.get('warehouse'),
'sales_order': sales_order,
'project': project
})
if not (strip_html(item.get("description")) and strip_html(item_doc.description)):
row.description = item_doc.item_name or item.get('item_code')
material_request.insert()
material_request.flags.ignore_permissions = 1
material_request.run_method("set_missing_values")

View File

@ -506,6 +506,95 @@ class TestSalesOrder(unittest.TestCase):
so.reload()
self.assertEqual(so.packed_items[0].qty, 8)
def test_update_child_with_tax_template(self):
"""
Test Action: Create a SO with one item having its tax account head already in the SO.
Add the same item + new item with tax template via Update Items.
Expected result: First Item's tax row is updated. New tax row is added for second Item.
"""
if not frappe.db.exists("Item", "Test Item with Tax"):
make_item("Test Item with Tax", {
'is_stock_item': 1,
})
if not frappe.db.exists("Item Tax Template", {"title": 'Test Update Items Template'}):
frappe.get_doc({
'doctype': 'Item Tax Template',
'title': 'Test Update Items Template',
'company': '_Test Company',
'taxes': [
{
'tax_type': "_Test Account Service Tax - _TC",
'tax_rate': 10,
}
]
}).insert()
new_item_with_tax = frappe.get_doc("Item", "Test Item with Tax")
new_item_with_tax.append("taxes", {
"item_tax_template": "Test Update Items Template",
"valid_from": nowdate()
})
new_item_with_tax.save()
tax_template = "_Test Account Excise Duty @ 10"
item = "_Test Item Home Desktop 100"
if not frappe.db.exists("Item Tax", {"parent":item, "item_tax_template":tax_template}):
item_doc = frappe.get_doc("Item", item)
item_doc.append("taxes", {
"item_tax_template": tax_template,
"valid_from": nowdate()
})
item_doc.save()
else:
# update valid from
frappe.db.sql("""UPDATE `tabItem Tax` set valid_from = CURDATE()
where parent = %(item)s and item_tax_template = %(tax)s""",
{"item": item, "tax": tax_template})
so = make_sales_order(item_code=item, qty=1, do_not_save=1)
so.append("taxes", {
"account_head": "_Test Account Excise Duty - _TC",
"charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC",
"description": "Excise Duty",
"doctype": "Sales Taxes and Charges",
"rate": 10
})
so.insert()
so.submit()
self.assertEqual(so.taxes[0].tax_amount, 10)
self.assertEqual(so.taxes[0].total, 110)
old_stock_settings_value = frappe.db.get_single_value("Stock Settings", "default_warehouse")
frappe.db.set_value("Stock Settings", None, "default_warehouse", "_Test Warehouse - _TC")
items = json.dumps([
{'item_code' : item, 'rate' : 100, 'qty' : 1, 'docname': so.items[0].name},
{'item_code' : item, 'rate' : 200, 'qty' : 1}, # added item whose tax account head already exists in PO
{'item_code' : new_item_with_tax.name, 'rate' : 100, 'qty' : 1} # added item whose tax account head is missing in PO
])
update_child_qty_rate('Sales Order', items, so.name)
so.reload()
self.assertEqual(so.taxes[0].tax_amount, 40)
self.assertEqual(so.taxes[0].total, 440)
self.assertEqual(so.taxes[1].account_head, "_Test Account Service Tax - _TC")
self.assertEqual(so.taxes[1].tax_amount, 40)
self.assertEqual(so.taxes[1].total, 480)
# teardown
frappe.db.sql("""UPDATE `tabItem Tax` set valid_from = NULL
where parent = %(item)s and item_tax_template = %(tax)s""", {"item": item, "tax": tax_template})
so.cancel()
so.delete()
new_item_with_tax.delete()
frappe.get_doc("Item Tax Template", "Test Update Items Template").delete()
frappe.db.set_value("Stock Settings", None, "default_warehouse", old_stock_settings_value)
def test_warehouse_user(self):
frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 1 - _TC", "test@example.com")
frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", "test2@example.com")

View File

@ -43,7 +43,7 @@
{
"hidden": 0,
"label": "Data Import and Settings",
"links": "[\n {\n \"description\": \"Import Data from CSV / Excel files.\",\n \"icon\": \"octicon octicon-cloud-upload\",\n \"label\": \"Import Data\",\n \"name\": \"Data Import\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Import Chart Of Accounts from CSV / Excel files\",\n \"labe\": \"Chart Of Accounts Importer\",\n \"label\": \"Chart of Accounts Importer\",\n \"name\": \"Chart of Accounts Importer\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Letter Heads for print templates.\",\n \"label\": \"Letter Head\",\n \"name\": \"Letter Head\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add / Manage Email Accounts.\",\n \"label\": \"Email Account\",\n \"name\": \"Email Account\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]"
"links": "[\n {\n \"description\": \"Import Data from CSV / Excel files.\",\n \"icon\": \"octicon octicon-cloud-upload\",\n \"label\": \"Import Data\",\n \"name\": \"Data Import\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Import Chart of Accounts from CSV / Excel files\",\n \"label\": \"Chart of Accounts Importer\",\n \"label\": \"Chart of Accounts Importer\",\n \"name\": \"Chart of Accounts Importer\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Letter Heads for print templates.\",\n \"label\": \"Letter Head\",\n \"name\": \"Letter Head\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add / Manage Email Accounts.\",\n \"label\": \"Email Account\",\n \"name\": \"Email Account\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]"
}
],
"category": "Modules",

View File

@ -261,14 +261,14 @@
{
"fieldname": "create_chart_of_accounts_based_on",
"fieldtype": "Select",
"label": "Create Chart Of Accounts Based On",
"label": "Create Chart of Accounts Based on",
"options": "\nStandard Template\nExisting Company"
},
{
"depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Standard Template\"",
"fieldname": "chart_of_accounts",
"fieldtype": "Select",
"label": "Chart Of Accounts Template",
"label": "Chart of Accounts Template",
"no_copy": 1
},
{

View File

@ -60,14 +60,10 @@
},
"Australia": {
"Australia GST1": {
"Australia GST": {
"account_name": "GST 10%",
"tax_rate": 10.00,
"default": 1
},
"Australia GST 2%": {
"account_name": "GST 2%",
"tax_rate": 2
}
},
@ -648,9 +644,18 @@
},
"Italy": {
"Italy Tax": {
"account_name": "VAT",
"tax_rate": 22.00
"Italy VAT 22%": {
"account_name": "IVA 22%",
"tax_rate": 22.00,
"default": 1
},
"Italy VAT 10%":{
"account_name": "IVA 10%",
"tax_rate": 10.00
},
"Italy VAT 4%":{
"account_name": "IVA 4%",
"tax_rate": 4.00
}
},

View File

@ -577,8 +577,9 @@ class Item(WebsiteGenerator):
# if barcode is getting updated , the row name has to reset.
# Delete previous old row doc and re-enter row as if new to reset name in db.
item_barcode.set("__islocal", True)
item_barcode_entry_name = item_barcode.name
item_barcode.name = None
frappe.delete_doc("Item Barcode", item_barcode.name)
frappe.delete_doc("Item Barcode", item_barcode_entry_name)
def validate_warehouse_for_reorder(self):
'''Validate Reorder level table for duplicate and conditional mandatory'''

View File

@ -471,7 +471,7 @@ class TestItem(unittest.TestCase):
item_doc = frappe.get_doc('Item', item_code)
new_barcode = item_doc.append('barcodes')
new_barcode.update(barcode_properties_list[0])
self.assertRaises(frappe.DuplicateEntryError, item_doc.save)
self.assertRaises(frappe.UniqueValidationError, item_doc.save)
# Add invalid barcode - should cause InvalidBarcode
item_doc = frappe.get_doc('Item', item_code)

View File

@ -296,7 +296,7 @@ class PurchaseReceipt(BuyingController):
if self.is_return or flt(d.item_tax_amount):
loss_account = expenses_included_in_valuation
else:
loss_account = stock_rbnb
loss_account = self.get_company_default("default_expense_account")
gl_entries.append(self.get_gl_dict({
"account": loss_account,

View File

@ -6,7 +6,7 @@ import unittest
import json
import frappe, erpnext
import frappe.defaults
from frappe.utils import cint, flt, cstr, today, random_string
from frappe.utils import cint, flt, cstr, today, random_string, add_days
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice
from erpnext.stock.doctype.item.test_item import create_item
from erpnext import set_perpetual_inventory
@ -180,18 +180,15 @@ class TestPurchaseReceipt(unittest.TestCase):
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
#stock raw materials in a warehouse before transfer
make_stock_entry(target="_Test Warehouse - _TC",
item_code="_Test Item Home Desktop 100", qty=1, basic_rate=100)
make_stock_entry(target="_Test Warehouse - _TC",
item_code = "Test Extra Item 1", qty=1, basic_rate=100)
make_stock_entry(target="_Test Warehouse - _TC",
item_code = "_Test Item", qty=1, basic_rate=100)
item_code = "_Test FG Item", qty=1, basic_rate=100)
rm_items = [
{
"item_code": item_code,
"rm_item_code": po.supplied_items[0].rm_item_code,
"item_name": "_Test Item",
"item_name": "_Test FG Item",
"qty": po.supplied_items[0].required_qty,
"warehouse": "_Test Warehouse - _TC",
"stock_uom": "Nos"
@ -203,14 +200,6 @@ class TestPurchaseReceipt(unittest.TestCase):
"qty": po.supplied_items[1].required_qty,
"warehouse": "_Test Warehouse - _TC",
"stock_uom": "Nos"
},
{
"item_code": item_code,
"rm_item_code": po.supplied_items[2].rm_item_code,
"item_name": "_Test Item Home Desktop 100",
"qty": po.supplied_items[2].required_qty,
"warehouse": "_Test Warehouse - _TC",
"stock_uom": "Nos"
}
]
rm_item_string = json.dumps(rm_items)
@ -676,6 +665,59 @@ class TestPurchaseReceipt(unittest.TestCase):
warehouse.account = ''
warehouse.save()
def test_backdated_purchase_receipt(self):
# make purchase receipt for default company
make_purchase_receipt(company="_Test Company 4", warehouse="Stores - _TC4")
# try to make another backdated PR
posting_date = add_days(today(), -1)
pr = make_purchase_receipt(company="_Test Company 4", warehouse="Stores - _TC4",
do_not_submit=True)
pr.set_posting_time = 1
pr.posting_date = posting_date
pr.save()
self.assertRaises(frappe.ValidationError, pr.submit)
# make purchase receipt for other company backdated
pr = make_purchase_receipt(company="_Test Company 5", warehouse="Stores - _TC5",
do_not_submit=True)
pr.set_posting_time = 1
pr.posting_date = posting_date
pr.submit()
# Allowed to submit for other company's PR
self.assertEqual(pr.docstatus, 1)
def test_backdated_purchase_receipt_for_same_company_different_warehouse(self):
# make purchase receipt for default company
make_purchase_receipt(company="_Test Company 4", warehouse="Stores - _TC4")
# try to make another backdated PR
posting_date = add_days(today(), -1)
pr = make_purchase_receipt(company="_Test Company 4", warehouse="Stores - _TC4",
do_not_submit=True)
pr.set_posting_time = 1
pr.posting_date = posting_date
pr.save()
self.assertRaises(frappe.ValidationError, pr.submit)
# make purchase receipt for other company backdated
pr = make_purchase_receipt(company="_Test Company 4", warehouse="Finished Goods - _TC4",
do_not_submit=True)
pr.set_posting_time = 1
pr.posting_date = posting_date
pr.submit()
# Allowed to submit for other company's PR
self.assertEqual(pr.docstatus, 1)
def get_sl_entries(voucher_type, voucher_no):
return frappe.db.sql(""" select actual_qty, warehouse, stock_value_difference
from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s

View File

@ -205,7 +205,9 @@ class StockEntry(StockController):
for f in ("uom", "stock_uom", "description", "item_name", "expense_account",
"cost_center", "conversion_factor"):
if f in ["stock_uom", "conversion_factor"] or not item.get(f):
if f == "stock_uom" or not item.get(f):
item.set(f, item_details.get(f))
if f == 'conversion_factor' and item.uom == item_details.get('stock_uom'):
item.set(f, item_details.get(f))
if not item.transfer_qty and item.qty:
@ -849,6 +851,8 @@ class StockEntry(StockController):
frappe.throw(_("Posting date and posting time is mandatory"))
self.set_work_order_details()
self.flags.backflush_based_on = frappe.db.get_single_value("Manufacturing Settings",
"backflush_raw_materials_based_on")
if self.bom_no:
@ -865,14 +869,16 @@ class StockEntry(StockController):
item["to_warehouse"] = self.pro_doc.wip_warehouse
self.add_to_stock_entry_detail(item_dict)
elif (self.work_order and (self.purpose == "Manufacture" or self.purpose == "Material Consumption for Manufacture")
and not self.pro_doc.skip_transfer and backflush_based_on == "Material Transferred for Manufacture"):
elif (self.work_order and (self.purpose == "Manufacture"
or self.purpose == "Material Consumption for Manufacture") and not self.pro_doc.skip_transfer
and self.flags.backflush_based_on == "Material Transferred for Manufacture"):
self.get_transfered_raw_materials()
elif (self.work_order and backflush_based_on== "BOM" and
(self.purpose == "Manufacture" or self.purpose == "Material Consumption for Manufacture")
elif (self.work_order and (self.purpose == "Manufacture" or
self.purpose == "Material Consumption for Manufacture") and self.flags.backflush_based_on== "BOM"
and frappe.db.get_single_value("Manufacturing Settings", "material_consumption")== 1):
self.get_unconsumed_raw_materials()
else:
if not self.fg_completed_qty:
frappe.throw(_("Manufacturing Quantity is mandatory"))
@ -1111,7 +1117,6 @@ class StockEntry(StockController):
for d in backflushed_materials.get(item.item_code):
if d.get(item.warehouse):
if (qty > req_qty):
qty = req_qty
qty-= d.get(item.warehouse)
if qty > 0:
@ -1137,12 +1142,24 @@ class StockEntry(StockController):
item_dict = self.get_pro_order_required_items(backflush_based_on)
max_qty = flt(self.pro_doc.qty)
allow_overproduction = False
overproduction_percentage = flt(frappe.db.get_single_value("Manufacturing Settings",
"overproduction_percentage_for_work_order"))
to_transfer_qty = flt(self.pro_doc.material_transferred_for_manufacturing) + flt(self.fg_completed_qty)
transfer_limit_qty = max_qty + ((max_qty * overproduction_percentage) / 100)
if transfer_limit_qty >= to_transfer_qty:
allow_overproduction = True
for item, item_details in iteritems(item_dict):
pending_to_issue = flt(item_details.required_qty) - flt(item_details.transferred_qty)
desire_to_transfer = flt(self.fg_completed_qty) * flt(item_details.required_qty) / max_qty
if (desire_to_transfer <= pending_to_issue or
(desire_to_transfer > 0 and backflush_based_on == "Material Transferred for Manufacture")):
if (desire_to_transfer <= pending_to_issue
or (desire_to_transfer > 0 and backflush_based_on == "Material Transferred for Manufacture")
or allow_overproduction):
item_dict[item]["qty"] = desire_to_transfer
elif pending_to_issue > 0:
item_dict[item]["qty"] = pending_to_issue

View File

@ -795,6 +795,32 @@ class TestStockEntry(unittest.TestCase):
])
)
def test_conversion_factor_change(self):
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
repack_entry = frappe.copy_doc(test_records[3])
repack_entry.posting_date = nowdate()
repack_entry.posting_time = nowtime()
repack_entry.set_stock_entry_type()
repack_entry.insert()
# check current uom and conversion factor
self.assertTrue(repack_entry.items[0].uom, "_Test UOM")
self.assertTrue(repack_entry.items[0].conversion_factor, 1)
# change conversion factor
repack_entry.items[0].uom = "_Test UOM 1"
repack_entry.items[0].stock_uom = "_Test UOM 1"
repack_entry.items[0].conversion_factor = 2
repack_entry.save()
repack_entry.submit()
self.assertEqual(repack_entry.items[0].conversion_factor, 2)
self.assertEqual(repack_entry.items[0].uom, "_Test UOM 1")
self.assertEqual(repack_entry.items[0].qty, 50)
self.assertEqual(repack_entry.items[0].transfer_qty, 100)
frappe.db.set_default("allow_negative_stock", 0)
def make_serialized_item(**args):
args = frappe._dict(args)
se = frappe.copy_doc(test_records[0])

View File

@ -238,7 +238,6 @@
"oldfieldname": "conversion_factor",
"oldfieldtype": "Currency",
"print_hide": 1,
"read_only": 1,
"reqd": 1
},
{
@ -498,15 +497,14 @@
"depends_on": "eval:parent.purpose===\"Repack\" && doc.t_warehouse",
"fieldname": "set_basic_rate_manually",
"fieldtype": "Check",
"label": "Set Basic Rate Manually",
"show_days": 1,
"show_seconds": 1
"label": "Set Basic Rate Manually"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-06-08 12:57:03.172887",
"modified": "2020-09-22 17:55:03.384138",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry Detail",

View File

@ -5,7 +5,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import flt, getdate, add_days, formatdate
from frappe.utils import flt, getdate, add_days, formatdate, get_datetime, date_diff
from frappe.model.document import Document
from datetime import date
from erpnext.controllers.item_variant import ItemTemplateCannotHaveStock
@ -33,6 +33,8 @@ class StockLedgerEntry(Document):
self.scrub_posting_time()
self.validate_and_set_fiscal_year()
self.block_transactions_against_group_warehouse()
self.validate_with_last_transaction_posting_time()
self.validate_future_posting()
def on_submit(self):
self.check_stock_frozen_date()
@ -139,6 +141,30 @@ class StockLedgerEntry(Document):
from erpnext.stock.utils import is_group_warehouse
is_group_warehouse(self.warehouse)
def validate_with_last_transaction_posting_time(self):
last_transaction_time = frappe.db.sql("""
select MAX(timestamp(posting_date, posting_time)) as posting_time
from `tabStock Ledger Entry`
where docstatus = 1 and item_code = %s
and warehouse = %s""", (self.item_code, self.warehouse))[0][0]
cur_doc_posting_datetime = "%s %s" % (self.posting_date, self.get("posting_time") or "00:00:00")
if last_transaction_time and get_datetime(cur_doc_posting_datetime) < get_datetime(last_transaction_time):
msg = _("Last Stock Transaction for item {0} under warehouse {1} was on {2}.").format(frappe.bold(self.item_code),
frappe.bold(self.warehouse), frappe.bold(last_transaction_time))
msg += "<br><br>" + _("Stock Transactions for Item {0} under warehouse {1} cannot be posted before this time.").format(
frappe.bold(self.item_code), frappe.bold(self.warehouse))
msg += "<br><br>" + _("Please remove this item and try to submit again or update the posting time.")
frappe.throw(msg, title=_("Backdated Stock Entry"))
def validate_future_posting(self):
if date_diff(self.posting_date, getdate()) > 0:
msg = _("Posting future stock transactions are not allowed due to Immutable Ledger")
frappe.throw(msg, title=_("Future Posting Not Allowed"))
def on_doctype_update():
if not frappe.db.has_index('tabStock Ledger Entry', 'posting_sort_index'):
frappe.db.commit()

View File

@ -109,6 +109,10 @@ frappe.ui.form.on("Stock Reconciliation", {
frappe.model.set_value(cdt, cdn, "current_amount", r.message.rate * r.message.qty);
frappe.model.set_value(cdt, cdn, "amount", r.message.rate * r.message.qty);
frappe.model.set_value(cdt, cdn, "current_serial_no", r.message.serial_nos);
if (frm.doc.purpose == "Stock Reconciliation") {
frappe.model.set_value(cdt, cdn, "serial_no", r.message.serial_nos);
}
}
});
}

View File

@ -67,6 +67,8 @@ class StockReconciliation(StockController):
if item_dict.get("serial_nos"):
item.current_serial_no = item_dict.get("serial_nos")
if self.purpose == "Stock Reconciliation":
item.serial_no = item.current_serial_no
item.current_qty = item_dict.get("qty")
item.current_valuation_rate = item_dict.get("rate")

View File

@ -124,7 +124,7 @@ class TestStockReconciliation(unittest.TestCase):
to_delete_records.append(sr.name)
sr = create_stock_reconciliation(item_code=serial_item_code,
warehouse = serial_warehouse, qty=5, rate=300, serial_no = '\n'.join(serial_nos))
warehouse = serial_warehouse, qty=5, rate=300)
serial_nos1 = get_serial_nos(sr.items[0].serial_no)
self.assertEqual(len(serial_nos1), 5)

View File

@ -398,6 +398,11 @@ def get_item_warehouse(item, args, overwrite_warehouse, defaults={}):
else:
warehouse = args.get('warehouse')
if not warehouse:
default_warehouse = frappe.db.get_single_value("Stock Settings", "default_warehouse")
if frappe.db.get_value("Warehouse", default_warehouse, "company") == args.company:
return default_warehouse
return warehouse
def update_barcode_value(out):

View File

@ -3,6 +3,14 @@
frappe.query_reports["Batch-Wise Balance History"] = {
"filters": [
{
"fieldname":"company",
"label": __("Company"),
"fieldtype": "Link",
"options": "Company",
"default": frappe.defaults.get_user_default("Company"),
"reqd": 1
},
{
"fieldname":"from_date",
"label": __("From Date"),
@ -20,12 +28,46 @@ frappe.query_reports["Batch-Wise Balance History"] = {
"reqd": 1
},
{
"fieldname": "item",
"label": __("Item"),
"fieldname":"item_code",
"label": __("Item Code"),
"fieldtype": "Link",
"options": "Item",
"width": "80"
}
"get_query": function() {
return {
filters: {
"has_batch_no": 1
}
};
}
},
{
"fieldname":"warehouse",
"label": __("Warehouse"),
"fieldtype": "Link",
"options": "Warehouse",
"get_query": function() {
let company = frappe.query_report.get_filter_value('company');
return {
filters: {
"company": company
}
};
}
},
{
"fieldname":"batch_no",
"label": __("Batch No"),
"fieldtype": "Link",
"options": "Batch",
"get_query": function() {
let item_code = frappe.query_report.get_filter_value('item_code');
return {
filters: {
"item": item_code
}
};
}
},
],
"formatter": function (value, row, column, data, default_formatter) {
if (column.fieldname == "Batch" && data && !!data["Batch"]) {

View File

@ -57,6 +57,10 @@ def get_conditions(filters):
else:
frappe.throw(_("'To Date' is required"))
for field in ["item_code", "warehouse", "batch_no", "company"]:
if filters.get(field):
conditions += " and {0} = {1}".format(field, frappe.db.escape(filters.get(field)))
return conditions

View File

@ -16,10 +16,11 @@ def execute(filters=None):
data = []
for item, item_dict in iteritems(item_details):
earliest_age, latest_age = 0, 0
fifo_queue = sorted(filter(_func, item_dict["fifo_queue"]), key=_func)
details = item_dict["details"]
if not fifo_queue or (not item_dict.get("total_qty")): continue
if not fifo_queue and (not item_dict.get("total_qty")): continue
average_age = get_average_age(fifo_queue, to_date)
earliest_age = date_diff(to_date, fifo_queue[0][1])
@ -170,7 +171,8 @@ def get_fifo_queue(filters, sle=None):
item_details.setdefault(key, {"details": d, "fifo_queue": []})
fifo_queue = item_details[key]["fifo_queue"]
transferred_item_details.setdefault((d.voucher_no, d.name), [])
transferred_item_key = (d.voucher_no, d.name, d.warehouse)
transferred_item_details.setdefault(transferred_item_key, [])
if d.voucher_type == "Stock Reconciliation":
d.actual_qty = flt(d.qty_after_transaction) - flt(item_details[key].get("qty_after_transaction", 0))
@ -178,10 +180,10 @@ def get_fifo_queue(filters, sle=None):
serial_no_list = get_serial_nos(d.serial_no) if d.serial_no else []
if d.actual_qty > 0:
if transferred_item_details.get((d.voucher_no, d.name)):
batch = transferred_item_details[(d.voucher_no, d.name)][0]
if transferred_item_details.get(transferred_item_key):
batch = transferred_item_details[transferred_item_key][0]
fifo_queue.append(batch)
transferred_item_details[((d.voucher_no, d.name))].pop(0)
transferred_item_details[transferred_item_key].pop(0)
else:
if serial_no_list:
for serial_no in serial_no_list:
@ -205,11 +207,11 @@ def get_fifo_queue(filters, sle=None):
# if batch qty > 0
# not enough or exactly same qty in current batch, clear batch
qty_to_pop -= flt(batch[0])
transferred_item_details[(d.voucher_no, d.name)].append(fifo_queue.pop(0))
transferred_item_details[transferred_item_key].append(fifo_queue.pop(0))
else:
# all from current batch
batch[0] = flt(batch[0]) - qty_to_pop
transferred_item_details[(d.voucher_no, d.name)].append([qty_to_pop, batch[1]])
transferred_item_details[transferred_item_key].append([qty_to_pop, batch[1]])
qty_to_pop = 0
item_details[key]["qty_after_transaction"] = d.qty_after_transaction

View File

@ -1,11 +1,24 @@
<h3>{{_("Request for Quotation")}}</h3>
<h4>{{_("Request for Quotation")}}</h4>
<p>{{ supplier_salutation if supplier_salutation else ''}} {{ supplier_name }},</p>
<p>{{ message }}</p>
<p>{{_("The Request for Quotation can be accessed by clicking on the following button")}}:</p>
<p>
<button style="border: 1px solid #15c; padding: 6px; border-radius: 5px; background-color: white;">
<a href="{{ rfq_link }}" style="color: #15c; text-decoration:none;" target="_blank">Submit your Quotation</a>
</button>
</p><br>
<p>{{_("Regards")}},<br>
{{ user_fullname }}</p><br>
{% if update_password_link %}
<p>{{_("Please click on the following link to set your new password")}}:</p>
<p><a href="{{ update_password_link }}">{{ update_password_link }}</a></p>
{% else %}
<p>{{_("The request for quotation can be accessed by clicking on the following link")}}:</p>
<p><a href="{{ rfq_link }}">Submit your Quotation</a></p>
<p>{{_("Please click on the following button to set your new password")}}:</p>
<p>
<button style="border: 1px solid #15c; padding: 4px; border-radius: 5px; background-color: white;">
<a href="{{ update_password_link }}" style="color: #15c; font-size: 12px; text-decoration:none;" target="_blank">{{_("Update Password") }}</a>
</button>
</p>
{% endif %}
<p>{{_("Thank you")}},<br>
{{ user_fullname }}</p>

View File

@ -521,7 +521,6 @@ Charge of type 'Actual' in row {0} cannot be included in Item Rate,Heffing van t
Chargeble,Chargeble,
Charges are updated in Purchase Receipt against each item,Kostes word opgedateer in Aankoopontvangste teen elke item,
"Charges will be distributed proportionately based on item qty or amount, as per your selection","Kostes sal proporsioneel verdeel word op grond van die hoeveelheid of hoeveelheid van die produk, soos per u keuse",
Chart Of Accounts,Grafiek van rekeninge,
Chart of Cost Centers,Grafiek van kostesentrums,
Check all,Kyk alles,
Checkout,Uitteken,
@ -581,7 +580,6 @@ Company {0} does not exist,Maatskappy {0} bestaan nie,
Compensatory Off,Kompenserende Off,
Compensatory leave request days not in valid holidays,Vergoedingsverlof versoek dae nie in geldige vakansiedae,
Complaint,klagte,
Completed Qty can not be greater than 'Qty to Manufacture',Voltooide hoeveelheid kan nie groter wees as &#39;Hoeveelheid om te vervaardig&#39; nie,
Completion Date,voltooiingsdatum,
Computer,rekenaar,
Condition,toestand,
@ -2033,7 +2031,6 @@ Please select Category first,Kies asseblief Kategorie eerste,
Please select Charge Type first,Kies asseblief die laastipe eers,
Please select Company,Kies asseblief Maatskappy,
Please select Company and Designation,Kies asseblief Maatskappy en Aanwysing,
Please select Company and Party Type first,Kies asseblief eers Maatskappy- en Partytipe,
Please select Company and Posting Date to getting entries,Kies asseblief Maatskappy en Posdatum om inskrywings te kry,
Please select Company first,Kies asseblief Maatskappy eerste,
Please select Completion Date for Completed Asset Maintenance Log,Kies asseblief Voltooiingsdatum vir voltooide bateonderhoudslog,
@ -2980,7 +2977,6 @@ The name of the institute for which you are setting up this system.,Die naam van
The name of your company for which you are setting up this system.,Die naam van u maatskappy waarvoor u hierdie stelsel opstel.,
The number of shares and the share numbers are inconsistent,Die aantal aandele en die aandele is onbestaanbaar,
The payment gateway account in plan {0} is different from the payment gateway account in this payment request,Die betaling gateway rekening in plan {0} verskil van die betaling gateway rekening in hierdie betaling versoek,
The request for quotation can be accessed by clicking on the following link,Die versoek om kwotasie kan verkry word deur op die volgende skakel te kliek,
The selected BOMs are not for the same item,Die gekose BOM&#39;s is nie vir dieselfde item nie,
The selected item cannot have Batch,Die gekose item kan nie Batch hê nie,
The seller and the buyer cannot be the same,Die verkoper en die koper kan nie dieselfde wees nie,
@ -3130,7 +3126,6 @@ Total contribution percentage should be equal to 100,Die totale bydraepersentasi
Total flexible benefit component amount {0} should not be less than max benefits {1},Die totale bedrag vir komponent van buigsame voordele {0} mag nie minder wees as die maksimum voordele nie {1},
Total hours: {0},Totale ure: {0},
Total leaves allocated is mandatory for Leave Type {0},"Totale blare wat toegeken is, is verpligtend vir Verlof Tipe {0}",
Total weightage assigned should be 100%. It is {0},Totale gewig toegeken moet 100% wees. Dit is {0},
Total working hours should not be greater than max working hours {0},Totale werksure moet nie groter wees nie as maksimum werksure {0},
Total {0} ({1}),Totaal {0} ({1}),
"Total {0} for all items is zero, may be you should change 'Distribute Charges Based On'","Totale {0} vir alle items is nul, mag u verander word &quot;Versprei koste gebaseer op &#39;",
@ -3544,7 +3539,6 @@ Company GSTIN,Maatskappy GSTIN,
Company field is required,Ondernemingsveld word vereis,
Creating Dimensions...,Skep dimensies ...,
Duplicate entry against the item code {0} and manufacturer {1},Dupliseer inskrywing teen die itemkode {0} en vervaardiger {1},
Import Chart Of Accounts from CSV / Excel files,Voer rekeningkaart uit CSV / Excel-lêers in,
Invalid GSTIN! The input you've entered doesn't match the GSTIN format for UIN Holders or Non-Resident OIDAR Service Providers,"Ongeldige GSTIN! Die invoer wat u ingevoer het, stem nie ooreen met die GSTIN-formaat vir UIN-houers of OIDAR-diensverskaffers wat nie inwoon nie",
Invoice Grand Total,Faktuur groot totaal,
Last carbon check date cannot be a future date,Die laaste datum vir koolstoftoets kan nie &#39;n toekoms wees nie,
@ -3921,7 +3915,6 @@ Plaid authentication error,Plaid-verifikasiefout,
Plaid public token error,Geplaaste openbare tekenfout,
Plaid transactions sync error,Gesinkroniseerfout in plaidtransaksies,
Please check the error log for details about the import errors,Kontroleer die foutlogboek vir meer inligting oor die invoerfoute,
Please click on the following link to set your new password,Klik asseblief op die volgende skakel om u nuwe wagwoord te stel,
Please create <b>DATEV Settings</b> for Company <b>{}</b>.,Skep asseblief <b>DATEV-instellings</b> vir die maatskappy <b>{}</b> .,
Please create adjustment Journal Entry for amount {0} ,Skep &#39;n aanpassingsjoernaalinskrywing vir bedrag {0},
Please do not create more than 500 items at a time,Moenie meer as 500 items op &#39;n slag skep nie,
@ -3997,6 +3990,7 @@ Refreshing,verfrissende,
Release date must be in the future,Die datum van vrylating moet in die toekoms wees,
Relieving Date must be greater than or equal to Date of Joining,Verligtingsdatum moet groter wees as of gelyk wees aan die Datum van aansluiting,
Rename,hernoem,
Rename Not Allowed,Hernoem nie toegelaat nie,
Repayment Method is mandatory for term loans,Terugbetalingsmetode is verpligtend vir termynlenings,
Repayment Start Date is mandatory for term loans,Aanvangsdatum vir terugbetaling is verpligtend vir termynlenings,
Report Item,Rapporteer item,
@ -4043,7 +4037,6 @@ Search results for,Soek resultate vir,
Select All,Kies Alles,
Select Difference Account,Kies Verskilrekening,
Select a Default Priority.,Kies &#39;n standaardprioriteit.,
Select a Supplier from the Default Supplier List of the items below.,Kies &#39;n verskaffer uit die standaardverskafferlys van die onderstaande items.,
Select a company,Kies &#39;n maatskappy,
Select finance book for the item {0} at row {1},Kies finansieringsboek vir die item {0} op ry {1},
Select only one Priority as Default.,Kies slegs een prioriteit as verstek.,
@ -4247,7 +4240,6 @@ Yes,Ja,
Actual ,werklike,
Add to cart,Voeg by die winkelwagen,
Budget,begroting,
Chart Of Accounts Importer,Invoerder van rekeningrekeninge,
Chart of Accounts,Tabel van rekeninge,
Customer database.,Kliënt databasis.,
Days Since Last order,Dae sedert die laaste bestelling,
@ -4546,7 +4538,6 @@ Role that is allowed to submit transactions that exceed credit limits set.,Rol w
Check Supplier Invoice Number Uniqueness,Kontroleer Verskaffer-faktuurnommer Uniekheid,
Make Payment via Journal Entry,Betaal via Joernaal Inskrywing,
Unlink Payment on Cancellation of Invoice,Ontkoppel betaling met kansellasie van faktuur,
Unlink Advance Payment on Cancelation of Order,Ontkoppel vooruitbetaling by kansellasie van bestelling,
Book Asset Depreciation Entry Automatically,Boekbate-waardeverminderinginskrywing outomaties,
Automatically Add Taxes and Charges from Item Tax Template,Belasting en heffings word outomaties bygevoeg vanaf die itembelastingsjabloon,
Automatically Fetch Payment Terms,Haal betalingsvoorwaardes outomaties aan,
@ -4940,7 +4931,6 @@ Closing Account Head,Sluitingsrekeninghoof,
POS Customer Group,POS kliënt groep,
POS Field,POS veld,
POS Item Group,POS Item Group,
[Select],[Kies],
Company Address,Maatskappyadres,
Update Stock,Werk Voorraad,
Ignore Pricing Rule,Ignoreer prysreël,
@ -6481,7 +6471,6 @@ HR-APR-.YY.-.MM.,HR-APR-.YY.-.MM.,
Appraisal Template,Appraisal Template,
For Employee Name,Vir Werknemer Naam,
Goals,Doelwitte,
Calculate Total Score,Bereken totale telling,
Total Score (Out of 5),Totale telling (uit 5),
"Any other remarks, noteworthy effort that should go in the records.","Enige ander opmerkings, noemenswaardige poging wat in die rekords moet plaasvind.",
Appraisal Goal,Evalueringsdoel,
@ -6599,11 +6588,6 @@ Relieving Date,Ontslagdatum,
Reason for Leaving,Rede vir vertrek,
Leave Encashed?,Verlaten verlaat?,
Encashment Date,Bevestigingsdatum,
Exit Interview Details,Afhanklike onderhoudsbesonderhede,
Held On,Aangehou,
Reason for Resignation,Rede vir bedanking,
Better Prospects,Beter vooruitsigte,
Health Concerns,Gesondheid Kommer,
New Workplace,Nuwe werkplek,
HR-EAD-.YYYY.-,HR-EAD-.YYYY.-,
Returned Amount,Terugbetaalde bedrag,
@ -8239,9 +8223,6 @@ Landed Cost Help,Landed Cost Help,
Manufacturers used in Items,Vervaardigers gebruik in items,
Limited to 12 characters,Beperk tot 12 karakters,
MAT-MR-.YYYY.-,MAT-MR-.YYYY.-,
Set Warehouse,Stel pakhuis,
Sets 'For Warehouse' in each row of the Items table.,Stel &#39;Vir pakhuis&#39; in elke ry van die Artikeltabel in.,
Requested For,Gevra vir,
Partially Ordered,Gedeeltelik bestel,
Transferred,oorgedra,
% Ordered,% Bestel,
@ -8690,8 +8671,6 @@ Material Request Warehouse,Materiaalversoekpakhuis,
Select warehouse for material requests,Kies pakhuis vir materiaalversoeke,
Transfer Materials For Warehouse {0},Oordragmateriaal vir pakhuis {0},
Production Plan Material Request Warehouse,Produksieplan Materiaalversoekpakhuis,
Set From Warehouse,Stel vanaf pakhuis,
Source Warehouse (Material Transfer),Bronpakhuis (materiaaloordrag),
Sets 'Source Warehouse' in each row of the items table.,Stel &#39;Bronpakhuis&#39; in elke ry van die artikeltabel in.,
Sets 'Target Warehouse' in each row of the items table.,Stel &#39;Target Warehouse&#39; in elke ry van die artikeltabel in.,
Show Cancelled Entries,Wys gekanselleerde inskrywings,
@ -9157,7 +9136,6 @@ Professional Tax,Professionele belasting,
Is Income Tax Component,Is inkomstebelasting komponent,
Component properties and references ,Komponenteienskappe en verwysings,
Additional Salary ,Bykomende salaris,
Condtion and formula,Kondisie en formule,
Unmarked days,Ongemerkte dae,
Absent Days,Afwesige dae,
Conditions and Formula variable and example,Voorwaardes en formule veranderlike en voorbeeld,
@ -9444,7 +9422,6 @@ Plaid invalid request error,Plaid ongeldige versoekfout,
Please check your Plaid client ID and secret values,Gaan u Plaid-kliënt-ID en geheime waardes na,
Bank transaction creation error,Fout met die skep van banktransaksies,
Unit of Measurement,Eenheid van mate,
Row #{}: Selling rate for item {} is lower than its {}. Selling rate should be atleast {},Ry # {}: die verkoopkoers vir item {} is laer as die {}. Verkoopprys moet ten minste {} wees,
Fiscal Year {0} Does Not Exist,Fiskale jaar {0} bestaan nie,
Row # {0}: Returned Item {1} does not exist in {2} {3},Ry # {0}: Teruggestuurde item {1} bestaan nie in {2} {3},
Valuation type charges can not be marked as Inclusive,Kostes van waardasie kan nie as Inklusief gemerk word nie,
@ -9598,8 +9575,60 @@ Set Response Time and Resolution Time for Priority {0} in row {1}.,Stel reaksiet
Response Time for {0} priority in row {1} can't be greater than Resolution Time.,Die responstyd vir {0} prioriteit in ry {1} kan nie langer wees as die resolusietyd nie.,
{0} is not enabled in {1},{0} is nie geaktiveer in {1},
Group by Material Request,Groepeer volgens materiaalversoek,
"Row {0}: For Supplier {0}, Email Address is Required to Send Email",Ry {0}: Vir verskaffer {0} word e-posadres vereis om e-pos te stuur,
Email Sent to Supplier {0},E-pos gestuur aan verskaffer {0},
"The Access to Request for Quotation From Portal is Disabled. To Allow Access, Enable it in Portal Settings.",Die toegang tot die versoek vir &#39;n kwotasie vanaf die portaal is uitgeskakel. Skakel dit in Portaalinstellings in om toegang te verleen.,
Supplier Quotation {0} Created,Kwotasie van verskaffer {0} geskep,
Valid till Date cannot be before Transaction Date,Geldige bewerkingsdatum kan nie voor transaksiedatum wees nie,
Unlink Advance Payment on Cancellation of Order,Ontkoppel vooruitbetaling by kansellasie van bestelling,
"Simple Python Expression, Example: territory != 'All Territories'","Eenvoudige Python-uitdrukking, Voorbeeld: gebied! = &#39;Alle gebiede&#39;",
Sales Contributions and Incentives,Verkoopsbydraes en aansporings,
Sourced by Supplier,Van verskaffer verkry,
Total weightage assigned should be 100%.<br>It is {0},Die totale gewigstoekenning moet 100% wees.<br> Dit is {0},
Account {0} exists in parent company {1}.,Rekening {0} bestaan in moedermaatskappy {1}.,
"To overrule this, enable '{0}' in company {1}",Skakel &#39;{0}&#39; in die maatskappy {1} in om dit te oorheers.,
Invalid condition expression,Ongeldige toestandsuitdrukking,
Please Select a Company First,Kies eers &#39;n maatskappy,
Please Select Both Company and Party Type First,Kies asseblief eers die maatskappy en die partytjie,
Provide the invoice portion in percent,Verskaf die faktuurgedeelte in persent,
Give number of days according to prior selection,Gee die aantal dae volgens voorafgaande keuse,
Email Details,E-posbesonderhede,
"Select a greeting for the receiver. E.g. Mr., Ms., etc.","Kies &#39;n groet vir die ontvanger. Bv. Mnr., Me., Ens.",
Preview Email,Voorskou e-pos,
Please select a Supplier,Kies &#39;n verskaffer,
Supplier Lead Time (days),Leveringstyd (dae),
"Home, Work, etc.","Huis, werk, ens.",
Exit Interview Held On,Uitgangsonderhoud gehou,
Condition and formula,Toestand en formule,
Sets 'Target Warehouse' in each row of the Items table.,Stel &#39;Target Warehouse&#39; in elke ry van die Items-tabel.,
Sets 'Source Warehouse' in each row of the Items table.,Stel &#39;Bronpakhuis&#39; in elke ry van die Artikeltabel in.,
POS Register,POS-register,
"Can not filter based on POS Profile, if grouped by POS Profile","Kan nie gebaseer op POS-profiel filter nie, indien dit gegroepeer is volgens POS-profiel",
"Can not filter based on Customer, if grouped by Customer","Kan nie op grond van die klant filter nie, indien dit volgens die klant gegroepeer is",
"Can not filter based on Cashier, if grouped by Cashier","Kan nie filter op grond van Kassier nie, indien dit gegroepeer is volgens Kassier",
Payment Method,Betalings metode,
"Can not filter based on Payment Method, if grouped by Payment Method","Kan nie op grond van die betaalmetode filter nie, indien dit gegroepeer is volgens die betaalmetode",
Supplier Quotation Comparison,Vergelyking tussen kwotasies van verskaffers,
Price per Unit (Stock UOM),Prys per eenheid (voorraad UOM),
Group by Supplier,Groepeer volgens verskaffer,
Group by Item,Groepeer volgens item,
Remember to set {field_label}. It is required by {regulation}.,Onthou om {field_label} in te stel. Dit word deur {regulasie} vereis.,
Enrollment Date cannot be before the Start Date of the Academic Year {0},Inskrywingsdatum kan nie voor die begindatum van die akademiese jaar wees nie {0},
Enrollment Date cannot be after the End Date of the Academic Term {0},Inskrywingsdatum kan nie na die einddatum van die akademiese termyn {0} wees nie,
Enrollment Date cannot be before the Start Date of the Academic Term {0},Inskrywingsdatum kan nie voor die begindatum van die akademiese termyn {0} wees nie,
Posting future transactions are not allowed due to Immutable Ledger,As gevolg van Immutable Ledger word toekomstige transaksies nie toegelaat nie,
Future Posting Not Allowed,Toekomstige plasing word nie toegelaat nie,
"To enable Capital Work in Progress Accounting, ","Om rekeningkundige kapitaalwerk moontlik te maak,",
you must select Capital Work in Progress Account in accounts table,u moet Capital Work in Progress-rekening in die rekeningtabel kies,
You can also set default CWIP account in Company {},U kan ook die standaard CWIP-rekening instel in die maatskappy {},
The Request for Quotation can be accessed by clicking on the following button,Toegang tot die versoek vir &#39;n kwotasie is deur op die volgende knoppie te klik,
Regards,Groete,
Please click on the following button to set your new password,Klik op die volgende knoppie om u nuwe wagwoord in te stel,
Update Password,Wagwoord op te dateer,
Row #{}: Selling rate for item {} is lower than its {}. Selling {} should be atleast {},Ry # {}: die verkoopkoers vir item {} is laer as die {}. Verkoop {} moet ten minste {} wees,
You can alternatively disable selling price validation in {} to bypass this validation.,U kan ook die validering van verkooppryse in {} deaktiveer om hierdie validering te omseil.,
Invalid Selling Price,Ongeldige verkoopprys,
Address needs to be linked to a Company. Please add a row for Company in the Links table.,Adres moet aan &#39;n maatskappy gekoppel word. Voeg asseblief &#39;n ry vir Company in die skakeltabel.,
Company Not Linked,Maatskappy nie gekoppel nie,
Import Chart of Accounts from CSV / Excel files,Voer rekeningrekeninge in vanaf CSV / Excel-lêers,
Completed Qty cannot be greater than 'Qty to Manufacture',Voltooide hoeveelheid kan nie groter wees as &#39;hoeveelheid om te vervaardig&#39;,
"Row {0}: For Supplier {1}, Email Address is Required to send an email",Ry {0}: Vir verskaffer {1} word e-posadres vereis om &#39;n e-pos te stuur,

Can't render this file because it is too large.

View File

@ -521,7 +521,6 @@ Charge of type 'Actual' in row {0} cannot be included in Item Rate,አይነት
Chargeble,ቻርጅ,
Charges are updated in Purchase Receipt against each item,ክፍያዎች እያንዳንዱ ንጥል ላይ የግዢ ደረሰኝ ውስጥ መዘመን ነው,
"Charges will be distributed proportionately based on item qty or amount, as per your selection","ክፍያዎች ተመጣጣኝ መጠን በእርስዎ ምርጫ መሠረት, ንጥል ብዛት ወይም መጠን ላይ በመመርኮዝ መሰራጨት ይሆናል",
Chart Of Accounts,መለያዎች ገበታ,
Chart of Cost Centers,ወጪ ማዕከላት ገበታ,
Check all,ሁሉንም ይመልከቱ,
Checkout,ጨርሰህ ውጣ,
@ -581,7 +580,6 @@ Company {0} does not exist,ኩባንያ {0} የለም,
Compensatory Off,የማካካሻ አጥፋ,
Compensatory leave request days not in valid holidays,ተቀባይነት ባላቸው በዓላት ውስጥ ክፍያ የማይሰጥ የቀን የጥበቃ ቀን ጥያቄ,
Complaint,ቅሬታ,
Completed Qty can not be greater than 'Qty to Manufacture',ይልቅ &#39;ብዛት ለማምረት&#39; ተጠናቋል ብዛት የበለጠ መሆን አይችልም,
Completion Date,ማጠናቀቂያ ቀን,
Computer,ኮምፕዩተር,
Condition,ሁኔታ,
@ -2033,7 +2031,6 @@ Please select Category first,የመጀመሪያው ምድብ ይምረጡ,
Please select Charge Type first,በመጀመሪያ የክፍያ አይነት ይምረጡ,
Please select Company,ኩባንያ ይምረጡ,
Please select Company and Designation,እባክዎ ኩባንያ እና ዲዛይን ይምረጡ,
Please select Company and Party Type first,በመጀመሪያ ኩባንያ እና የፓርቲ አይነት ይምረጡ,
Please select Company and Posting Date to getting entries,እባክዎ ግቤቶችን ለመመዝገብ እባክዎ ኩባንያ እና የድረ-ገጽ ቀንን ይምረጡ,
Please select Company first,መጀመሪያ ኩባንያ እባክዎ ይምረጡ,
Please select Completion Date for Completed Asset Maintenance Log,እባክዎን ለተጠናቀቀው የንብረት ጥገና ምዝግብ ማስታወሻ ቀነ-ገደብ ይምረጡ,
@ -2980,7 +2977,6 @@ The name of the institute for which you are setting up this system.,ተቋሙ
The name of your company for which you are setting up this system.,የእርስዎን ኩባንያ ስም ስለ እናንተ ይህ ሥርዓት ማዋቀር ነው.,
The number of shares and the share numbers are inconsistent,የአክሲዮኖች ቁጥር እና የማካካሻ ቁጥሮች ወጥ ናቸው,
The payment gateway account in plan {0} is different from the payment gateway account in this payment request,የክፍያ ዕቅድ ክፍያ በእቅድ {0} ውስጥ በዚህ የክፍያ ጥያቄ ውስጥ ካለው የክፍያ በር መለያ የተለየ ነው።,
The request for quotation can be accessed by clicking on the following link,ጥቅስ ለማግኘት ጥያቄው በሚከተለው አገናኝ ላይ ጠቅ በማድረግ ሊደረስባቸው ይችላሉ,
The selected BOMs are not for the same item,የተመረጡት BOMs ተመሳሳይ ንጥል አይደሉም,
The selected item cannot have Batch,የተመረጠው ንጥል ባች ሊኖረው አይችልም,
The seller and the buyer cannot be the same,ሻጩ እና ገዢው ተመሳሳይ መሆን አይችሉም,
@ -3130,7 +3126,6 @@ Total contribution percentage should be equal to 100,ጠቅላላ መዋጮ መ
Total flexible benefit component amount {0} should not be less than max benefits {1},አጠቃላይ ተለዋዋጭ የድጋፍ አካል መጠን {0} ከከፍተኛው ጥቅሞች በታች መሆን የለበትም {1},
Total hours: {0},ጠቅላላ ሰዓት: {0},
Total leaves allocated is mandatory for Leave Type {0},ጠቅላላ ቅጠሎች የተመደቡበት አይነት {0},
Total weightage assigned should be 100%. It is {0},100% መሆን አለበት የተመደበ ጠቅላላ weightage. ይህ ነው {0},
Total working hours should not be greater than max working hours {0},ጠቅላላ የሥራ ሰዓቶች ከፍተኛ የሥራ ሰዓት በላይ መሆን የለበትም {0},
Total {0} ({1}),ጠቅላላ {0} ({1}),
"Total {0} for all items is zero, may be you should change 'Distribute Charges Based On'","ጠቅላላ {0} ሁሉም ንጥሎች እናንተ &#39;ላይ የተመሠረተ ክፍያዎች ያሰራጩ&#39; መቀየር አለበት ሊሆን ይችላል, ዜሮ ነው",
@ -3544,7 +3539,6 @@ Company GSTIN,የኩባንያ GSTIN,
Company field is required,የኩባንያው መስክ ያስፈልጋል።,
Creating Dimensions...,ልኬቶችን በመፍጠር ላይ ...,
Duplicate entry against the item code {0} and manufacturer {1},በእቃ ኮዱ {0} እና በአምራቹ {1} ላይ የተባዛ ግቤት,
Import Chart Of Accounts from CSV / Excel files,የመለያዎች ገበታዎችን ከ CSV / የ Excel ፋይሎች ያስመጡ።,
Invalid GSTIN! The input you've entered doesn't match the GSTIN format for UIN Holders or Non-Resident OIDAR Service Providers,ልክ ያልሆነ GSTIN! ያስገባኸው ግቤት ለ UIN Holders ወይም ነዋሪ ላልሆኑ OIDAR አገልግሎት አቅራቢዎች ከ GSTIN ቅርጸት ጋር አይጣጣምም ፡፡,
Invoice Grand Total,የክፍያ መጠየቂያ ግራንድ አጠቃላይ።,
Last carbon check date cannot be a future date,የመጨረሻው የካርቦን ፍተሻ ቀን የወደፊት ቀን ሊሆን አይችልም።,
@ -3921,7 +3915,6 @@ Plaid authentication error,የተዘረጋ ማረጋገጫ ስህተት።,
Plaid public token error,የተዘበራረቀ የህዝብ የምስጋና የምስክር ወረቀት,
Plaid transactions sync error,የተዘዋወሩ ግብይቶች የማመሳሰል ስህተት።,
Please check the error log for details about the import errors,እባክዎን ስለማስመጣት ስህተቶች ዝርዝር ለማግኘት የስህተት ምዝግብ ማስታወሻውን ይመልከቱ ፡፡,
Please click on the following link to set your new password,አዲሱን የይለፍ ቃል ለማዘጋጀት በሚከተለው አገናኝ ላይ ጠቅ ያድርጉ,
Please create <b>DATEV Settings</b> for Company <b>{}</b>.,እባክዎ <b>ለኩባንያ የ DATEV ቅንብሮችን</b> ይፍጠሩ <b>{}</b> ።,
Please create adjustment Journal Entry for amount {0} ,እባክዎ ለቁጥር {0} ማስተካከያ ጆርናል ግቤት ይፍጠሩ,
Please do not create more than 500 items at a time,እባክዎን በአንድ ጊዜ ከ 500 በላይ እቃዎችን አይፍጠሩ ፡፡,
@ -3997,6 +3990,7 @@ Refreshing,በማደስ ላይ,
Release date must be in the future,የሚለቀቅበት ቀን ለወደፊቱ መሆን አለበት።,
Relieving Date must be greater than or equal to Date of Joining,የመልሶ ማግኛ ቀን ከተቀላቀለበት ቀን የሚበልጥ ወይም እኩል መሆን አለበት,
Rename,ዳግም ሰይም,
Rename Not Allowed,ዳግም መሰየም አልተፈቀደም።,
Repayment Method is mandatory for term loans,የመክፈያ ዘዴ ለጊዜ ብድሮች አስገዳጅ ነው,
Repayment Start Date is mandatory for term loans,የመክፈያ መጀመሪያ ቀን ለአበዳሪ ብድሮች አስገዳጅ ነው,
Report Item,ንጥል ሪፖርት ያድርጉ ፡፡,
@ -4043,7 +4037,6 @@ Search results for,የፍለጋ ውጤቶች,
Select All,ሁሉንም ምረጥ,
Select Difference Account,የልዩ መለያ ይምረጡ።,
Select a Default Priority.,ነባሪ ቅድሚያ ይምረጡ።,
Select a Supplier from the Default Supplier List of the items below.,ከዚህ በታች ካሉት ነገሮች ነባሪ አቅራቢ ዝርዝር አቅራቢን ይምረጡ ፡፡,
Select a company,ኩባንያ ይምረጡ።,
Select finance book for the item {0} at row {1},ለዕቃው ፋይናንስ መጽሐፍ ይምረጡ {0} ረድፍ {1},
Select only one Priority as Default.,እንደ ነባሪ አንድ ቅድሚያ የሚሰጠውን ይምረጡ።,
@ -4247,7 +4240,6 @@ Yes,አዎ,
Actual ,ትክክለኛ,
Add to cart,ወደ ግዢው ቅርጫት ጨምር,
Budget,ባጀት,
Chart Of Accounts Importer,የመለያዎች አስመጪ ገበታ።,
Chart of Accounts,የአድራሻዎች ዝርዝር,
Customer database.,የደንበኛ ውሂብ ጎታ.,
Days Since Last order,የመጨረሻ ትዕዛዝ ጀምሮ ቀናት,
@ -4546,7 +4538,6 @@ Role that is allowed to submit transactions that exceed credit limits set.,ካ
Check Supplier Invoice Number Uniqueness,ማጣሪያ አቅራቢው የደረሰኝ ቁጥር ልዩ,
Make Payment via Journal Entry,ጆርናል Entry በኩል ክፍያ አድርግ,
Unlink Payment on Cancellation of Invoice,የደረሰኝ ስረዛ ላይ ክፍያ አታገናኝ,
Unlink Advance Payment on Cancelation of Order,በትዕዛዝ መተላለፍ ላይ የቅድሚያ ክፍያ ክፍያን አያላቅቁ።,
Book Asset Depreciation Entry Automatically,መጽሐፍ የንብረት ዋጋ መቀነስ Entry ሰር,
Automatically Add Taxes and Charges from Item Tax Template,ከእቃው ግብር አብነት ግብርን እና ክፍያዎች በራስ-ሰር ያክሉ።,
Automatically Fetch Payment Terms,የክፍያ ውሎችን በራስ-ሰር ያውጡ።,
@ -4940,7 +4931,6 @@ Closing Account Head,የመለያ ኃላፊ በመዝጋት ላይ,
POS Customer Group,POS የደንበኛ ቡድን,
POS Field,POS መስክ,
POS Item Group,POS ንጥል ቡድን,
[Select],[ምረጥ],
Company Address,የኩባንያ አድራሻ,
Update Stock,አዘምን Stock,
Ignore Pricing Rule,የዋጋ አሰጣጥ ደንብ ችላ,
@ -6481,7 +6471,6 @@ HR-APR-.YY.-.MM.,HR-APR-YY.-. ኤም.,
Appraisal Template,ግምገማ አብነት,
For Employee Name,የሰራተኛ ስም ለ,
Goals,ግቦች,
Calculate Total Score,አጠቃላይ ነጥብ አስላ,
Total Score (Out of 5),(5 ውጪ) አጠቃላይ ነጥብ,
"Any other remarks, noteworthy effort that should go in the records.","ሌሎች ማንኛውም አስተያየት, መዝገቦች ውስጥ መሄድ ዘንድ ትኩረት የሚስብ ጥረት.",
Appraisal Goal,ግምገማ ግብ,
@ -6599,11 +6588,6 @@ Relieving Date,ማስታገሻ ቀን,
Reason for Leaving,የምትሄድበት ምክንያት,
Leave Encashed?,Encashed ይውጡ?,
Encashment Date,Encashment ቀን,
Exit Interview Details,መውጫ ቃለ ዝርዝሮች,
Held On,የተያዙ ላይ,
Reason for Resignation,ሥራ መልቀቅ ለ ምክንያት,
Better Prospects,የተሻለ ተስፋ,
Health Concerns,የጤና ሰጋት,
New Workplace,አዲስ በሥራ ቦታ,
HR-EAD-.YYYY.-,ሃ-ኤአር-ያዮያን.-,
Returned Amount,የተመለሰው መጠን,
@ -8239,9 +8223,6 @@ Landed Cost Help,አረፈ ወጪ እገዛ,
Manufacturers used in Items,ንጥሎች ውስጥ ጥቅም ላይ አምራቾች,
Limited to 12 characters,12 ቁምፊዎች የተገደበ,
MAT-MR-.YYYY.-,ት እሚል-ያሲ-ያዮያን.-,
Set Warehouse,መጋዘን ያዘጋጁ,
Sets 'For Warehouse' in each row of the Items table.,በእቃዎቹ ሰንጠረዥ በእያንዳንዱ ረድፍ ‹ለመጋዘን› ያዘጋጃል ፡፡,
Requested For,ለ ተጠይቋል,
Partially Ordered,በከፊል የታዘዘ,
Transferred,ተላልፈዋል,
% Ordered,% የዕቃው መረጃ,
@ -8690,8 +8671,6 @@ Material Request Warehouse,የቁሳቁስ ጥያቄ መጋዘን,
Select warehouse for material requests,ለቁሳዊ ጥያቄዎች መጋዘን ይምረጡ,
Transfer Materials For Warehouse {0},ቁሳቁሶችን ለመጋዘን ያስተላልፉ {0},
Production Plan Material Request Warehouse,የምርት እቅድ ቁሳቁስ ጥያቄ መጋዘን,
Set From Warehouse,ከመጋዘን ተዘጋጅ,
Source Warehouse (Material Transfer),ምንጭ መጋዘን (ቁሳቁስ ማስተላለፍ),
Sets 'Source Warehouse' in each row of the items table.,በእያንዲንደ የእቃ ሰንጠረ tableች ረድፍ ውስጥ ‹ምንጭ መጋዘን› ያዘጋጃሌ ፡፡,
Sets 'Target Warehouse' in each row of the items table.,በእያንዲንደ የጠረጴዛዎች ረድፍ ውስጥ ‹ዒላማ መጋዘን› ያዘጋጃሌ ፡፡,
Show Cancelled Entries,የተሰረዙ ግቤቶችን አሳይ,
@ -9157,7 +9136,6 @@ Professional Tax,የሙያ ግብር,
Is Income Tax Component,የገቢ ግብር አካል ነው,
Component properties and references ,የአካል ክፍሎች እና ማጣቀሻዎች,
Additional Salary ,ተጨማሪ ደመወዝ,
Condtion and formula,መጨናነቅ እና ቀመር,
Unmarked days,ምልክት ያልተደረገባቸው ቀናት,
Absent Days,የቀሩ ቀናት,
Conditions and Formula variable and example,ሁኔታዎች እና የቀመር ተለዋዋጭ እና ምሳሌ,
@ -9444,7 +9422,6 @@ Plaid invalid request error,ትክክለኛ ያልሆነ የጥያቄ ስህተ
Please check your Plaid client ID and secret values,እባክዎ የፕላድ ደንበኛ መታወቂያዎን እና ሚስጥራዊ እሴቶችዎን ያረጋግጡ,
Bank transaction creation error,የባንክ ግብይት መፍጠር ስህተት,
Unit of Measurement,የመለኪያ አሃድ,
Row #{}: Selling rate for item {} is lower than its {}. Selling rate should be atleast {},ረድፍ # {}: ለንጥል የመሸጥ መጠን {} ከእሱ ያነሰ ነው። የመሸጥ መጠን ቢያንስ ቢያንስ መሆን አለበት {},
Fiscal Year {0} Does Not Exist,የበጀት ዓመት {0} የለም,
Row # {0}: Returned Item {1} does not exist in {2} {3},ረድፍ # {0}: የተመለሰ ንጥል {1} በ {2} {3} ውስጥ የለም,
Valuation type charges can not be marked as Inclusive,የዋጋ አሰጣጥ አይነት ክፍያዎች ሁሉን ያካተተ ሆኖ ምልክት ሊደረግባቸው አይቻልም,
@ -9598,8 +9575,60 @@ Set Response Time and Resolution Time for Priority {0} in row {1}.,የምላሽ
Response Time for {0} priority in row {1} can't be greater than Resolution Time.,የምላሽ ጊዜ ለ {0} በተከታታይ ቅድሚያ የሚሰጠው {1} ከመፍትሔው ጊዜ ሊበልጥ አይችልም።,
{0} is not enabled in {1},{0} በ {1} ውስጥ አልነቃም,
Group by Material Request,በቁሳዊ ጥያቄ በቡድን,
"Row {0}: For Supplier {0}, Email Address is Required to Send Email",ረድፍ {0} ለአቅራቢው {0} ኢሜል ለመላክ የኢሜል አድራሻ ያስፈልጋል,
Email Sent to Supplier {0},ለአቅራቢ ኢሜይል ተልኳል {0},
"The Access to Request for Quotation From Portal is Disabled. To Allow Access, Enable it in Portal Settings.",ከመግቢያው የጥቆማ ጥያቄ መዳረሻ ተሰናክሏል ፡፡ መዳረሻን ለመፍቀድ በ Portal ቅንብሮች ውስጥ ያንቁት።,
Supplier Quotation {0} Created,የአቅራቢ ጥቅስ {0} ተፈጥሯል,
Valid till Date cannot be before Transaction Date,እስከዛሬ ድረስ የሚሰራ ከግብይት ቀን በፊት መሆን አይችልም,
Unlink Advance Payment on Cancellation of Order,በትእዛዝ ስረዛ ላይ የቅድሚያ ክፍያ ግንኙነትን ያላቅቁ,
"Simple Python Expression, Example: territory != 'All Territories'",ቀላል የፓይዘን መግለጫ ፣ ምሳሌ: ክልል! = &#39;ሁሉም ግዛቶች&#39;,
Sales Contributions and Incentives,የሽያጭ አስተዋፅዖዎች እና ማበረታቻዎች,
Sourced by Supplier,በአቅራቢው ተነስቷል,
Total weightage assigned should be 100%.<br>It is {0},የተመደበው አጠቃላይ ክብደት 100% መሆን አለበት ፡፡<br> እሱ {0} ነው,
Account {0} exists in parent company {1}.,መለያ {0} በወላጅ ኩባንያ ውስጥ አለ {1}።,
"To overrule this, enable '{0}' in company {1}",ይህንን ለመሻር በኩባንያው ውስጥ {0} ን ያንቁ {1},
Invalid condition expression,ልክ ያልሆነ ሁኔታ መግለጫ,
Please Select a Company First,እባክዎ መጀመሪያ አንድ ኩባንያ ይምረጡ,
Please Select Both Company and Party Type First,እባክዎ መጀመሪያ ሁለቱንም ኩባንያ እና የድግስ ዓይነት ይምረጡ,
Provide the invoice portion in percent,የክፍያ መጠየቂያውን ክፍል በመቶኛ ያቅርቡ,
Give number of days according to prior selection,በቀድሞው ምርጫ መሠረት የቀናትን ቁጥር ይስጡ,
Email Details,የኢሜል ዝርዝሮች,
"Select a greeting for the receiver. E.g. Mr., Ms., etc.",ለተቀባዩ ሰላምታ ይምረጡ ፡፡ ለምሳሌ ሚስተር ወይዘሮ ወ.ዘ.ተ.,
Preview Email,ኢሜል ቅድመ እይታ,
Please select a Supplier,እባክዎ አቅራቢ ይምረጡ,
Supplier Lead Time (days),የአቅራቢ መሪ ጊዜ (ቀናት),
"Home, Work, etc.",ቤት ፣ ሥራ ፣ ወዘተ,
Exit Interview Held On,መውጫ ቃለ መጠይቅ በርቷል,
Condition and formula,ሁኔታ እና ቀመር,
Sets 'Target Warehouse' in each row of the Items table.,በእያንዲንደ የእቃ ሰንጠረ rowች ረድፍ ውስጥ ‹ዒላማ መጋዘን› ያዘጋጃሌ ፡፡,
Sets 'Source Warehouse' in each row of the Items table.,በእያንዲንደ የእቃ ሰንጠረ rowች ረድፍ ውስጥ ‹ምንጭ መጋዘን› ያዘጋጃሌ ፡፡,
POS Register,POS ይመዝገቡ,
"Can not filter based on POS Profile, if grouped by POS Profile",በ POS መገለጫ ከተመደቡ በ POS መገለጫ ላይ ተመስርተው ማጣሪያ ማድረግ አይቻልም,
"Can not filter based on Customer, if grouped by Customer",በደንበኛው ከተመደበ በደንበኛው ላይ የተመሠረተ ማጣሪያ ማድረግ አይቻልም,
"Can not filter based on Cashier, if grouped by Cashier",በገንዘብ ተቀባዩ ከተመደቡ በገንዘብ ተቀባይ ላይ የተመሠረተ ማጣራት አይቻልም,
Payment Method,የክፍያ ዘዴ,
"Can not filter based on Payment Method, if grouped by Payment Method",በክፍያ ዘዴ ከተመደቡ በክፍያ ዘዴው መሠረት ማጣሪያ ማድረግ አይቻልም,
Supplier Quotation Comparison,የአቅራቢዎች ጥቅስ ንፅፅር,
Price per Unit (Stock UOM),ዋጋ በአንድ ክፍል (ክምችት UOM),
Group by Supplier,በአቅራቢ ቡድን,
Group by Item,በንጥል በቡድን,
Remember to set {field_label}. It is required by {regulation}.,{Field_label} ን ማቀናበርን ያስታውሱ። በ {ደንብ} ይፈለጋል።,
Enrollment Date cannot be before the Start Date of the Academic Year {0},የምዝገባ ቀን ከአካዳሚክ አመቱ መጀመሪያ ቀን በፊት መሆን አይችልም {0},
Enrollment Date cannot be after the End Date of the Academic Term {0},የመመዝገቢያ ቀን ከትምህርታዊ ጊዜ ማብቂያ ቀን በኋላ መሆን አይችልም {0},
Enrollment Date cannot be before the Start Date of the Academic Term {0},የምዝገባ ቀን ከትምህርቱ ዘመን መጀመሪያ ቀን በፊት መሆን አይችልም {0},
Posting future transactions are not allowed due to Immutable Ledger,በሚተላለፍ ሊደር ምክንያት የወደፊት ግብይቶችን መለጠፍ አይፈቀድም,
Future Posting Not Allowed,የወደፊቱ መለጠፍ አልተፈቀደም,
"To enable Capital Work in Progress Accounting, ",በሂሳብ አያያዝ ውስጥ የካፒታል ሥራን ለማንቃት ፣,
you must select Capital Work in Progress Account in accounts table,በሂሳብ ሰንጠረዥ ውስጥ በሂሳብ መዝገብ ውስጥ ካፒታል ሥራን መምረጥ አለብዎት,
You can also set default CWIP account in Company {},እንዲሁም ነባሪ የ CWIP መለያ በኩባንያ ውስጥ ማቀናበር ይችላሉ {},
The Request for Quotation can be accessed by clicking on the following button,የጥያቄ ጥያቄ የሚከተለውን ቁልፍ በመጫን ማግኘት ይቻላል,
Regards,ከሰላምታ ጋር,
Please click on the following button to set your new password,አዲሱን የይለፍ ቃልዎን ለማዘጋጀት እባክዎ በሚከተለው ቁልፍ ላይ ጠቅ ያድርጉ,
Update Password,የይለፍ ቃል ያዘምኑ,
Row #{}: Selling rate for item {} is lower than its {}. Selling {} should be atleast {},ረድፍ # {}-ለንጥል የመሸጥ መጠን {} ከእርሷ ያነሰ ነው። መሸጥ {} ቢያንስ ቢያንስ መሆን አለበት {},
You can alternatively disable selling price validation in {} to bypass this validation.,ይህንን ማረጋገጫ ለማለፍ በ {} ውስጥ የሽያጭ ዋጋ ማረጋገጫውን በአማራጭ ማሰናከል ይችላሉ።,
Invalid Selling Price,ልክ ያልሆነ የሽያጭ ዋጋ,
Address needs to be linked to a Company. Please add a row for Company in the Links table.,አድራሻ ከኩባንያ ጋር መገናኘት አለበት ፡፡ እባክዎ በአገናኝስ ሰንጠረዥ ውስጥ ለኩባንያ አንድ ረድፍ ያክሉ።,
Company Not Linked,ኩባንያ አልተያያዘም,
Import Chart of Accounts from CSV / Excel files,የመለያዎች ገበታ ከ CSV / Excel ፋይሎች ያስመጡ,
Completed Qty cannot be greater than 'Qty to Manufacture',የተጠናቀቀው ኪቲ ከ Qty to Manufacturere ሊበልጥ አይችልም ፡፡,
"Row {0}: For Supplier {1}, Email Address is Required to send an email",ረድፍ {0} ለአቅራቢ {1} ኢሜል ለመላክ የኢሜል አድራሻ ያስፈልጋል,

Can't render this file because it is too large.

Some files were not shown because too many files have changed in this diff Show More