Merge pull request #26261 from ruchamahabal/org-chart
feat: Organizational Chart
diff --git a/.github/helper/semgrep_rules/frappe_correctness.yml b/.github/helper/semgrep_rules/frappe_correctness.yml
index faab334..d9603e8 100644
--- a/.github/helper/semgrep_rules/frappe_correctness.yml
+++ b/.github/helper/semgrep_rules/frappe_correctness.yml
@@ -98,8 +98,6 @@
languages: [python]
severity: WARNING
paths:
- exclude:
- - test_*.py
include:
- "*/**/doctype/*"
diff --git a/.github/helper/semgrep_rules/security.yml b/.github/helper/semgrep_rules/security.yml
index 5a5098b..8b21979 100644
--- a/.github/helper/semgrep_rules/security.yml
+++ b/.github/helper/semgrep_rules/security.yml
@@ -8,18 +8,3 @@
dynamic content. Avoid it or use safe_eval().
languages: [python]
severity: ERROR
-
-- id: frappe-sqli-format-strings
- patterns:
- - pattern-inside: |
- @frappe.whitelist()
- def $FUNC(...):
- ...
- - pattern-either:
- - pattern: frappe.db.sql("..." % ...)
- - pattern: frappe.db.sql(f"...", ...)
- - pattern: frappe.db.sql("...".format(...), ...)
- message: |
- Detected use of raw string formatting for SQL queries. This can lead to sql injection vulnerabilities. Refer security guidelines - https://github.com/frappe/erpnext/wiki/Code-Security-Guidelines
- languages: [python]
- severity: WARNING
diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml
index 7c6b843..1d180f2 100644
--- a/.github/workflows/backport.yml
+++ b/.github/workflows/backport.yml
@@ -1,16 +1,25 @@
name: Backport
on:
- pull_request:
+ pull_request_target:
types:
- closed
- labeled
jobs:
- backport:
- runs-on: ubuntu-18.04
- name: Backport
+ main:
+ runs-on: ubuntu-latest
steps:
- - name: Backport
- uses: tibdex/backport@v1
+ - name: Checkout Actions
+ uses: actions/checkout@v2
with:
- github_token: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
+ repository: "frappe/backport"
+ path: ./actions
+ ref: develop
+ - name: Install Actions
+ run: npm install --production --prefix ./actions
+ - name: Run backport
+ uses: ./actions/backport
+ with:
+ token: ${{secrets.BACKPORT_BOT_TOKEN}}
+ labelsToAdd: "backport"
+ title: "{{originalTitle}}"
diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml
index 389524e..e27b406 100644
--- a/.github/workflows/semgrep.yml
+++ b/.github/workflows/semgrep.yml
@@ -1,34 +1,18 @@
name: Semgrep
on:
- pull_request:
- branches:
- - develop
- - version-13-hotfix
- - version-13-pre-release
+ pull_request: { }
+
jobs:
semgrep:
name: Frappe Linter
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - name: Setup python3
- uses: actions/setup-python@v2
- with:
- python-version: 3.8
-
- - name: Setup semgrep
- run: |
- python -m pip install -q semgrep
- git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q
-
- - name: Semgrep errors
- run: |
- files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF)
- [[ -d .github/helper/semgrep_rules ]] && semgrep --severity ERROR --config=.github/helper/semgrep_rules --quiet --error $files
- semgrep --config="r/python.lang.correctness" --quiet --error $files
-
- - name: Semgrep warnings
- run: |
- files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF)
- [[ -d .github/helper/semgrep_rules ]] && semgrep --severity WARNING --severity INFO --config=.github/helper/semgrep_rules --quiet $files
+ - uses: actions/checkout@v2
+ - uses: returntocorp/semgrep-action@v1
+ env:
+ SEMGREP_TIMEOUT: 120
+ with:
+ config: >-
+ r/python.lang.correctness
+ .github/helper/semgrep_rules
diff --git a/CODEOWNERS b/CODEOWNERS
index 7cf65a7..a4a14de 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -3,16 +3,33 @@
# These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence,
-manufacturing/ @rohitwaghchaure @marination
-accounts/ @deepeshgarg007 @nextchamp-saqib
-loan_management/ @deepeshgarg007 @rohitwaghchaure
-pos* @nextchamp-saqib @rohitwaghchaure
-assets/ @nextchamp-saqib @deepeshgarg007
-stock/ @marination @rohitwaghchaure
-buying/ @marination @deepeshgarg007
-hr/ @Anurag810 @rohitwaghchaure
-projects/ @hrwX @nextchamp-saqib
-support/ @hrwX @marination
-healthcare/ @ruchamahabal @marination
-erpnext_integrations/ @Mangesh-Khairnar @nextchamp-saqib
-requirements.txt @gavindsouza
+erpnext/accounts/ @nextchamp-saqib @deepeshgarg007
+erpnext/assets/ @nextchamp-saqib @deepeshgarg007
+erpnext/erpnext_integrations/ @nextchamp-saqib
+erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007
+erpnext/regional @nextchamp-saqib @deepeshgarg007
+erpnext/selling @nextchamp-saqib @deepeshgarg007
+erpnext/support/ @nextchamp-saqib @deepeshgarg007
+pos* @nextchamp-saqib
+
+erpnext/buying/ @marination @rohitwaghchaure @ankush
+erpnext/e_commerce/ @marination
+erpnext/maintenance/ @marination @rohitwaghchaure
+erpnext/manufacturing/ @marination @rohitwaghchaure @ankush
+erpnext/portal/ @marination
+erpnext/quality_management/ @marination @rohitwaghchaure
+erpnext/shopping_cart/ @marination
+erpnext/stock/ @marination @rohitwaghchaure @ankush
+
+erpnext/crm/ @ruchamahabal @pateljannat
+erpnext/education/ @ruchamahabal @pateljannat
+erpnext/healthcare/ @ruchamahabal @pateljannat @chillaranand
+erpnext/hr/ @ruchamahabal @pateljannat
+erpnext/non_profit/ @ruchamahabal
+erpnext/payroll @ruchamahabal @pateljannat
+erpnext/projects/ @ruchamahabal @pateljannat
+
+erpnext/controllers @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination
+
+.github/ @surajshetty3416 @ankush
+requirements.txt @gavindsouza
diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py
index 1be2fbf..f763df0 100644
--- a/erpnext/accounts/doctype/account/account.py
+++ b/erpnext/accounts/doctype/account/account.py
@@ -230,7 +230,7 @@
if self.check_gle_exists():
throw(_("Account with existing transaction can not be converted to group."))
elif self.account_type and not self.flags.exclude_account_type_check:
- throw(_("Cannot covert to Group because Account Type is selected."))
+ throw(_("Cannot convert to Group because Account Type is selected."))
else:
self.is_group = 1
self.save()
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index 703e93c..49a2afe 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -19,6 +19,7 @@
"book_asset_depreciation_entry_automatically",
"unlink_advance_payment_on_cancelation_of_order",
"post_change_gl_entries",
+ "enable_discount_accounting",
"tax_settings_section",
"determine_address_tax_category_from",
"column_break_19",
@@ -261,6 +262,13 @@
"fieldname": "post_change_gl_entries",
"fieldtype": "Check",
"label": "Create Ledger Entries for Change Amount"
+ },
+ {
+ "default": "0",
+ "description": "If enabled, additional ledger entries will be made for discounts in a separate Discount Account",
+ "fieldname": "enable_discount_accounting",
+ "fieldtype": "Check",
+ "label": "Enable Discount Accounting"
}
],
"icon": "icon-cog",
@@ -268,7 +276,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2021-06-17 20:26:03.721202",
+ "modified": "2021-07-12 18:54:29.084958",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
index ac4a2d6..5544913 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
@@ -21,6 +21,7 @@
self.validate_stale_days()
self.enable_payment_schedule_in_print()
+ self.toggle_discount_accounting_fields()
def validate_stale_days(self):
if not self.allow_stale and cint(self.stale_days) <= 0:
@@ -33,3 +34,22 @@
for doctype in ("Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"):
make_property_setter(doctype, "due_date", "print_hide", show_in_print, "Check", validate_fields_for_doctype=False)
make_property_setter(doctype, "payment_schedule", "print_hide", 0 if show_in_print else 1, "Check", validate_fields_for_doctype=False)
+
+ def toggle_discount_accounting_fields(self):
+ enable_discount_accounting = cint(self.enable_discount_accounting)
+
+ for doctype in ["Sales Invoice Item", "Purchase Invoice Item"]:
+ make_property_setter(doctype, "discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)
+ if enable_discount_accounting:
+ make_property_setter(doctype, "discount_account", "mandatory_depends_on", "eval: doc.discount_amount", "Code", validate_fields_for_doctype=False)
+ else:
+ make_property_setter(doctype, "discount_account", "mandatory_depends_on", "", "Code", validate_fields_for_doctype=False)
+
+ for doctype in ["Sales Invoice", "Purchase Invoice"]:
+ make_property_setter(doctype, "additional_discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)
+ if enable_discount_accounting:
+ make_property_setter(doctype, "additional_discount_account", "mandatory_depends_on", "eval: doc.discount_amount", "Code", validate_fields_for_doctype=False)
+ else:
+ make_property_setter(doctype, "additional_discount_account", "mandatory_depends_on", "", "Code", validate_fields_for_doctype=False)
+
+ make_property_setter("Item", "default_discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/budget/test_budget.py b/erpnext/accounts/doctype/budget/test_budget.py
index 603e21e..6c25f00 100644
--- a/erpnext/accounts/doctype/budget/test_budget.py
+++ b/erpnext/accounts/doctype/budget/test_budget.py
@@ -249,7 +249,7 @@
def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None):
if budget_against_field == "project":
- budget_against = "_Test Project"
+ budget_against = frappe.db.get_value("Project", {"project_name": "_Test Project"})
else:
budget_against = budget_against_CC or "_Test Cost Center - _TC"
@@ -275,7 +275,7 @@
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
elif budget_against_field == "project":
make_journal_entry("_Test Account Cost for Goods Sold - _TC",
- "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project="_Test Project", posting_date=nowdate())
+ "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project=budget_against, posting_date=nowdate())
def make_budget(**args):
args = frappe._dict(args)
diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
index 4fd8413..8456b49 100644
--- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
+++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
@@ -391,5 +391,5 @@
})
company.save()
- install_country_fixtures(company.name)
+ install_country_fixtures(company.name, company.country)
company.create_default_tax_template()
diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
index 5619321..f2b0a8c 100644
--- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
+++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
@@ -27,6 +27,9 @@
if not (self.company and self.posting_date):
frappe.throw(_("Please select Company and Posting Date to getting entries"))
+ def on_cancel(self):
+ self.ignore_linked_doctypes = ('GL Entry')
+
@frappe.whitelist()
def check_journal_entry_condition(self):
total_debit = frappe.db.get_value("Journal Entry Account", {
@@ -99,10 +102,12 @@
sum(debit) - sum(credit) as balance
from `tabGL Entry`
where account in (%s)
- group by account, party_type, party
+ and posting_date <= %s
+ and is_cancelled = 0
+ group by account, NULLIF(party_type,''), NULLIF(party,'')
having sum(debit) != sum(credit)
order by account
- """ % ', '.join(['%s']*len(accounts)), tuple(accounts), as_dict=1)
+ """ % (', '.join(['%s']*len(accounts)), '%s'), tuple(accounts + [self.posting_date]), as_dict=1)
return account_details
@@ -143,9 +148,9 @@
"party_type": d.get("party_type"),
"party": d.get("party"),
"account_currency": d.get("account_currency"),
- "balance": d.get("balance_in_account_currency"),
- dr_or_cr: abs(d.get("balance_in_account_currency")),
- "exchange_rate":d.get("new_exchange_rate"),
+ "balance": flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")),
+ dr_or_cr: flt(abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")),
+ "exchange_rate": flt(d.get("new_exchange_rate"), d.precision("new_exchange_rate")),
"reference_type": "Exchange Rate Revaluation",
"reference_name": self.name,
})
@@ -154,9 +159,9 @@
"party_type": d.get("party_type"),
"party": d.get("party"),
"account_currency": d.get("account_currency"),
- "balance": d.get("balance_in_account_currency"),
- reverse_dr_or_cr: abs(d.get("balance_in_account_currency")),
- "exchange_rate": d.get("current_exchange_rate"),
+ "balance": flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")),
+ reverse_dr_or_cr: flt(abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")),
+ "exchange_rate": flt(d.get("current_exchange_rate"), d.precision("current_exchange_rate")),
"reference_type": "Exchange Rate Revaluation",
"reference_name": self.name
})
@@ -185,9 +190,9 @@
account_details = {}
company_currency = erpnext.get_company_currency(company)
- balance = get_balance_on(account, party_type=party_type, party=party, in_account_currency=False)
+ balance = get_balance_on(account, date=posting_date, party_type=party_type, party=party, in_account_currency=False)
if balance:
- balance_in_account_currency = get_balance_on(account, party_type=party_type, party=party)
+ balance_in_account_currency = get_balance_on(account, date=posting_date, party_type=party_type, party=party)
current_exchange_rate = balance / balance_in_account_currency if balance_in_account_currency else 0
new_exchange_rate = get_exchange_rate(account_currency, company_currency, posting_date)
new_balance_in_base_currency = balance_in_account_currency * new_exchange_rate
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index d3ac3a6..439b1ed 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -7,6 +7,8 @@
frappe.ui.form.on('Payment Entry', {
onload: function(frm) {
+ frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice'];
+
if(frm.doc.__islocal) {
if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null);
if (!frm.doc.paid_to) frm.set_value("paid_to_account_currency", null);
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json
index 51f18a5..6f362c1 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.json
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json
@@ -667,6 +667,7 @@
{
"fieldname": "base_paid_amount_after_tax",
"fieldtype": "Currency",
+ "hidden": 1,
"label": "Paid Amount After Tax (Company Currency)",
"options": "Company:company:default_currency",
"read_only": 1
@@ -693,21 +694,25 @@
"depends_on": "eval:doc.received_amount && doc.payment_type != 'Internal Transfer'",
"fieldname": "received_amount_after_tax",
"fieldtype": "Currency",
+ "hidden": 1,
"label": "Received Amount After Tax",
- "options": "paid_to_account_currency"
+ "options": "paid_to_account_currency",
+ "read_only": 1
},
{
"depends_on": "doc.received_amount",
"fieldname": "base_received_amount_after_tax",
"fieldtype": "Currency",
+ "hidden": 1,
"label": "Received Amount After Tax (Company Currency)",
- "options": "Company:company:default_currency"
+ "options": "Company:company:default_currency",
+ "read_only": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2021-06-22 20:37:06.154206",
+ "modified": "2021-07-09 08:58:15.008761",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index ff00fde..46904f7 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -411,9 +411,15 @@
if not self.advance_tax_account:
frappe.throw(_("Advance TDS account is mandatory for advance TDS deduction"))
- reference_doclist = []
net_total = self.paid_amount
- included_in_paid_amount = 0
+
+ for reference in self.get("references"):
+ net_total_for_tds = 0
+ if reference.reference_doctype == 'Purchase Order':
+ net_total_for_tds += flt(frappe.db.get_value('Purchase Order', reference.reference_name, 'net_total'))
+
+ if net_total_for_tds:
+ net_total = net_total_for_tds
# Adding args as purchase invoice to get TDS amount
args = frappe._dict({
@@ -430,7 +436,7 @@
return
tax_withholding_details.update({
- 'included_in_paid_amount': included_in_paid_amount,
+ 'add_deduct_tax': 'Add',
'cost_center': self.cost_center or erpnext.get_default_cost_center(self.company)
})
@@ -519,16 +525,19 @@
self.unallocated_amount = 0
if self.party:
total_deductions = sum(flt(d.amount) for d in self.get("deductions"))
+ included_taxes = self.get_included_taxes()
if self.payment_type == "Receive" \
- and self.base_total_allocated_amount < self.base_received_amount_after_tax + total_deductions \
- and self.total_allocated_amount < self.paid_amount_after_tax + (total_deductions / self.source_exchange_rate):
- self.unallocated_amount = (self.received_amount_after_tax + total_deductions -
+ and self.base_total_allocated_amount < self.base_received_amount + total_deductions \
+ and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate):
+ self.unallocated_amount = (self.received_amount + total_deductions -
self.base_total_allocated_amount) / self.source_exchange_rate
+ self.unallocated_amount -= included_taxes
elif self.payment_type == "Pay" \
- and self.base_total_allocated_amount < (self.base_paid_amount_after_tax - total_deductions) \
- and self.total_allocated_amount < self.received_amount_after_tax + (total_deductions / self.target_exchange_rate):
- self.unallocated_amount = (self.base_paid_amount_after_tax - (total_deductions +
+ and self.base_total_allocated_amount < (self.base_paid_amount - total_deductions) \
+ and self.total_allocated_amount < self.received_amount + (total_deductions / self.target_exchange_rate):
+ self.unallocated_amount = (self.base_paid_amount - (total_deductions +
self.base_total_allocated_amount)) / self.target_exchange_rate
+ self.unallocated_amount -= included_taxes
def set_difference_amount(self):
base_unallocated_amount = flt(self.unallocated_amount) * (flt(self.source_exchange_rate)
@@ -537,17 +546,29 @@
base_party_amount = flt(self.base_total_allocated_amount) + flt(base_unallocated_amount)
if self.payment_type == "Receive":
- self.difference_amount = base_party_amount - self.base_received_amount_after_tax
+ self.difference_amount = base_party_amount - self.base_received_amount
elif self.payment_type == "Pay":
- self.difference_amount = self.base_paid_amount_after_tax - base_party_amount
+ self.difference_amount = self.base_paid_amount - base_party_amount
else:
- self.difference_amount = self.base_paid_amount_after_tax - flt(self.base_received_amount_after_tax)
+ self.difference_amount = self.base_paid_amount - flt(self.base_received_amount)
total_deductions = sum(flt(d.amount) for d in self.get("deductions"))
+ included_taxes = self.get_included_taxes()
- self.difference_amount = flt(self.difference_amount - total_deductions,
+ self.difference_amount = flt(self.difference_amount - total_deductions - included_taxes,
self.precision("difference_amount"))
+ def get_included_taxes(self):
+ included_taxes = 0
+ for tax in self.get('taxes'):
+ if tax.included_in_paid_amount:
+ if tax.add_deduct_tax == 'Add':
+ included_taxes += tax.base_tax_amount
+ else:
+ included_taxes -= tax.base_tax_amount
+
+ return included_taxes
+
# Paid amount is auto allocated in the reference document by default.
# Clear the reference document which doesn't have allocated amount on validate so that form can be loaded fast
def clear_unallocated_reference_document_rows(self):
@@ -690,8 +711,8 @@
"account": self.paid_from,
"account_currency": self.paid_from_account_currency,
"against": self.party if self.payment_type=="Pay" else self.paid_to,
- "credit_in_account_currency": self.paid_amount_after_tax,
- "credit": self.base_paid_amount_after_tax,
+ "credit_in_account_currency": self.paid_amount,
+ "credit": self.base_paid_amount,
"cost_center": self.cost_center
}, item=self)
)
@@ -701,8 +722,8 @@
"account": self.paid_to,
"account_currency": self.paid_to_account_currency,
"against": self.party if self.payment_type=="Receive" else self.paid_from,
- "debit_in_account_currency": self.received_amount_after_tax,
- "debit": self.base_received_amount_after_tax,
+ "debit_in_account_currency": self.received_amount,
+ "debit": self.base_received_amount,
"cost_center": self.cost_center
}, item=self)
)
@@ -715,35 +736,42 @@
if self.payment_type in ('Pay', 'Internal Transfer'):
dr_or_cr = "debit" if d.add_deduct_tax == "Add" else "credit"
+ against = self.party or self.paid_from
elif self.payment_type == 'Receive':
dr_or_cr = "credit" if d.add_deduct_tax == "Add" else "debit"
+ against = self.party or self.paid_to
payment_or_advance_account = self.get_party_account_for_taxes()
+ tax_amount = d.tax_amount
+ base_tax_amount = d.base_tax_amount
+
+ if self.advance_tax_account:
+ tax_amount = -1 * tax_amount
+ base_tax_amount = -1 * base_tax_amount
gl_entries.append(
self.get_gl_dict({
"account": d.account_head,
- "against": self.party if self.payment_type=="Receive" else self.paid_from,
- dr_or_cr: d.base_tax_amount,
- dr_or_cr + "_in_account_currency": d.base_tax_amount
+ "against": against,
+ dr_or_cr: tax_amount,
+ dr_or_cr + "_in_account_currency": base_tax_amount
if account_currency==self.company_currency
else d.tax_amount,
"cost_center": d.cost_center
}, account_currency, item=d))
#Intentionally use -1 to get net values in party account
- gl_entries.append(
- self.get_gl_dict({
- "account": payment_or_advance_account,
- "against": self.party if self.payment_type=="Receive" else self.paid_from,
- dr_or_cr: -1 * d.base_tax_amount,
- dr_or_cr + "_in_account_currency": -1*d.base_tax_amount
- if account_currency==self.company_currency
- else d.tax_amount,
- "cost_center": self.cost_center,
- "party_type": self.party_type,
- "party": self.party
- }, account_currency, item=d))
+ if not d.included_in_paid_amount or self.advance_tax_account:
+ gl_entries.append(
+ self.get_gl_dict({
+ "account": payment_or_advance_account,
+ "against": against,
+ dr_or_cr: -1 * tax_amount,
+ dr_or_cr + "_in_account_currency": -1 * base_tax_amount
+ if account_currency==self.company_currency
+ else d.tax_amount,
+ "cost_center": self.cost_center,
+ }, account_currency, item=d))
def add_deductions_gl_entries(self, gl_entries):
for d in self.get("deductions"):
@@ -767,9 +795,9 @@
if self.advance_tax_account:
return self.advance_tax_account
elif self.payment_type == 'Receive':
- return self.paid_from
- elif self.payment_type in ('Pay', 'Internal Transfer'):
return self.paid_to
+ elif self.payment_type in ('Pay', 'Internal Transfer'):
+ return self.paid_from
def update_advance_paid(self):
if self.payment_type in ("Receive", "Pay") and self.party:
@@ -1648,12 +1676,6 @@
if dt == "Employee Advance":
paid_amount = received_amount * doc.get('exchange_rate', 1)
- if dt == "Purchase Order" and doc.apply_tds:
- if party_account_currency == bank.account_currency:
- paid_amount = received_amount = doc.base_net_total
- else:
- paid_amount = received_amount = doc.base_net_total * doc.get('exchange_rate', 1)
-
return paid_amount, received_amount
def apply_early_payment_discount(paid_amount, received_amount, doc):
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index 6635128..d788d91 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -306,5 +306,5 @@
}
]
})
-
+ jv.flags.ignore_mandatory = True
jv.submit()
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
index 7459c11..33c3e04 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
@@ -1545,6 +1545,7 @@
"fieldname": "consolidated_invoice",
"fieldtype": "Link",
"label": "Consolidated Sales Invoice",
+ "no_copy": 1,
"options": "Sales Invoice",
"read_only": 1
}
@@ -1552,7 +1553,7 @@
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
- "modified": "2021-02-01 15:03:33.800707",
+ "modified": "2021-07-29 13:37:20.636171",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice",
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
index 428989a..0be41b4 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
@@ -558,7 +558,8 @@
"description": "Simple Python Expression, Example: territory != 'All Territories'",
"fieldname": "condition",
"fieldtype": "Code",
- "label": "Condition"
+ "label": "Condition",
+ "options": "PythonExpression"
},
{
"fieldname": "column_break_42",
@@ -575,7 +576,7 @@
"icon": "fa fa-gift",
"idx": 1,
"links": [],
- "modified": "2021-03-06 22:01:24.840422",
+ "modified": "2021-08-06 15:10:04.219321",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule",
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index b54d0e7..94abf3b 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -168,7 +168,7 @@
frappe.throw(_("Invalid {0}").format(args.get(field)))
parent_groups = frappe.db.sql_list("""select name from `tab%s`
- where lft>=%s and rgt<=%s""" % (parenttype, '%s', '%s'), (lft, rgt))
+ where lft<=%s and rgt>=%s""" % (parenttype, '%s', '%s'), (lft, rgt))
if parenttype in ["Customer Group", "Item Group", "Territory"]:
parent_field = "parent_{0}".format(frappe.scrub(parenttype))
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index dc9094c..4a77ef0 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -134,7 +134,7 @@
},
get_query_filters: {
docstatus: 1,
- status: ["not in", ["Closed", "Completed"]],
+ status: ["not in", ["Closed", "Completed", "Return Issued"]],
company: me.frm.doc.company,
is_return: 0
}
@@ -275,7 +275,7 @@
// Do not update if inter company reference is there as the details will already be updated
if(this.frm.updating_party_details || this.frm.doc.inter_company_invoice_reference)
return;
-
+
erpnext.utils.get_party_details(this.frm, "erpnext.accounts.party.get_party_details",
{
posting_date: this.frm.doc.posting_date,
@@ -283,7 +283,8 @@
party: this.frm.doc.supplier,
party_type: "Supplier",
account: this.frm.doc.credit_to,
- price_list: this.frm.doc.buying_price_list
+ price_list: this.frm.doc.buying_price_list,
+ fetch_payment_terms_template: cint(!this.frm.doc.ignore_default_payment_terms_template)
}, function() {
me.apply_pricing_rule();
me.frm.doc.apply_tds = me.frm.supplier_tds ? 1 : 0;
@@ -365,7 +366,7 @@
items_add: function(doc, cdt, cdn) {
var row = frappe.get_doc(cdt, cdn);
this.frm.script_manager.copy_from_first_row("items", row,
- ["expense_account", "cost_center", "project"]);
+ ["expense_account", "discount_account", "cost_center", "project"]);
},
on_submit: function() {
@@ -499,6 +500,16 @@
'Payment Entry': 'Payment'
}
+ frm.set_query("additional_discount_account", function() {
+ return {
+ filters: {
+ company: frm.doc.company,
+ is_group: 0,
+ report_type: "Profit and Loss",
+ }
+ };
+ });
+
frm.fields_dict['items'].grid.get_field('deferred_expense_account').get_query = function(doc) {
return {
filters: {
@@ -508,6 +519,16 @@
}
}
}
+
+ frm.fields_dict['items'].grid.get_field('discount_account').get_query = function(doc) {
+ return {
+ filters: {
+ 'report_type': 'Profit and Loss',
+ 'company': doc.company,
+ "is_group": 0
+ }
+ }
+ }
},
refresh: function(frm) {
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index 00ef7d5..7025dd9 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -96,6 +96,7 @@
"section_break_44",
"apply_discount_on",
"base_discount_amount",
+ "additional_discount_account",
"column_break_46",
"additional_discount_percentage",
"discount_amount",
@@ -131,6 +132,7 @@
"advances",
"payment_schedule_section",
"payment_terms_template",
+ "ignore_default_payment_terms_template",
"payment_schedule",
"terms_section_break",
"tc_name",
@@ -175,7 +177,9 @@
"hidden": 1,
"label": "Title",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "naming_series",
@@ -187,7 +191,9 @@
"options": "ACC-PINV-.YYYY.-\nACC-PINV-RET-.YYYY.-",
"print_hide": 1,
"reqd": 1,
- "set_only_once": 1
+ "set_only_once": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "supplier",
@@ -199,7 +205,9 @@
"options": "Supplier",
"print_hide": 1,
"reqd": 1,
- "search_index": 1
+ "search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"bold": 1,
@@ -211,7 +219,9 @@
"label": "Supplier Name",
"oldfieldname": "supplier_name",
"oldfieldtype": "Data",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fetch_from": "supplier.tax_id",
@@ -219,21 +229,27 @@
"fieldtype": "Read Only",
"label": "Tax Id",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "due_date",
"fieldtype": "Date",
"label": "Due Date",
"oldfieldname": "due_date",
- "oldfieldtype": "Date"
+ "oldfieldtype": "Date",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "is_paid",
"fieldtype": "Check",
"label": "Is Paid",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -241,19 +257,25 @@
"fieldtype": "Check",
"label": "Is Return (Debit Note)",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "apply_tds",
"fieldtype": "Check",
"label": "Apply Tax Withholding Amount",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break1",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -263,13 +285,17 @@
"label": "Company",
"options": "Company",
"print_hide": 1,
- "remember_last_selected_value": 1
+ "remember_last_selected_value": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
- "options": "Cost Center"
+ "options": "Cost Center",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "Today",
@@ -281,7 +307,9 @@
"oldfieldtype": "Date",
"print_hide": 1,
"reqd": 1,
- "search_index": 1
+ "search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "posting_time",
@@ -290,6 +318,8 @@
"no_copy": 1,
"print_hide": 1,
"print_width": "100px",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "100px"
},
{
@@ -298,7 +328,9 @@
"fieldname": "set_posting_time",
"fieldtype": "Check",
"label": "Edit Posting Date and Time",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "amended_from",
@@ -310,44 +342,58 @@
"oldfieldtype": "Link",
"options": "Purchase Invoice",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "eval:doc.on_hold",
"fieldname": "sb_14",
"fieldtype": "Section Break",
- "label": "Hold Invoice"
+ "label": "Hold Invoice",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "on_hold",
"fieldtype": "Check",
- "label": "Hold Invoice"
+ "label": "Hold Invoice",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.on_hold",
"description": "Once set, this invoice will be on hold till the set date",
"fieldname": "release_date",
"fieldtype": "Date",
- "label": "Release Date"
+ "label": "Release Date",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "cb_17",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.on_hold",
"fieldname": "hold_comment",
"fieldtype": "Small Text",
- "label": "Reason For Putting On Hold"
+ "label": "Reason For Putting On Hold",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "bill_no",
"fieldname": "supplier_invoice_details",
"fieldtype": "Section Break",
- "label": "Supplier Invoice Details"
+ "label": "Supplier Invoice Details",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "bill_no",
@@ -355,11 +401,15 @@
"label": "Supplier Invoice No",
"oldfieldname": "bill_no",
"oldfieldtype": "Data",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_15",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "bill_date",
@@ -368,13 +418,17 @@
"no_copy": 1,
"oldfieldname": "bill_date",
"oldfieldtype": "Date",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "return_against",
"fieldname": "returns",
"fieldtype": "Section Break",
- "label": "Returns"
+ "label": "Returns",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "return_against",
@@ -384,26 +438,34 @@
"no_copy": 1,
"options": "Purchase Invoice",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "section_addresses",
"fieldtype": "Section Break",
- "label": "Address and Contact"
+ "label": "Address and Contact",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "supplier_address",
"fieldtype": "Link",
"label": "Select Supplier Address",
"options": "Address",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "address_display",
"fieldtype": "Small Text",
"label": "Address",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_person",
@@ -411,51 +473,67 @@
"in_global_search": 1,
"label": "Contact Person",
"options": "Contact",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_display",
"fieldtype": "Small Text",
"label": "Contact",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_mobile",
"fieldtype": "Small Text",
"label": "Mobile No",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_email",
"fieldtype": "Small Text",
"label": "Contact Email",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "col_break_address",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_address",
"fieldtype": "Link",
"label": "Select Shipping Address",
"options": "Address",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_address_display",
"fieldtype": "Small Text",
"label": "Shipping Address",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "currency_and_price_list",
"fieldtype": "Section Break",
"label": "Currency and Price List",
- "options": "fa fa-tag"
+ "options": "fa fa-tag",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "currency",
@@ -464,7 +542,9 @@
"oldfieldname": "currency",
"oldfieldtype": "Select",
"options": "Currency",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "conversion_rate",
@@ -473,18 +553,24 @@
"oldfieldname": "conversion_rate",
"oldfieldtype": "Currency",
"precision": "9",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break2",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "buying_price_list",
"fieldtype": "Link",
"label": "Price List",
"options": "Price List",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "price_list_currency",
@@ -492,14 +578,18 @@
"label": "Price List Currency",
"options": "Currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "plc_conversion_rate",
"fieldtype": "Float",
"label": "Price List Exchange Rate",
"precision": "9",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -508,11 +598,15 @@
"label": "Ignore Pricing Rule",
"no_copy": 1,
"permlevel": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "sec_warehouse",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "update_stock",
@@ -521,7 +615,9 @@
"fieldtype": "Link",
"label": "Set Accepted Warehouse",
"options": "Warehouse",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "update_stock",
@@ -531,11 +627,15 @@
"label": "Rejected Warehouse",
"no_copy": 1,
"options": "Warehouse",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "col_break_warehouse",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "No",
@@ -543,25 +643,33 @@
"fieldtype": "Select",
"label": "Raw Materials Supplied",
"options": "No\nYes",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "items_section",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-shopping-cart"
+ "options": "fa fa-shopping-cart",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "update_stock",
"fieldtype": "Check",
"label": "Update Stock",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "scan_barcode",
"fieldtype": "Data",
- "label": "Scan Barcode"
+ "label": "Scan Barcode",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_bulk_edit": 1,
@@ -571,25 +679,33 @@
"oldfieldname": "entries",
"oldfieldtype": "Table",
"options": "Purchase Invoice Item",
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "pricing_rule_details",
"fieldtype": "Section Break",
- "label": "Pricing Rules"
+ "label": "Pricing Rules",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "pricing_rules",
"fieldtype": "Table",
"label": "Pricing Rule Detail",
"options": "Pricing Rule Detail",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible_depends_on": "supplied_items",
"fieldname": "raw_materials_supplied",
"fieldtype": "Section Break",
- "label": "Raw Materials Supplied"
+ "label": "Raw Materials Supplied",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "update_stock",
@@ -597,17 +713,23 @@
"fieldtype": "Table",
"label": "Supplied Items",
"no_copy": 1,
- "options": "Purchase Receipt Item Supplied"
+ "options": "Purchase Receipt Item Supplied",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "section_break_26",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_qty",
"fieldtype": "Float",
"label": "Total Quantity",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_total",
@@ -615,7 +737,9 @@
"label": "Total (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_net_total",
@@ -625,18 +749,24 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_28",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total",
"fieldtype": "Currency",
"label": "Total",
"options": "currency",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "net_total",
@@ -646,42 +776,56 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_net_weight",
"fieldtype": "Float",
"label": "Total Net Weight",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_section",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-money"
+ "options": "fa fa-money",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "tax_category",
"fieldtype": "Link",
"label": "Tax Category",
"options": "Tax Category",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_49",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_rule",
"fieldtype": "Link",
"label": "Shipping Rule",
"options": "Shipping Rule",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "section_break_51",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_and_charges",
@@ -690,7 +834,9 @@
"oldfieldname": "purchase_other_charges",
"oldfieldtype": "Link",
"options": "Purchase Taxes and Charges Template",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes",
@@ -698,13 +844,17 @@
"label": "Purchase Taxes and Charges",
"oldfieldname": "purchase_tax_details",
"oldfieldtype": "Table",
- "options": "Purchase Taxes and Charges"
+ "options": "Purchase Taxes and Charges",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "sec_tax_breakup",
"fieldtype": "Section Break",
- "label": "Tax Breakup"
+ "label": "Tax Breakup",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "other_charges_calculation",
@@ -713,13 +863,17 @@
"no_copy": 1,
"oldfieldtype": "HTML",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "totals",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-money"
+ "options": "fa fa-money",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_taxes_and_charges_added",
@@ -729,7 +883,9 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_taxes_and_charges_deducted",
@@ -739,7 +895,9 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_total_taxes_and_charges",
@@ -749,11 +907,15 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_40",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_and_charges_added",
@@ -763,7 +925,9 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_and_charges_deducted",
@@ -773,7 +937,9 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_taxes_and_charges",
@@ -781,14 +947,18 @@
"label": "Total Taxes and Charges",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "discount_amount",
"fieldname": "section_break_44",
"fieldtype": "Section Break",
- "label": "Additional Discount"
+ "label": "Additional Discount",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "Grand Total",
@@ -796,7 +966,9 @@
"fieldtype": "Select",
"label": "Apply Additional Discount On",
"options": "\nGrand Total\nNet Total",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_discount_amount",
@@ -804,28 +976,38 @@
"label": "Additional Discount Amount (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_46",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "additional_discount_percentage",
"fieldtype": "Float",
"label": "Additional Discount Percentage",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "discount_amount",
"fieldtype": "Currency",
"label": "Additional Discount Amount",
"options": "currency",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "section_break_49",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_grand_total",
@@ -835,7 +1017,9 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:!doc.disable_rounded_total",
@@ -845,7 +1029,9 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:!doc.disable_rounded_total",
@@ -855,7 +1041,9 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_in_words",
@@ -865,13 +1053,17 @@
"oldfieldname": "in_words",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break8",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -882,7 +1074,9 @@
"oldfieldname": "grand_total_import",
"oldfieldtype": "Currency",
"options": "currency",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:!doc.disable_rounded_total",
@@ -892,7 +1086,9 @@
"no_copy": 1,
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:!doc.disable_rounded_total",
@@ -902,7 +1098,9 @@
"no_copy": 1,
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "in_words",
@@ -912,7 +1110,9 @@
"oldfieldname": "in_words_import",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_advance",
@@ -923,7 +1123,9 @@
"oldfieldtype": "Currency",
"options": "party_account_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "outstanding_amount",
@@ -934,14 +1136,18 @@
"oldfieldtype": "Currency",
"options": "party_account_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"depends_on": "grand_total",
"fieldname": "disable_rounded_total",
"fieldtype": "Check",
- "label": "Disable Rounded Total"
+ "label": "Disable Rounded Total",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -949,20 +1155,26 @@
"depends_on": "eval:doc.is_paid===1||(doc.advances && doc.advances.length>0)",
"fieldname": "payments_section",
"fieldtype": "Section Break",
- "label": "Payments"
+ "label": "Payments",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "mode_of_payment",
"fieldtype": "Link",
"label": "Mode of Payment",
"options": "Mode of Payment",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "cash_bank_account",
"fieldtype": "Link",
"label": "Cash/Bank Account",
- "options": "Account"
+ "options": "Account",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "clearance_date",
@@ -970,11 +1182,15 @@
"label": "Clearance Date",
"no_copy": 1,
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "col_br_payments",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "is_paid",
@@ -983,7 +1199,9 @@
"label": "Paid Amount",
"no_copy": 1,
"options": "currency",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_paid_amount",
@@ -992,7 +1210,9 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -1000,7 +1220,9 @@
"depends_on": "grand_total",
"fieldname": "write_off",
"fieldtype": "Section Break",
- "label": "Write Off"
+ "label": "Write Off",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "write_off_amount",
@@ -1008,7 +1230,9 @@
"label": "Write Off Amount",
"no_copy": 1,
"options": "currency",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_write_off_amount",
@@ -1017,11 +1241,15 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_61",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:flt(doc.write_off_amount)!=0",
@@ -1029,7 +1257,9 @@
"fieldtype": "Link",
"label": "Write Off Account",
"options": "Account",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:flt(doc.write_off_amount)!=0",
@@ -1037,7 +1267,9 @@
"fieldtype": "Link",
"label": "Write Off Cost Center",
"options": "Cost Center",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -1047,13 +1279,17 @@
"label": "Advance Payments",
"oldfieldtype": "Section Break",
"options": "fa fa-money",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "allocate_advances_automatically",
"fieldtype": "Check",
- "label": "Set Advances and Allocate (FIFO)"
+ "label": "Set Advances and Allocate (FIFO)",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:!doc.allocate_advances_automatically",
@@ -1061,7 +1297,9 @@
"fieldtype": "Button",
"label": "Get Advances Paid",
"oldfieldtype": "Button",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "advances",
@@ -1071,20 +1309,26 @@
"oldfieldname": "advance_allocation_details",
"oldfieldtype": "Table",
"options": "Purchase Invoice Advance",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "eval:(!doc.is_return)",
"fieldname": "payment_schedule_section",
"fieldtype": "Section Break",
- "label": "Payment Terms"
+ "label": "Payment Terms",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "payment_terms_template",
"fieldtype": "Link",
"label": "Payment Terms Template",
- "options": "Payment Terms Template"
+ "options": "Payment Terms Template",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "payment_schedule",
@@ -1092,7 +1336,9 @@
"label": "Payment Schedule",
"no_copy": 1,
"options": "Payment Schedule",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -1100,25 +1346,33 @@
"fieldname": "terms_section_break",
"fieldtype": "Section Break",
"label": "Terms and Conditions",
- "options": "fa fa-legal"
+ "options": "fa fa-legal",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "tc_name",
"fieldtype": "Link",
"label": "Terms",
"options": "Terms and Conditions",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "terms",
"fieldtype": "Text Editor",
- "label": "Terms and Conditions1"
+ "label": "Terms and Conditions1",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "printing_settings",
"fieldtype": "Section Break",
- "label": "Printing Settings"
+ "label": "Printing Settings",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1126,7 +1380,9 @@
"fieldtype": "Link",
"label": "Letter Head",
"options": "Letter Head",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1134,11 +1390,15 @@
"fieldname": "group_same_items",
"fieldtype": "Check",
"label": "Group same items",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_112",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1150,14 +1410,18 @@
"oldfieldtype": "Link",
"options": "Print Heading",
"print_hide": 1,
- "report_hide": 1
+ "report_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "language",
"fieldtype": "Data",
"label": "Print Language",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -1166,7 +1430,9 @@
"label": "More Information",
"oldfieldtype": "Section Break",
"options": "fa fa-file-text",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "credit_to",
@@ -1177,7 +1443,9 @@
"options": "Account",
"print_hide": 1,
"reqd": 1,
- "search_index": 1
+ "search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "party_account_currency",
@@ -1187,7 +1455,9 @@
"no_copy": 1,
"options": "Currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "No",
@@ -1197,7 +1467,9 @@
"oldfieldname": "is_opening",
"oldfieldtype": "Select",
"options": "No\nYes",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "against_expense_account",
@@ -1207,11 +1479,15 @@
"no_copy": 1,
"oldfieldname": "against_expense_account",
"oldfieldtype": "Small Text",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_63",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "Draft",
@@ -1220,7 +1496,9 @@
"in_standard_filter": 1,
"label": "Status",
"options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled\nInternal Transfer",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "inter_company_invoice_reference",
@@ -1229,7 +1507,9 @@
"no_copy": 1,
"options": "Sales Invoice",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "remarks",
@@ -1238,14 +1518,18 @@
"no_copy": 1,
"oldfieldname": "remarks",
"oldfieldtype": "Text",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "subscription_section",
"fieldtype": "Section Break",
"label": "Subscription Section",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1254,7 +1538,9 @@
"fieldtype": "Date",
"label": "From Date",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1263,11 +1549,15 @@
"fieldtype": "Date",
"label": "To Date",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_114",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "auto_repeat",
@@ -1276,24 +1566,32 @@
"no_copy": 1,
"options": "Auto Repeat",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
"depends_on": "eval: doc.auto_repeat",
"fieldname": "update_auto_repeat_reference",
"fieldtype": "Button",
- "label": "Update Auto Repeat Reference"
+ "label": "Update Auto Repeat Reference",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
- "label": "Accounting Dimensions "
+ "label": "Accounting Dimensions ",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "dimension_col_break",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -1301,7 +1599,9 @@
"fieldname": "is_internal_supplier",
"fieldtype": "Check",
"label": "Is Internal Supplier",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "tax_withholding_category",
@@ -1309,25 +1609,33 @@
"hidden": 1,
"label": "Tax Withholding Category",
"options": "Tax Withholding Category",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "billing_address",
"fieldtype": "Link",
"label": "Select Billing Address",
- "options": "Address"
+ "options": "Address",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "billing_address_display",
"fieldtype": "Small Text",
"label": "Billing Address",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
- "options": "Project"
+ "options": "Project",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.is_internal_supplier",
@@ -1335,7 +1643,9 @@
"fieldname": "unrealized_profit_loss_account",
"fieldtype": "Link",
"label": "Unrealized Profit / Loss Account",
- "options": "Account"
+ "options": "Account",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.is_internal_supplier",
@@ -1344,7 +1654,9 @@
"fieldname": "represents_company",
"fieldtype": "Link",
"label": "Represents Company",
- "options": "Company"
+ "options": "Company",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.update_stock && doc.is_internal_supplier",
@@ -1356,6 +1668,8 @@
"options": "Warehouse",
"print_hide": 1,
"print_width": "50px",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50px"
},
{
@@ -1367,6 +1681,8 @@
"options": "Warehouse",
"print_hide": 1,
"print_width": "50px",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50px"
},
{
@@ -1377,13 +1693,29 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "fieldname": "additional_discount_account",
+ "fieldtype": "Link",
+ "label": "Additional Discount Account",
+ "options": "Account"
+ },
+ {
+ "default": "0",
+ "fieldname": "ignore_default_payment_terms_template",
+ "fieldtype": "Check",
+ "hidden": 1,
+ "label": "Ignore Default Payment Terms Template",
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
}
],
"icon": "fa fa-file-text",
"idx": 204,
"is_submittable": 1,
"links": [],
- "modified": "2021-06-15 18:20:56.806195",
+ "modified": "2021-08-07 17:53:14.351439",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index f799279..d7d9a38 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -446,6 +446,7 @@
self.make_supplier_gl_entry(gl_entries)
self.make_item_gl_entries(gl_entries)
+ self.make_discount_gl_entries(gl_entries)
if self.check_asset_cwip_enabled():
self.get_asset_gl_entry(gl_entries)
@@ -518,6 +519,8 @@
if d.category in ('Valuation', 'Total and Valuation')
and flt(d.base_tax_amount_after_discount_amount)]
+ enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
+
for item in self.get("items"):
if flt(item.base_net_amount):
account_currency = get_account_currency(item.expense_account)
@@ -608,7 +611,7 @@
if (not item.enable_deferred_expense or self.is_return) else item.deferred_expense_account)
if not item.is_fixed_asset:
- amount = flt(item.base_net_amount, item.precision("base_net_amount"))
+ dummy, amount = self.get_amount_and_base_amount(item, enable_discount_accounting)
else:
amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount"))
@@ -822,8 +825,11 @@
def make_tax_gl_entries(self, gl_entries):
# tax table gl entries
valuation_tax = {}
+ enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
+
for tax in self.get("taxes"):
- if tax.category in ("Total", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount):
+ amount, base_amount = self.get_tax_amounts(tax, enable_discount_accounting)
+ if tax.category in ("Total", "Valuation and Total") and flt(base_amount):
account_currency = get_account_currency(tax.account_head)
dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit"
@@ -832,21 +838,21 @@
self.get_gl_dict({
"account": tax.account_head,
"against": self.supplier,
- dr_or_cr: tax.base_tax_amount_after_discount_amount,
- dr_or_cr + "_in_account_currency": tax.base_tax_amount_after_discount_amount \
- if account_currency==self.company_currency \
- else tax.tax_amount_after_discount_amount,
+ dr_or_cr: base_amount,
+ dr_or_cr + "_in_account_currency": base_amount
+ if account_currency==self.company_currency
+ else amount,
"cost_center": tax.cost_center
}, account_currency, item=tax)
)
# accumulate valuation tax
- if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount) \
+ if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(base_amount) \
and not self.is_internal_transfer():
if self.auto_accounting_for_stock and not tax.cost_center:
frappe.throw(_("Cost Center is required in row {0} in Taxes table for type {1}").format(tax.idx, _(tax.category)))
valuation_tax.setdefault(tax.name, 0)
valuation_tax[tax.name] += \
- (tax.add_deduct_tax == "Add" and 1 or -1) * flt(tax.base_tax_amount_after_discount_amount)
+ (tax.add_deduct_tax == "Add" and 1 or -1) * flt(base_amount)
if self.is_opening == "No" and self.negative_expense_to_be_booked and valuation_tax:
# credit valuation tax amount in "Expenses Included In Valuation"
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index c9384be..f0f5a58 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -230,6 +230,50 @@
self.assertEqual(expected_values[gle.account][1], gle.debit)
self.assertEqual(expected_values[gle.account][2], gle.credit)
+ def test_purchase_invoice_with_discount_accounting_enabled(self):
+ enable_discount_accounting()
+
+ discount_account = create_account(account_name="Discount Account",
+ parent_account="Indirect Expenses - _TC", company="_Test Company")
+ pi = make_purchase_invoice(discount_account=discount_account, rate=45)
+
+ expected_gle = [
+ ["_Test Account Cost for Goods Sold - _TC", 250.0, 0.0, nowdate()],
+ ["Creditors - _TC", 0.0, 225.0, nowdate()],
+ ["Discount Account - _TC", 0.0, 25.0, nowdate()]
+ ]
+
+ check_gl_entries(self, pi.name, expected_gle, nowdate())
+ enable_discount_accounting(enable=0)
+
+ def test_additional_discount_for_purchase_invoice_with_discount_accounting_enabled(self):
+ enable_discount_accounting()
+ additional_discount_account = create_account(account_name="Discount Account",
+ parent_account="Indirect Expenses - _TC", company="_Test Company")
+
+ pi = make_purchase_invoice(do_not_save=1, parent_cost_center="Main - _TC")
+ pi.apply_discount_on = "Grand Total"
+ pi.additional_discount_account = additional_discount_account
+ pi.additional_discount_percentage = 10
+ pi.disable_rounded_total = 1
+ pi.append("taxes", {
+ "charge_type": "On Net Total",
+ "account_head": "_Test Account VAT - _TC",
+ "cost_center": "Main - _TC",
+ "description": "Test",
+ "rate": 10
+ })
+ pi.submit()
+
+ expected_gle = [
+ ["_Test Account Cost for Goods Sold - _TC", 250.0, 0.0, nowdate()],
+ ["_Test Account VAT - _TC", 25.0, 0.0, nowdate()],
+ ["Creditors - _TC", 0.0, 247.5, nowdate()],
+ ["Discount Account - _TC", 0.0, 27.5, nowdate()]
+ ]
+
+ check_gl_entries(self, pi.name, expected_gle, nowdate())
+
def test_purchase_invoice_change_naming_series(self):
pi = frappe.copy_doc(test_records[1])
pi.insert()
@@ -954,8 +998,17 @@
acc_settings.save()
def test_gain_loss_with_advance_entry(self):
- unlink_enabled = frappe.db.get_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice")
- frappe.db.set_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", 1)
+ unlink_enabled = frappe.db.get_value(
+ "Accounts Settings", "Accounts Settings",
+ "unlink_payment_on_cancel_of_invoice")
+
+ frappe.db.set_value(
+ "Accounts Settings", "Accounts Settings",
+ "unlink_payment_on_cancel_of_invoice", 1)
+
+ original_account = frappe.db.get_value("Company", "_Test Company", "exchange_gain_loss_account")
+ frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", "Exchange Gain/Loss - _TC")
+
pay = frappe.get_doc({
'doctype': 'Payment Entry',
'company': '_Test Company',
@@ -988,14 +1041,15 @@
expected_gle = [
["_Test Account Cost for Goods Sold - _TC", 37500.0],
- ["_Test Payable USD - _TC", -40000.0],
- ["Exchange Gain/Loss - _TC", 2500.0]
+ ["_Test Payable USD - _TC", -35000.0],
+ ["Exchange Gain/Loss - _TC", -2500.0]
]
gl_entries = frappe.db.sql("""
select account, sum(debit - credit) as balance from `tabGL Entry`
where voucher_no=%s
- group by account order by account asc""", (pi.name), as_dict=1)
+ group by account
+ order by account asc""", (pi.name), as_dict=1)
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_gle[i][0], gle.account)
@@ -1018,8 +1072,8 @@
expected_gle = [
["_Test Account Cost for Goods Sold - _TC", 36500.0],
- ["_Test Payable USD - _TC", -38000.0],
- ["Exchange Gain/Loss - _TC", 1500.0]
+ ["_Test Payable USD - _TC", -35000.0],
+ ["Exchange Gain/Loss - _TC", -1500.0]
]
gl_entries = frappe.db.sql("""
@@ -1055,6 +1109,7 @@
pay.cancel()
frappe.db.set_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled)
+ frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", original_account)
def test_purchase_invoice_advance_taxes(self):
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
@@ -1129,6 +1184,18 @@
self.assertEqual(expected_gle[i][0], gle.account)
self.assertEqual(expected_gle[i][1], gle.amount)
+def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
+ gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
+ from `tabGL Entry`
+ where voucher_type='Purchase Invoice' and voucher_no=%s and posting_date >= %s
+ order by posting_date asc, account asc""", (voucher_no, posting_date), as_dict=1)
+
+ for i, gle in enumerate(gl_entries):
+ doc.assertEqual(expected_gle[i][0], gle.account)
+ doc.assertEqual(expected_gle[i][1], gle.debit)
+ doc.assertEqual(expected_gle[i][2], gle.credit)
+ doc.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
+
def update_tax_witholding_category(company, account, date):
from erpnext.accounts.utils import get_fiscal_year
@@ -1159,6 +1226,11 @@
accounts_settings.unlink_payment_on_cancellation_of_invoice = enable
accounts_settings.save()
+def enable_discount_accounting(enable=1):
+ accounts_settings = frappe.get_doc("Accounts Settings")
+ accounts_settings.enable_discount_accounting = enable
+ accounts_settings.save()
+
def make_purchase_invoice(**args):
pi = frappe.new_doc("Purchase Invoice")
args = frappe._dict(args)
@@ -1181,6 +1253,7 @@
pi.return_against = args.return_against
pi.is_subcontracted = args.is_subcontracted or "No"
pi.supplier_warehouse = args.supplier_warehouse or "_Test Warehouse 1 - _TC"
+ pi.cost_center = args.parent_cost_center
pi.append("items", {
"item_code": args.item or args.item_code or "_Test Item",
@@ -1189,7 +1262,10 @@
"received_qty": args.received_qty or 0,
"rejected_qty": args.rejected_qty or 0,
"rate": args.rate or 50,
- 'expense_account': args.expense_account or '_Test Account Cost for Goods Sold - _TC',
+ "price_list_rate": args.price_list_rate or 50,
+ "expense_account": args.expense_account or '_Test Account Cost for Goods Sold - _TC',
+ "discount_account": args.discount_account or None,
+ "discount_amount": args.discount_amount or 0,
"conversion_factor": 1.0,
"serial_no": args.serial_no,
"stock_uom": args.uom or "_Test UOM",
diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
index 8a55ff8..922b567 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -73,6 +73,7 @@
"manufacturer_part_no",
"accounting",
"expense_account",
+ "discount_account",
"col_break5",
"is_fixed_asset",
"asset_location",
@@ -849,12 +850,18 @@
"options": "Company:company:default_currency",
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "fieldname": "discount_account",
+ "fieldtype": "Link",
+ "label": "Discount Account",
+ "options": "Account"
}
],
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2021-06-16 19:57:03.101571",
+ "modified": "2021-07-13 02:04:37.787882",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index f813425..56f1165 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -347,7 +347,7 @@
items_add: function(doc, cdt, cdn) {
var row = frappe.get_doc(cdt, cdn);
- this.frm.script_manager.copy_from_first_row("items", row, ["income_account", "cost_center"]);
+ this.frm.script_manager.copy_from_first_row("items", row, ["income_account", "discount_account", "cost_center"]);
},
set_dynamic_labels: function() {
@@ -510,7 +510,6 @@
}
});
-
// Cost Center in Details Table
// -----------------------------
cur_frm.fields_dict["items"].grid.get_field("cost_center").get_query = function(doc) {
@@ -592,6 +591,16 @@
};
});
+ frm.set_query("additional_discount_account", function() {
+ return {
+ filters: {
+ company: frm.doc.company,
+ is_group: 0,
+ report_type: "Profit and Loss",
+ }
+ };
+ });
+
frm.custom_make_buttons = {
'Delivery Note': 'Delivery',
'Sales Invoice': 'Return / Credit Note',
@@ -618,6 +627,17 @@
}
}
+ // discount account
+ frm.fields_dict['items'].grid.get_field('discount_account').get_query = function(doc) {
+ return {
+ filters: {
+ 'report_type': 'Profit and Loss',
+ 'company': doc.company,
+ "is_group": 0
+ }
+ }
+ }
+
frm.fields_dict['items'].grid.get_field('deferred_revenue_account').get_query = function(doc) {
return {
filters: {
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index e7dd6b8..545b77a 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -48,6 +48,8 @@
"shipping_address",
"company_address",
"company_address_display",
+ "dispatch_address_name",
+ "dispatch_address",
"currency_and_price_list",
"currency",
"conversion_rate",
@@ -104,6 +106,7 @@
"section_break_49",
"apply_discount_on",
"base_discount_amount",
+ "additional_discount_account",
"column_break_51",
"additional_discount_percentage",
"discount_amount",
@@ -125,6 +128,7 @@
"get_advances",
"advances",
"payment_schedule_section",
+ "ignore_default_payment_terms_template",
"payment_terms_template",
"payment_schedule",
"payments_section",
@@ -195,7 +199,9 @@
"fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1,
- "options": "fa fa-user"
+ "options": "fa fa-user",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -207,7 +213,9 @@
"hide_seconds": 1,
"label": "Title",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"bold": 1,
@@ -222,7 +230,9 @@
"options": "ACC-SINV-.YYYY.-\nACC-SINV-RET-.YYYY.-",
"print_hide": 1,
"reqd": 1,
- "set_only_once": 1
+ "set_only_once": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"bold": 1,
@@ -236,7 +246,9 @@
"oldfieldtype": "Link",
"options": "Customer",
"print_hide": 1,
- "search_index": 1
+ "search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"bold": 1,
@@ -250,7 +262,9 @@
"label": "Customer Name",
"oldfieldname": "customer_name",
"oldfieldtype": "Data",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "tax_id",
@@ -259,7 +273,9 @@
"hide_seconds": 1,
"label": "Tax Id",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "project",
@@ -271,7 +287,9 @@
"oldfieldname": "project_name",
"oldfieldtype": "Link",
"options": "Project",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -282,7 +300,9 @@
"label": "Include Payment (POS)",
"oldfieldname": "is_pos",
"oldfieldtype": "Check",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "is_pos",
@@ -292,7 +312,9 @@
"hide_seconds": 1,
"label": "POS Profile",
"options": "POS Profile",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -302,14 +324,18 @@
"hide_seconds": 1,
"label": "Is Return (Credit Note)",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break1",
"fieldtype": "Column Break",
"hide_days": 1,
"hide_seconds": 1,
- "oldfieldtype": "Column Break"
+ "oldfieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "company",
@@ -323,7 +349,9 @@
"options": "Company",
"print_hide": 1,
"remember_last_selected_value": 1,
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "cost_center",
@@ -331,7 +359,9 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "Cost Center",
- "options": "Cost Center"
+ "options": "Cost Center",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"bold": 1,
@@ -345,7 +375,9 @@
"oldfieldname": "posting_date",
"oldfieldtype": "Date",
"reqd": 1,
- "search_index": 1
+ "search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "posting_time",
@@ -356,7 +388,9 @@
"no_copy": 1,
"oldfieldname": "posting_time",
"oldfieldtype": "Time",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -366,7 +400,9 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "Edit Posting Date and Time",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "due_date",
@@ -376,7 +412,9 @@
"label": "Payment Due Date",
"no_copy": 1,
"oldfieldname": "due_date",
- "oldfieldtype": "Date"
+ "oldfieldtype": "Date",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "amended_from",
@@ -390,7 +428,9 @@
"oldfieldtype": "Link",
"options": "Sales Invoice",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.return_against || doc.is_debit_note",
@@ -403,7 +443,9 @@
"options": "Sales Invoice",
"print_hide": 1,
"read_only_depends_on": "eval:doc.is_return",
- "search_index": 1
+ "search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -412,7 +454,9 @@
"fieldtype": "Check",
"hide_days": 1,
"hide_seconds": 1,
- "label": "Update Billed Amount in Sales Order"
+ "label": "Update Billed Amount in Sales Order",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -421,7 +465,9 @@
"fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1,
- "label": "Customer PO Details"
+ "label": "Customer PO Details",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -431,13 +477,17 @@
"hide_seconds": 1,
"label": "Customer's Purchase Order",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_23",
"fieldtype": "Column Break",
"hide_days": 1,
- "hide_seconds": 1
+ "hide_seconds": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -445,7 +495,9 @@
"fieldtype": "Date",
"hide_days": 1,
"hide_seconds": 1,
- "label": "Customer's Purchase Order Date"
+ "label": "Customer's Purchase Order Date",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -453,7 +505,9 @@
"fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1,
- "label": "Address and Contact"
+ "label": "Address and Contact",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "customer_address",
@@ -462,7 +516,9 @@
"hide_seconds": 1,
"label": "Customer Address",
"options": "Address",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "address_display",
@@ -470,7 +526,9 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "Address",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_person",
@@ -480,7 +538,9 @@
"in_global_search": 1,
"label": "Contact Person",
"options": "Contact",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_display",
@@ -488,7 +548,9 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "Contact",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_mobile",
@@ -497,7 +559,9 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "Mobile No",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_email",
@@ -508,7 +572,9 @@
"label": "Contact Email",
"options": "Email",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "territory",
@@ -517,13 +583,17 @@
"hide_seconds": 1,
"label": "Territory",
"options": "Territory",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "col_break4",
"fieldtype": "Column Break",
"hide_days": 1,
- "hide_seconds": 1
+ "hide_seconds": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_address_name",
@@ -532,7 +602,9 @@
"hide_seconds": 1,
"label": "Shipping Address Name",
"options": "Address",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_address",
@@ -541,7 +613,9 @@
"hide_seconds": 1,
"label": "Shipping Address",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "company_address",
@@ -550,7 +624,9 @@
"hide_seconds": 1,
"label": "Company Address Name",
"options": "Address",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "company_address_display",
@@ -560,7 +636,9 @@
"hide_seconds": 1,
"label": "Company Address",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -569,7 +647,9 @@
"fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1,
- "label": "Currency and Price List"
+ "label": "Currency and Price List",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "currency",
@@ -581,7 +661,9 @@
"oldfieldtype": "Select",
"options": "Currency",
"print_hide": 1,
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"description": "Rate at which Customer Currency is converted to customer's base currency",
@@ -594,13 +676,17 @@
"oldfieldtype": "Currency",
"precision": "9",
"print_hide": 1,
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break2",
"fieldtype": "Column Break",
"hide_days": 1,
"hide_seconds": 1,
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -613,7 +699,9 @@
"oldfieldtype": "Select",
"options": "Price List",
"print_hide": 1,
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "price_list_currency",
@@ -624,7 +712,9 @@
"options": "Currency",
"print_hide": 1,
"read_only": 1,
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"description": "Rate at which Price list currency is converted to customer's base currency",
@@ -635,7 +725,9 @@
"label": "Price List Exchange Rate",
"precision": "9",
"print_hide": 1,
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -646,14 +738,18 @@
"label": "Ignore Pricing Rule",
"no_copy": 1,
"permlevel": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "sec_warehouse",
"fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1,
- "label": "Warehouse"
+ "label": "Warehouse",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "update_stock",
@@ -663,7 +759,9 @@
"hide_seconds": 1,
"label": "Source Warehouse",
"options": "Warehouse",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "items_section",
@@ -672,7 +770,9 @@
"hide_seconds": 1,
"label": "Items",
"oldfieldtype": "Section Break",
- "options": "fa fa-shopping-cart"
+ "options": "fa fa-shopping-cart",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -683,14 +783,18 @@
"label": "Update Stock",
"oldfieldname": "update_stock",
"oldfieldtype": "Check",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "scan_barcode",
"fieldtype": "Data",
"hide_days": 1,
"hide_seconds": 1,
- "label": "Scan Barcode"
+ "label": "Scan Barcode",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_bulk_edit": 1,
@@ -702,14 +806,18 @@
"oldfieldname": "entries",
"oldfieldtype": "Table",
"options": "Sales Invoice Item",
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "pricing_rule_details",
"fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1,
- "label": "Pricing Rules"
+ "label": "Pricing Rules",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "pricing_rules",
@@ -718,7 +826,9 @@
"hide_seconds": 1,
"label": "Pricing Rule Detail",
"options": "Pricing Rule Detail",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "packing_list",
@@ -727,7 +837,9 @@
"hide_seconds": 1,
"label": "Packing List",
"options": "fa fa-suitcase",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "packed_items",
@@ -736,7 +848,9 @@
"hide_seconds": 1,
"label": "Packed Items",
"options": "Packed Item",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "product_bundle_help",
@@ -744,7 +858,9 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "Product Bundle Help",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -754,7 +870,9 @@
"fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1,
- "label": "Time Sheet List"
+ "label": "Time Sheet List",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "timesheets",
@@ -763,7 +881,9 @@
"hide_seconds": 1,
"label": "Time Sheets",
"options": "Sales Invoice Timesheet",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -774,13 +894,17 @@
"label": "Total Billing Amount",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "section_break_30",
"fieldtype": "Section Break",
"hide_days": 1,
- "hide_seconds": 1
+ "hide_seconds": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_qty",
@@ -788,7 +912,9 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "Total Quantity",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_total",
@@ -798,7 +924,9 @@
"label": "Total (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_net_total",
@@ -811,13 +939,17 @@
"options": "Company:company:default_currency",
"print_hide": 1,
"read_only": 1,
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_32",
"fieldtype": "Column Break",
"hide_days": 1,
- "hide_seconds": 1
+ "hide_seconds": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total",
@@ -826,7 +958,9 @@
"hide_seconds": 1,
"label": "Total",
"options": "currency",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "net_total",
@@ -836,7 +970,9 @@
"label": "Net Total",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_net_weight",
@@ -845,7 +981,9 @@
"hide_seconds": 1,
"label": "Total Net Weight",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_section",
@@ -853,7 +991,9 @@
"hide_days": 1,
"hide_seconds": 1,
"oldfieldtype": "Section Break",
- "options": "fa fa-money"
+ "options": "fa fa-money",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_and_charges",
@@ -864,13 +1004,17 @@
"oldfieldname": "charge",
"oldfieldtype": "Link",
"options": "Sales Taxes and Charges Template",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_38",
"fieldtype": "Column Break",
"hide_days": 1,
- "hide_seconds": 1
+ "hide_seconds": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_rule",
@@ -880,7 +1024,9 @@
"label": "Shipping Rule",
"oldfieldtype": "Button",
"options": "Shipping Rule",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "tax_category",
@@ -889,13 +1035,17 @@
"hide_seconds": 1,
"label": "Tax Category",
"options": "Tax Category",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "section_break_40",
"fieldtype": "Section Break",
"hide_days": 1,
- "hide_seconds": 1
+ "hide_seconds": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes",
@@ -905,7 +1055,9 @@
"label": "Sales Taxes and Charges",
"oldfieldname": "other_charges",
"oldfieldtype": "Table",
- "options": "Sales Taxes and Charges"
+ "options": "Sales Taxes and Charges",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -913,7 +1065,9 @@
"fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1,
- "label": "Tax Breakup"
+ "label": "Tax Breakup",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "other_charges_calculation",
@@ -924,13 +1078,17 @@
"no_copy": 1,
"oldfieldtype": "HTML",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "section_break_43",
"fieldtype": "Section Break",
"hide_days": 1,
- "hide_seconds": 1
+ "hide_seconds": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_total_taxes_and_charges",
@@ -942,13 +1100,17 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_47",
"fieldtype": "Column Break",
"hide_days": 1,
- "hide_seconds": 1
+ "hide_seconds": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_taxes_and_charges",
@@ -958,7 +1120,9 @@
"label": "Total Taxes and Charges",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -966,7 +1130,9 @@
"fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1,
- "label": "Loyalty Points Redemption"
+ "label": "Loyalty Points Redemption",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "redeem_loyalty_points",
@@ -976,7 +1142,9 @@
"hide_seconds": 1,
"label": "Loyalty Points",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "redeem_loyalty_points",
@@ -988,7 +1156,9 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -998,13 +1168,17 @@
"hide_seconds": 1,
"label": "Redeem Loyalty Points",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_77",
"fieldtype": "Column Break",
"hide_days": 1,
- "hide_seconds": 1
+ "hide_seconds": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fetch_from": "customer.loyalty_program",
@@ -1016,7 +1190,9 @@
"no_copy": 1,
"options": "Loyalty Program",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "redeem_loyalty_points",
@@ -1026,7 +1202,9 @@
"hide_seconds": 1,
"label": "Redemption Account",
"no_copy": 1,
- "options": "Account"
+ "options": "Account",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "redeem_loyalty_points",
@@ -1036,7 +1214,9 @@
"hide_seconds": 1,
"label": "Redemption Cost Center",
"no_copy": 1,
- "options": "Cost Center"
+ "options": "Cost Center",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -1045,7 +1225,9 @@
"fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1,
- "label": "Additional Discount"
+ "label": "Additional Discount",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "Grand Total",
@@ -1055,7 +1237,9 @@
"hide_seconds": 1,
"label": "Apply Additional Discount On",
"options": "\nGrand Total\nNet Total",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_discount_amount",
@@ -1065,13 +1249,17 @@
"label": "Additional Discount Amount (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_51",
"fieldtype": "Column Break",
"hide_days": 1,
- "hide_seconds": 1
+ "hide_seconds": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "additional_discount_percentage",
@@ -1079,7 +1267,9 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "Additional Discount Percentage",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "discount_amount",
@@ -1088,7 +1278,9 @@
"hide_seconds": 1,
"label": "Additional Discount Amount",
"options": "currency",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "totals",
@@ -1097,7 +1289,9 @@
"hide_seconds": 1,
"oldfieldtype": "Section Break",
"options": "fa fa-money",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_grand_total",
@@ -1110,7 +1304,9 @@
"options": "Company:company:default_currency",
"print_hide": 1,
"read_only": 1,
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:!doc.disable_rounded_total",
@@ -1122,7 +1318,9 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:!doc.disable_rounded_total",
@@ -1135,7 +1333,9 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"description": "In Words will be visible once you save the Sales Invoice.",
@@ -1148,7 +1348,9 @@
"oldfieldname": "in_words",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break5",
@@ -1157,6 +1359,8 @@
"hide_seconds": 1,
"oldfieldtype": "Column Break",
"print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -1171,7 +1375,9 @@
"oldfieldtype": "Currency",
"options": "currency",
"read_only": 1,
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:!doc.disable_rounded_total",
@@ -1183,7 +1389,9 @@
"no_copy": 1,
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"bold": 1,
@@ -1196,7 +1404,9 @@
"oldfieldname": "rounded_total_export",
"oldfieldtype": "Currency",
"options": "currency",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "in_words",
@@ -1208,7 +1418,9 @@
"oldfieldname": "in_words_export",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_advance",
@@ -1220,7 +1432,9 @@
"oldfieldtype": "Currency",
"options": "party_account_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "outstanding_amount",
@@ -1233,7 +1447,9 @@
"oldfieldtype": "Currency",
"options": "party_account_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -1245,7 +1461,9 @@
"label": "Advance Payments",
"oldfieldtype": "Section Break",
"options": "fa fa-money",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -1253,7 +1471,9 @@
"fieldtype": "Check",
"hide_days": 1,
"hide_seconds": 1,
- "label": "Allocate Advances Automatically (FIFO)"
+ "label": "Allocate Advances Automatically (FIFO)",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:!doc.allocate_advances_automatically",
@@ -1262,7 +1482,9 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "Get Advances Received",
- "options": "set_advances"
+ "options": "set_advances",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "advances",
@@ -1273,7 +1495,9 @@
"oldfieldname": "advance_adjustment_details",
"oldfieldtype": "Table",
"options": "Sales Invoice Advance",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -1282,7 +1506,9 @@
"fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1,
- "label": "Payment Terms"
+ "label": "Payment Terms",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:(!doc.is_pos && !doc.is_return)",
@@ -1293,7 +1519,9 @@
"label": "Payment Terms Template",
"no_copy": 1,
"options": "Payment Terms Template",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:(!doc.is_pos && !doc.is_return)",
@@ -1304,7 +1532,9 @@
"label": "Payment Schedule",
"no_copy": 1,
"options": "Payment Schedule",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.is_pos===1||(doc.advances && doc.advances.length>0)",
@@ -1313,7 +1543,9 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "Payments",
- "options": "fa fa-money"
+ "options": "fa fa-money",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "is_pos",
@@ -1326,7 +1558,9 @@
"oldfieldname": "cash_bank_account",
"oldfieldtype": "Link",
"options": "Account",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.is_pos===1",
@@ -1336,13 +1570,17 @@
"hide_seconds": 1,
"label": "Sales Invoice Payment",
"options": "Sales Invoice Payment",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "section_break_84",
"fieldtype": "Section Break",
"hide_days": 1,
- "hide_seconds": 1
+ "hide_seconds": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_paid_amount",
@@ -1353,13 +1591,17 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_86",
"fieldtype": "Column Break",
"hide_days": 1,
- "hide_seconds": 1
+ "hide_seconds": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval: doc.is_pos || doc.redeem_loyalty_points",
@@ -1373,13 +1615,17 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "section_break_88",
"fieldtype": "Section Break",
"hide_days": 1,
- "hide_seconds": 1
+ "hide_seconds": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "is_pos",
@@ -1391,13 +1637,17 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_90",
"fieldtype": "Column Break",
"hide_days": 1,
- "hide_seconds": 1
+ "hide_seconds": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "is_pos",
@@ -1408,7 +1658,9 @@
"label": "Change Amount",
"no_copy": 1,
"options": "currency",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "is_pos",
@@ -1418,7 +1670,9 @@
"hide_seconds": 1,
"label": "Account for Change Amount",
"options": "Account",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -1429,6 +1683,8 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "Write Off",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -1439,7 +1695,9 @@
"label": "Write Off Amount",
"no_copy": 1,
"options": "currency",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_write_off_amount",
@@ -1450,7 +1708,9 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -1460,13 +1720,17 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "Write Off Outstanding Amount",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_74",
"fieldtype": "Column Break",
"hide_days": 1,
- "hide_seconds": 1
+ "hide_seconds": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "write_off_account",
@@ -1475,7 +1739,9 @@
"hide_seconds": 1,
"label": "Write Off Account",
"options": "Account",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "write_off_cost_center",
@@ -1484,7 +1750,9 @@
"hide_seconds": 1,
"label": "Write Off Cost Center",
"options": "Cost Center",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -1494,7 +1762,9 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "Terms and Conditions",
- "oldfieldtype": "Section Break"
+ "oldfieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "tc_name",
@@ -1505,7 +1775,9 @@
"oldfieldname": "tc_name",
"oldfieldtype": "Link",
"options": "Terms and Conditions",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "terms",
@@ -1514,7 +1786,9 @@
"hide_seconds": 1,
"label": "Terms and Conditions Details",
"oldfieldname": "terms",
- "oldfieldtype": "Text Editor"
+ "oldfieldtype": "Text Editor",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -1522,7 +1796,9 @@
"fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1,
- "label": "Printing Settings"
+ "label": "Printing Settings",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1534,7 +1810,9 @@
"oldfieldname": "letter_head",
"oldfieldtype": "Select",
"options": "Letter Head",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1544,7 +1822,9 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "Group same items",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "language",
@@ -1553,13 +1833,17 @@
"hide_seconds": 1,
"label": "Print Language",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_84",
"fieldtype": "Column Break",
"hide_days": 1,
- "hide_seconds": 1
+ "hide_seconds": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1573,7 +1857,9 @@
"oldfieldtype": "Link",
"options": "Print Heading",
"print_hide": 1,
- "report_hide": 1
+ "report_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -1582,7 +1868,9 @@
"fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1,
- "label": "More Information"
+ "label": "More Information",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "inter_company_invoice_reference",
@@ -1591,7 +1879,9 @@
"hide_seconds": 1,
"label": "Inter Company Invoice Reference",
"options": "Purchase Invoice",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "customer_group",
@@ -1601,7 +1891,9 @@
"hide_seconds": 1,
"label": "Customer Group",
"options": "Customer Group",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "campaign",
@@ -1612,7 +1904,9 @@
"oldfieldname": "campaign",
"oldfieldtype": "Link",
"options": "Campaign",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -1622,13 +1916,17 @@
"hide_seconds": 1,
"label": "Is Discounted",
"no_copy": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "col_break23",
"fieldtype": "Column Break",
"hide_days": 1,
"hide_seconds": 1,
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -1642,7 +1940,9 @@
"no_copy": 1,
"options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled\nInternal Transfer",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "source",
@@ -1653,7 +1953,9 @@
"oldfieldname": "source",
"oldfieldtype": "Select",
"options": "Lead Source",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -1664,7 +1966,9 @@
"label": "Accounting Details",
"oldfieldtype": "Section Break",
"options": "fa fa-file-text",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "debit_to",
@@ -1677,7 +1981,9 @@
"options": "Account",
"print_hide": 1,
"reqd": 1,
- "search_index": 1
+ "search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "party_account_currency",
@@ -1689,7 +1995,9 @@
"no_copy": 1,
"options": "Currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "No",
@@ -1701,7 +2009,9 @@
"oldfieldname": "is_opening",
"oldfieldtype": "Select",
"options": "No\nYes",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "c_form_applicable",
@@ -1711,7 +2021,9 @@
"label": "C-Form Applicable",
"no_copy": 1,
"options": "No\nYes",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "c_form_no",
@@ -1722,7 +2034,9 @@
"no_copy": 1,
"options": "C-Form",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break8",
@@ -1730,7 +2044,9 @@
"hide_days": 1,
"hide_seconds": 1,
"oldfieldtype": "Column Break",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "remarks",
@@ -1741,7 +2057,9 @@
"no_copy": 1,
"oldfieldname": "remarks",
"oldfieldtype": "Text",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -1753,7 +2071,9 @@
"label": "Commission",
"oldfieldtype": "Section Break",
"options": "fa fa-group",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "sales_partner",
@@ -1764,7 +2084,9 @@
"oldfieldname": "sales_partner",
"oldfieldtype": "Link",
"options": "Sales Partner",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break10",
@@ -1773,6 +2095,8 @@
"hide_seconds": 1,
"oldfieldtype": "Column Break",
"print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -1783,7 +2107,9 @@
"label": "Commission Rate (%)",
"oldfieldname": "commission_rate",
"oldfieldtype": "Currency",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_commission",
@@ -1794,7 +2120,9 @@
"oldfieldname": "total_commission",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -1804,7 +2132,9 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "Sales Team",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1816,7 +2146,9 @@
"oldfieldname": "sales_team",
"oldfieldtype": "Table",
"options": "Sales Team",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -1824,7 +2156,9 @@
"fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1,
- "label": "Subscription Section"
+ "label": "Subscription Section",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1834,7 +2168,9 @@
"hide_seconds": 1,
"label": "From Date",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1844,13 +2180,17 @@
"hide_seconds": 1,
"label": "To Date",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_140",
"fieldtype": "Column Break",
"hide_days": 1,
- "hide_seconds": 1
+ "hide_seconds": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1862,7 +2202,9 @@
"no_copy": 1,
"options": "Auto Repeat",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1871,7 +2213,9 @@
"fieldtype": "Button",
"hide_days": 1,
"hide_seconds": 1,
- "label": "Update Auto Repeat Reference"
+ "label": "Update Auto Repeat Reference",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "against_income_account",
@@ -1884,7 +2228,9 @@
"oldfieldname": "against_income_account",
"oldfieldtype": "Small Text",
"print_hide": 1,
- "report_hide": 1
+ "report_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -1892,13 +2238,17 @@
"fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1,
- "label": "Accounting Dimensions"
+ "label": "Accounting Dimensions",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break",
"hide_days": 1,
- "hide_seconds": 1
+ "hide_seconds": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -1906,7 +2256,9 @@
"fieldname": "is_consolidated",
"fieldtype": "Check",
"label": "Is Consolidated",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -1916,14 +2268,18 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "Is Internal Customer",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fetch_from": "company.tax_id",
"fieldname": "company_tax_id",
"fieldtype": "Data",
"label": "Company Tax ID",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.is_internal_customer",
@@ -1931,7 +2287,9 @@
"fieldname": "unrealized_profit_loss_account",
"fieldtype": "Link",
"label": "Unrealized Profit / Loss Account",
- "options": "Account"
+ "options": "Account",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.is_internal_customer",
@@ -1941,31 +2299,72 @@
"fieldtype": "Link",
"label": "Represents Company",
"options": "Company",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_55",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval: doc.is_internal_customer && doc.update_stock",
"fieldname": "set_target_warehouse",
"fieldtype": "Link",
"label": "Set Target Warehouse",
- "options": "Warehouse"
+ "options": "Warehouse",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "is_debit_note",
"fieldtype": "Check",
- "label": "Is Debit Note"
+ "label": "Is Debit Note",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"depends_on": "grand_total",
"fieldname": "disable_rounded_total",
"fieldtype": "Check",
- "label": "Disable Rounded Total"
+ "label": "Disable Rounded Total",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "additional_discount_account",
+ "fieldtype": "Link",
+ "label": "Additional Discount Account",
+ "options": "Account"
+ },
+ {
+ "default": "0",
+ "fieldname": "ignore_default_payment_terms_template",
+ "fieldtype": "Check",
+ "hidden": 1,
+ "label": "Ignore Default Payment Terms Template",
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "dispatch_address_name",
+ "fieldtype": "Link",
+ "label": "Dispatch Address Name",
+ "options": "Address",
+ "print_hide": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "dispatch_address",
+ "fieldtype": "Small Text",
+ "label": "Dispatch Address",
+ "read_only": 1
}
],
"icon": "fa fa-file-text",
@@ -1978,7 +2377,7 @@
"link_fieldname": "consolidated_invoice"
}
],
- "modified": "2021-05-20 22:48:33.988881",
+ "modified": "2021-08-07 23:02:20.445127",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 6d1f624..eba8ba8 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -480,7 +480,7 @@
if not self.pos_profile:
pos_profile = get_pos_profile(self.company) or {}
if not pos_profile:
- frappe.throw(_("No POS Profile found. Please create a New POS Profile first"))
+ return
self.pos_profile = pos_profile.get('name')
pos = {}
@@ -846,6 +846,7 @@
self.allocate_advance_taxes(gl_entries)
self.make_item_gl_entries(gl_entries)
+ self.make_discount_gl_entries(gl_entries)
# merge gl entries before adding pos entries
gl_entries = merge_similar_entries(gl_entries)
@@ -885,18 +886,22 @@
)
def make_tax_gl_entries(self, gl_entries):
+ enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
+
for tax in self.get("taxes"):
+ amount, base_amount = self.get_tax_amounts(tax, enable_discount_accounting)
+
if flt(tax.base_tax_amount_after_discount_amount):
account_currency = get_account_currency(tax.account_head)
gl_entries.append(
self.get_gl_dict({
"account": tax.account_head,
"against": self.customer,
- "credit": flt(tax.base_tax_amount_after_discount_amount,
+ "credit": flt(base_amount,
tax.precision("tax_amount_after_discount_amount")),
- "credit_in_account_currency": (flt(tax.base_tax_amount_after_discount_amount,
+ "credit_in_account_currency": (flt(base_amount,
tax.precision("base_tax_amount_after_discount_amount")) if account_currency==self.company_currency else
- flt(tax.tax_amount_after_discount_amount, tax.precision("tax_amount_after_discount_amount"))),
+ flt(amount, tax.precision("tax_amount_after_discount_amount"))),
"cost_center": tax.cost_center
}, account_currency, item=tax)
)
@@ -915,6 +920,8 @@
def make_item_gl_entries(self, gl_entries):
# income account gl entries
+ enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
+
for item in self.get("items"):
if flt(item.base_net_amount, item.precision("base_net_amount")):
if item.is_fixed_asset:
@@ -940,15 +947,17 @@
income_account = (item.income_account
if (not item.enable_deferred_revenue or self.is_return) else item.deferred_revenue_account)
+ amount, base_amount = self.get_amount_and_base_amount(item, enable_discount_accounting)
+
account_currency = get_account_currency(income_account)
gl_entries.append(
self.get_gl_dict({
"account": income_account,
"against": self.customer,
- "credit": flt(item.base_net_amount, item.precision("base_net_amount")),
- "credit_in_account_currency": (flt(item.base_net_amount, item.precision("base_net_amount"))
+ "credit": flt(base_amount, item.precision("base_net_amount")),
+ "credit_in_account_currency": (flt(base_amount, item.precision("base_net_amount"))
if account_currency==self.company_currency
- else flt(item.net_amount, item.precision("net_amount"))),
+ else flt(amount, item.precision("net_amount"))),
"cost_center": item.cost_center,
"project": item.project or self.project
}, account_currency, item=item)
@@ -959,6 +968,12 @@
erpnext.is_perpetual_inventory_enabled(self.company):
gl_entries += super(SalesInvoice, self).get_gl_entries()
+ def set_asset_status(self, asset):
+ if self.is_return:
+ asset.set_status()
+ else:
+ asset.set_status("Sold" if self.docstatus==1 else None)
+
def make_loyalty_point_redemption_gle(self, gl_entries):
if cint(self.redeem_loyalty_points):
gl_entries.append(
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index dbc7f86..12f03be 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1908,6 +1908,8 @@
self.assertEqual(data['billLists'][0]['sgstValue'], 5400)
self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234')
self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000)
+ self.assertEqual(data['billLists'][0]['actualFromStateCode'],7)
+ self.assertEqual(data['billLists'][0]['fromStateCode'],27)
def test_einvoice_submission_without_irn(self):
# init
@@ -1984,6 +1986,54 @@
sales_invoice.save()
self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC")
+ def test_sales_invoice_with_discount_accounting_enabled(self):
+ from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import enable_discount_accounting
+
+ enable_discount_accounting()
+
+ discount_account = create_account(account_name="Discount Account",
+ parent_account="Indirect Expenses - _TC", company="_Test Company")
+ si = create_sales_invoice(discount_account=discount_account, discount_percentage=10, rate=90)
+
+ expected_gle = [
+ ["Debtors - _TC", 90.0, 0.0, nowdate()],
+ ["Discount Account - _TC", 10.0, 0.0, nowdate()],
+ ["Sales - _TC", 0.0, 100.0, nowdate()]
+ ]
+
+ check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
+ enable_discount_accounting(enable=0)
+
+ def test_additional_discount_for_sales_invoice_with_discount_accounting_enabled(self):
+ from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import enable_discount_accounting
+
+ enable_discount_accounting()
+ additional_discount_account = create_account(account_name="Discount Account",
+ parent_account="Indirect Expenses - _TC", company="_Test Company")
+
+ si = create_sales_invoice(parent_cost_center='Main - _TC', do_not_save=1)
+ si.apply_discount_on = "Grand Total"
+ si.additional_discount_account = additional_discount_account
+ si.additional_discount_percentage = 20
+ si.append("taxes", {
+ "charge_type": "On Net Total",
+ "account_head": "_Test Account VAT - _TC",
+ "cost_center": "Main - _TC",
+ "description": "Test",
+ "rate": 10
+ })
+ si.submit()
+
+ expected_gle = [
+ ["_Test Account VAT - _TC", 0.0, 10.0, nowdate()],
+ ["Debtors - _TC", 88, 0.0, nowdate()],
+ ["Discount Account - _TC", 22.0, 0.0, nowdate()],
+ ["Sales - _TC", 0.0, 100.0, nowdate()]
+ ]
+
+ check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
+ enable_discount_accounting(enable=0)
+
def get_sales_invoice_for_e_invoice():
si = make_sales_invoice_for_ewaybill()
si.naming_series = 'INV-2020-.#####'
@@ -2062,6 +2112,30 @@
address.save()
+ if not frappe.db.exists('Address', '_Test Dispatch-Address for Eway bill-Shipping'):
+ address = frappe.get_doc({
+ "address_line1": "_Test Dispatch Address Line 1",
+ "address_title": "_Test Dispatch-Address for Eway bill",
+ "address_type": "Shipping",
+ "city": "_Test City",
+ "state": "Test State",
+ "country": "India",
+ "doctype": "Address",
+ "is_primary_address": 0,
+ "phone": "+910000000000",
+ "gstin": "07AAACC1206D1ZI",
+ "gst_state": "Delhi",
+ "gst_state_number": "07",
+ "pincode": "1100101"
+ }).insert()
+
+ address.append("links", {
+ "link_doctype": "Company",
+ "link_name": "_Test Company"
+ })
+
+ address.save()
+
def make_test_transporter_for_ewaybill():
if not frappe.db.exists('Supplier', '_Test Transporter'):
frappe.get_doc({
@@ -2100,6 +2174,7 @@
si.distance = 2000
si.company_address = "_Test Address for Eway bill-Billing"
si.customer_address = "_Test Customer-Address for Eway bill-Shipping"
+ si.dispatch_address_name = "_Test Dispatch-Address for Eway bill-Shipping"
si.vehicle_no = "KA12KA1234"
si.gst_category = "Registered Regular"
si.mode_of_transport = 'Road'
@@ -2152,6 +2227,7 @@
si.currency=args.currency or "INR"
si.conversion_rate = args.conversion_rate or 1
si.naming_series = args.naming_series or "T-SINV-"
+ si.cost_center = args.parent_cost_center
si.append("items", {
"item_code": args.item or args.item_code or "_Test Item",
@@ -2163,8 +2239,11 @@
"uom": args.uom or "Nos",
"stock_uom": args.uom or "Nos",
"rate": args.rate if args.get("rate") is not None else 100,
+ "price_list_rate": args.price_list_rate if args.get("price_list_rate") is not None else 100,
"income_account": args.income_account or "Sales - _TC",
"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
+ "discount_account": args.discount_account or None,
+ "discount_amount": args.discount_amount or 0,
"cost_center": args.cost_center or "_Test Cost Center - _TC",
"serial_no": args.serial_no,
"conversion_factor": 1
diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
index 8e6952a..b65903b 100644
--- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
+++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
@@ -63,6 +63,7 @@
"finance_book",
"col_break4",
"expense_account",
+ "discount_account",
"deferred_revenue",
"deferred_revenue_account",
"service_stop_date",
@@ -821,12 +822,18 @@
"no_copy": 1,
"options": "currency",
"read_only": 1
+ },
+ {
+ "fieldname": "discount_account",
+ "fieldtype": "Link",
+ "label": "Discount Account",
+ "options": "Account"
}
],
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2021-02-23 01:05:22.123527",
+ "modified": "2021-07-05 15:07:22.857128",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",
diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json
index 1b7a0fe..cfdb167 100644
--- a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json
+++ b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json
@@ -27,7 +27,8 @@
"base_tax_amount",
"base_total",
"base_tax_amount_after_discount_amount",
- "item_wise_tax_detail"
+ "item_wise_tax_detail",
+ "dont_recompute_tax"
],
"fields": [
{
@@ -200,13 +201,22 @@
"fieldname": "included_in_paid_amount",
"fieldtype": "Check",
"label": "Considered In Paid Amount"
+ },
+ {
+ "default": "0",
+ "fieldname": "dont_recompute_tax",
+ "fieldtype": "Check",
+ "hidden": 1,
+ "label": "Dont Recompute tax",
+ "print_hide": 1,
+ "read_only": 1
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2021-06-14 01:44:36.899147",
+ "modified": "2021-07-27 12:40:59.051803",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Taxes and Charges",
diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.json b/erpnext/accounts/doctype/subscription_plan/subscription_plan.json
index 46ce093..771611a 100644
--- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.json
+++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.json
@@ -78,7 +78,7 @@
"label": "Cost"
},
{
- "depends_on": "eval:doc.price_determination==\"Based on price list\"",
+ "depends_on": "eval:doc.price_determination==\"Based On Price List\"",
"fieldname": "price_list",
"fieldtype": "Link",
"label": "Price List",
@@ -147,7 +147,7 @@
}
],
"links": [],
- "modified": "2020-06-25 10:53:44.205774",
+ "modified": "2021-08-09 10:53:44.205774",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Subscription Plan",
diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json
index f9160e2..153906f 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json
@@ -1,263 +1,151 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 1,
- "allow_rename": 1,
- "autoname": "Prompt",
- "beta": 0,
- "creation": "2018-04-13 18:42:06.431683",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "allow_import": 1,
+ "allow_rename": 1,
+ "autoname": "Prompt",
+ "creation": "2018-04-13 18:42:06.431683",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "category_details_section",
+ "category_name",
+ "round_off_tax_amount",
+ "column_break_2",
+ "consider_party_ledger_amount",
+ "tax_on_excess_amount",
+ "section_break_8",
+ "rates",
+ "section_break_7",
+ "accounts"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "category_name",
"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": "Category Name",
- "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
- },
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "section_break_8",
"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": "Tax Withholding Rates",
- "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
+ "show_days": 1,
+ "show_seconds": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "rates",
"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": "Rates",
- "length": 0,
- "no_copy": 0,
"options": "Tax Withholding Rate",
- "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
+ "show_days": 1,
+ "show_seconds": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "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,
+ "fieldname": "section_break_7",
+ "fieldtype": "Section Break",
"label": "Account Details",
- "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
- },
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "accounts",
- "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": "Accounts",
- "length": 0,
- "no_copy": 0,
- "options": "Tax Withholding Account",
- "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
+ "fieldname": "accounts",
+ "fieldtype": "Table",
+ "label": "Accounts",
+ "options": "Tax Withholding Account",
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "category_details_section",
+ "fieldtype": "Section Break",
+ "label": "Category Details",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "column_break_2",
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "default": "0",
+ "description": "Even invoices with apply tax withholding unchecked will be considered for checking cumulative threshold breach",
+ "fieldname": "consider_party_ledger_amount",
+ "fieldtype": "Check",
+ "label": "Consider Entire Party Ledger Amount",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "default": "0",
+ "description": "Tax will be withheld only for amount exceeding the cumulative threshold",
+ "fieldname": "tax_on_excess_amount",
+ "fieldtype": "Check",
+ "label": "Only Deduct Tax On Excess Amount ",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "description": "Checking this will round off the tax amount to the nearest integer",
+ "fieldname": "round_off_tax_amount",
+ "fieldtype": "Check",
+ "label": "Round Off Tax Amount",
+ "show_days": 1,
+ "show_seconds": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-07-17 22:53:26.193179",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Tax Withholding Category",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2021-07-27 21:47:34.396071",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Tax Withholding Category",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Accounts Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts Manager",
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Accounts User",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts User",
+ "share": 1,
"write": 1
}
- ],
- "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_views": 0
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
index b9ee4a0..481ef28 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -6,7 +6,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
-from frappe.utils import flt, getdate
+from frappe.utils import flt, getdate, cint
from erpnext.accounts.utils import get_fiscal_year
class TaxWithholdingCategory(Document):
@@ -86,7 +86,10 @@
"rate": tax_rate_detail.tax_withholding_rate,
"threshold": tax_rate_detail.single_threshold,
"cumulative_threshold": tax_rate_detail.cumulative_threshold,
- "description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category
+ "description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category,
+ "consider_party_ledger_amount": tax_withholding.consider_party_ledger_amount,
+ "tax_on_excess_amount": tax_withholding.tax_on_excess_amount,
+ "round_off_tax_amount": tax_withholding.round_off_tax_amount
})
def get_tax_withholding_rates(tax_withholding, fiscal_year):
@@ -145,6 +148,7 @@
def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, pan_no=None):
fiscal_year = fiscal_year_details[0]
+
vouchers = get_invoice_vouchers(parties, fiscal_year, inv.company, party_type=party_type)
advance_vouchers = get_advance_vouchers(parties, fiscal_year, inv.company, party_type=party_type)
taxable_vouchers = vouchers + advance_vouchers
@@ -235,10 +239,18 @@
def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_deducted, vouchers):
tds_amount = 0
+ invoice_filters = {
+ 'name': ('in', vouchers),
+ 'docstatus': 1
+ }
- supp_credit_amt = frappe.db.get_value('Purchase Invoice', {
- 'name': ('in', vouchers), 'docstatus': 1, 'apply_tds': 1
- }, 'sum(net_total)') or 0.0
+ field = 'sum(net_total)'
+
+ if not cint(tax_details.consider_party_ledger_amount):
+ invoice_filters.update({'apply_tds': 1})
+ field = 'sum(grand_total)'
+
+ supp_credit_amt = frappe.db.get_value('Purchase Invoice', invoice_filters, field) or 0.0
supp_jv_credit_amt = frappe.db.get_value('Journal Entry Account', {
'parent': ('in', vouchers), 'docstatus': 1,
@@ -255,6 +267,13 @@
cumulative_threshold = tax_details.get('cumulative_threshold', 0)
if ((threshold and inv.net_total >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)):
+ if (cumulative_threshold and supp_credit_amt >= cumulative_threshold) and cint(tax_details.tax_on_excess_amount):
+ # Get net total again as TDS is calculated on net total
+ # Grand is used to just check for threshold breach
+ net_total = frappe.db.get_value('Purchase Invoice', invoice_filters, 'sum(net_total)') or 0.0
+ net_total += inv.net_total
+ supp_credit_amt = net_total - cumulative_threshold
+
if ldc and is_valid_certificate(
ldc.valid_from, ldc.valid_upto,
inv.get('posting_date') or inv.get('transaction_date'), tax_deducted,
@@ -263,6 +282,9 @@
tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details)
else:
tds_amount = supp_credit_amt * tax_details.rate / 100 if supp_credit_amt > 0 else 0
+
+ if cint(tax_details.round_off_tax_amount):
+ tds_amount = round(tds_amount)
return tds_amount
diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
index dd26be7..2ba22ca 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
@@ -87,6 +87,31 @@
for d in invoices:
d.cancel()
+ def test_tax_withholding_category_checks(self):
+ invoices = []
+ frappe.db.set_value("Supplier", "Test TDS Supplier3", "tax_withholding_category", "New TDS Category")
+
+ # First Invoice with no tds check
+ pi = create_purchase_invoice(supplier = "Test TDS Supplier3", rate = 20000, do_not_save=True)
+ pi.apply_tds = 0
+ pi.save()
+ pi.submit()
+ invoices.append(pi)
+
+ # Second Invoice will apply TDS checked
+ pi1 = create_purchase_invoice(supplier = "Test TDS Supplier3", rate = 20000)
+ pi1.submit()
+ invoices.append(pi1)
+
+ # Cumulative threshold is 30000
+ # Threshold calculation should be on both the invoices
+ # TDS should be applied only on 1000
+ self.assertEqual(pi1.taxes[0].tax_amount, 1000)
+
+ for d in invoices:
+ d.cancel()
+
+
def test_cumulative_threshold_tcs(self):
frappe.db.set_value("Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS")
invoices = []
@@ -195,7 +220,7 @@
def create_records():
# create a new suppliers
- for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']:
+ for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2', 'Test TDS Supplier3']:
if frappe.db.exists('Supplier', name):
continue
@@ -311,3 +336,23 @@
'account': 'TDS - _TC'
}]
}).insert()
+
+ if not frappe.db.exists("Tax Withholding Category", "New TDS Category"):
+ frappe.get_doc({
+ "doctype": "Tax Withholding Category",
+ "name": "New TDS Category",
+ "category_name": "New TDS Category",
+ "round_off_tax_amount": 1,
+ "consider_party_ledger_amount": 1,
+ "tax_on_excess_amount": 1,
+ "rates": [{
+ 'fiscal_year': fiscal_year,
+ 'tax_withholding_rate': 10,
+ 'single_threshold': 0,
+ 'cumulative_threshold': 30000
+ }],
+ "accounts": [{
+ 'company': '_Test Company',
+ 'account': 'TDS - _TC'
+ }]
+ }).insert()
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index b97dc40..329f9a9 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -8,7 +8,7 @@
from frappe.core.doctype.user_permission.user_permission import get_permitted_documents
from frappe.model.utils import get_fetch_values
from frappe.utils import (add_days, getdate, formatdate, date_diff,
- add_years, get_timestamp, nowdate, flt, cstr, add_months, get_last_day)
+ add_years, get_timestamp, nowdate, flt, cstr, add_months, get_last_day, cint)
from frappe.contacts.doctype.address.address import (get_address_display,
get_default_address, get_company_address)
from frappe.contacts.doctype.contact.contact import get_contact_details
@@ -58,7 +58,7 @@
customer_group=party_details.customer_group, supplier_group=party_details.supplier_group, tax_category=party_details.tax_category,
billing_address=party_address, shipping_address=shipping_address)
- if fetch_payment_terms_template:
+ if cint(fetch_payment_terms_template):
party_details["payment_terms_template"] = get_payment_terms_template(party.name, party_type, company)
if not party_details.get("currency"):
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index a11b77a..b54646f 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -99,7 +99,6 @@
voucher_no = gle.voucher_no,
party = gle.party,
posting_date = gle.posting_date,
- remarks = gle.remarks,
account_currency = gle.account_currency,
invoiced = 0.0,
paid = 0.0,
@@ -579,7 +578,7 @@
self.gl_entries = frappe.db.sql("""
select
name, posting_date, account, party_type, party, voucher_type, voucher_no, cost_center,
- against_voucher_type, against_voucher, account_currency, remarks, {0}
+ against_voucher_type, against_voucher, account_currency, {0}
from
`tabGL Entry`
where
@@ -792,8 +791,6 @@
self.add_column(label=_('Supplier Group'), fieldname='supplier_group', fieldtype='Link',
options='Supplier Group')
- self.add_column(label=_('Remarks'), fieldname='remarks', fieldtype='Text', width=200)
-
def add_column(self, label, fieldname=None, fieldtype='Currency', options=None, width=120):
if not fieldname: fieldname = scrub(label)
if fieldtype=='Currency': options='currency'
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index e724e9b..1759fa3 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -55,9 +55,11 @@
if not account_details.get(account):
frappe.throw(_("Account {0} does not exists").format(account))
- if (filters.get("account") and filters.get("group_by") == _('Group by Account')
- and account_details[filters.account].is_group == 0):
- frappe.throw(_("Can not filter based on Account, if grouped by Account"))
+ if (filters.get("account") and filters.get("group_by") == _('Group by Account')):
+ filters.account = frappe.parse_json(filters.get('account'))
+ for account in filters.account:
+ if account_details[account].is_group == 0:
+ frappe.throw(_("Can not filter based on Child Account, if grouped by Account"))
if (filters.get("voucher_no")
and filters.get("group_by") in [_('Group by Voucher')]):
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py
index 84c7454..6d8623c 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/gross_profit.py
@@ -241,6 +241,7 @@
sle.voucher_detail_no == row.item_row:
previous_stock_value = len(my_sle) > i+1 and \
flt(my_sle[i+1].stock_value) or 0.0
+
if previous_stock_value:
return (previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty))
else:
@@ -335,7 +336,7 @@
res = frappe.db.sql("""select item_code, voucher_type, voucher_no,
voucher_detail_no, stock_value, warehouse, actual_qty as qty
from `tabStock Ledger Entry`
- where company=%(company)s
+ where company=%(company)s and is_cancelled = 0
order by
item_code desc, warehouse desc, posting_date desc,
posting_time desc, creation desc""", self.filters, as_dict=True)
diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
index e15715d..6b9df41 100644
--- a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
+++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
@@ -75,7 +75,8 @@
select voucher_no, credit
from `tabGL Entry`
where party in (%s) and credit > 0
- and company=%s and posting_date between %s and %s
+ and company=%s and is_cancelled = 0
+ and posting_date between %s and %s
""", (supplier, company, from_date, to_date), as_dict=1)
supplier_credit_amount = flt(sum(d.credit for d in entries))
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 1cdbd8d..9afe365 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -966,7 +966,7 @@
for e in existing_gle:
if entry.account == e.account:
account_existed = True
- if (entry.account == e.account and entry.against_account == e.against_account
+ if (entry.account == e.account
and (not entry.cost_center or not e.cost_center or entry.cost_center == e.cost_center)
and ( flt(entry.debit, precision) != flt(e.debit, precision) or
flt(entry.credit, precision) != flt(e.credit, precision))):
diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js
index 6f1bb28..922cc4a 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -82,24 +82,46 @@
if (in_list(["Submitted", "Partially Depreciated", "Fully Depreciated"], frm.doc.status)) {
frm.add_custom_button("Transfer Asset", function() {
erpnext.asset.transfer_asset(frm);
- });
+ }, __("Manage"));
frm.add_custom_button("Scrap Asset", function() {
erpnext.asset.scrap_asset(frm);
- });
+ }, __("Manage"));
frm.add_custom_button("Sell Asset", function() {
frm.trigger("make_sales_invoice");
- });
+ }, __("Manage"));
} else if (frm.doc.status=='Scrapped') {
frm.add_custom_button("Restore Asset", function() {
erpnext.asset.restore_asset(frm);
- });
+ }, __("Manage"));
+ }
+
+ if (frm.doc.maintenance_required && !frm.doc.maintenance_schedule) {
+ frm.add_custom_button(__("Maintain Asset"), function() {
+ frm.trigger("create_asset_maintenance");
+ }, __("Manage"));
+ }
+
+ frm.add_custom_button(__("Repair Asset"), function() {
+ frm.trigger("create_asset_repair");
+ }, __("Manage"));
+
+ if (frm.doc.status != 'Fully Depreciated') {
+ frm.add_custom_button(__("Adjust Asset Value"), function() {
+ frm.trigger("create_asset_adjustment");
+ }, __("Manage"));
+ }
+
+ if (!frm.doc.calculate_depreciation) {
+ frm.add_custom_button(__("Create Depreciation Entry"), function() {
+ frm.trigger("make_journal_entry");
+ }, __("Manage"));
}
if (frm.doc.purchase_receipt || !frm.doc.is_existing_asset) {
- frm.add_custom_button("General Ledger", function() {
+ frm.add_custom_button("View General Ledger", function() {
frappe.route_options = {
"voucher_no": frm.doc.name,
"from_date": frm.doc.available_for_use_date,
@@ -107,27 +129,9 @@
"company": frm.doc.company
};
frappe.set_route("query-report", "General Ledger");
- });
+ }, __("Manage"));
}
- if (frm.doc.maintenance_required && !frm.doc.maintenance_schedule) {
- frm.add_custom_button(__("Asset Maintenance"), function() {
- frm.trigger("create_asset_maintenance");
- }, __('Create'));
- }
- if (frm.doc.status != 'Fully Depreciated') {
- frm.add_custom_button(__("Asset Value Adjustment"), function() {
- frm.trigger("create_asset_adjustment");
- }, __('Create'));
- }
-
- if (!frm.doc.calculate_depreciation) {
- frm.add_custom_button(__("Depreciation Entry"), function() {
- frm.trigger("make_journal_entry");
- }, __('Create'));
- }
-
- frm.page.set_inner_btn_group_as_primary(__('Create'));
frm.trigger("setup_chart");
}
@@ -304,6 +308,20 @@
})
},
+ create_asset_repair: function(frm) {
+ frappe.call({
+ args: {
+ "asset": frm.doc.name,
+ "asset_name": frm.doc.asset_name
+ },
+ method: "erpnext.assets.doctype.asset.asset.create_asset_repair",
+ callback: function(r) {
+ var doclist = frappe.model.sync(r.message);
+ frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
+ }
+ });
+ },
+
create_asset_adjustment: function(frm) {
frappe.call({
args: {
diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json
index 421b9a6..de06075 100644
--- a/erpnext/assets/doctype/asset/asset.json
+++ b/erpnext/assets/doctype/asset/asset.json
@@ -502,7 +502,7 @@
"link_fieldname": "asset"
}
],
- "modified": "2021-01-22 12:38:59.091510",
+ "modified": "2021-06-24 14:58:51.097908",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 8799275..ecc35b0 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -168,17 +168,24 @@
d.precision("rate_of_depreciation"))
def make_depreciation_schedule(self):
- if 'Manual' not in [d.depreciation_method for d in self.finance_books]:
+ if 'Manual' not in [d.depreciation_method for d in self.finance_books] and not self.schedules:
self.schedules = []
- if self.get("schedules") or not self.available_for_use_date:
+ if not self.available_for_use_date:
return
for d in self.get('finance_books'):
self.validate_asset_finance_books(d)
+
+ start = self.clear_depreciation_schedule()
- value_after_depreciation = (flt(self.gross_purchase_amount) -
- flt(self.opening_accumulated_depreciation))
+ # value_after_depreciation - current Asset value
+ if d.value_after_depreciation:
+ value_after_depreciation = (flt(d.value_after_depreciation) -
+ flt(self.opening_accumulated_depreciation))
+ else:
+ value_after_depreciation = (flt(self.gross_purchase_amount) -
+ flt(self.opening_accumulated_depreciation))
d.value_after_depreciation = value_after_depreciation
@@ -191,7 +198,7 @@
number_of_pending_depreciations += 1
skip_row = False
- for n in range(number_of_pending_depreciations):
+ for n in range(start, number_of_pending_depreciations):
# If depreciation is already completed (for double declining balance)
if skip_row: continue
@@ -216,11 +223,13 @@
# For last row
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
- to_date = add_months(self.available_for_use_date,
- n * cint(d.frequency_of_depreciation))
+ if not self.flags.increase_in_asset_life:
+ # In case of increase_in_asset_life, the self.to_date is already set on asset_repair submission
+ self.to_date = add_months(self.available_for_use_date,
+ n * cint(d.frequency_of_depreciation))
depreciation_amount, days, months = self.get_pro_rata_amt(d,
- depreciation_amount, schedule_date, to_date)
+ depreciation_amount, schedule_date, self.to_date)
monthly_schedule_date = add_months(schedule_date, 1)
@@ -284,10 +293,23 @@
"finance_book_id": d.idx
})
+ # used when depreciation schedule needs to be modified due to increase in asset life
+ def clear_depreciation_schedule(self):
+ start = 0
+ for n in range(len(self.schedules)):
+ if not self.schedules[n].journal_entry:
+ del self.schedules[n:]
+ start = n
+ break
+ return start
+
+
+ # if it returns True, depreciation_amount will not be equal for the first and last rows
def check_is_pro_rata(self, row):
has_pro_rata = False
-
days = date_diff(row.depreciation_start_date, self.available_for_use_date) + 1
+
+ # if frequency_of_depreciation is 12 months, total_days = 365
total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation)
if days < total_days:
@@ -346,11 +368,12 @@
if d.finance_book_id not in finance_books:
accumulated_depreciation = flt(self.opening_accumulated_depreciation)
value_after_depreciation = flt(self.get_value_after_depreciation(d.finance_book_id))
- finance_books.append(d.finance_book_id)
+ finance_books.append(int(d.finance_book_id))
depreciation_amount = flt(d.depreciation_amount, d.precision("depreciation_amount"))
value_after_depreciation -= flt(depreciation_amount)
+ # for the last row, if depreciation method = Straight Line
if straight_line_idx and i == max(straight_line_idx) - 1:
book = self.get('finance_books')[cint(d.finance_book_id) - 1]
depreciation_amount += flt(value_after_depreciation -
@@ -626,8 +649,17 @@
return asset_maintenance
@frappe.whitelist()
+def create_asset_repair(asset, asset_name):
+ asset_repair = frappe.new_doc("Asset Repair")
+ asset_repair.update({
+ "asset": asset,
+ "asset_name": asset_name
+ })
+ return asset_repair
+
+@frappe.whitelist()
def create_asset_adjustment(asset, asset_category, company):
- asset_maintenance = frappe.new_doc("Asset Value Adjustment")
+ asset_maintenance = frappe.get_doc("Asset Value Adjustment")
asset_maintenance.update({
"asset": asset,
"company": company,
@@ -757,9 +789,16 @@
depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked)
if row.depreciation_method in ("Straight Line", "Manual"):
- depreciation_amount = (flt(row.value_after_depreciation) -
- flt(row.expected_value_after_useful_life)) / depreciation_left
+ # if the Depreciation Schedule is being prepared for the first time
+ if not asset.flags.increase_in_asset_life:
+ depreciation_amount = (flt(row.value_after_depreciation) -
+ flt(row.expected_value_after_useful_life)) / depreciation_left
+
+ # if the Depreciation Schedule is being modified after Asset Repair
+ else:
+ depreciation_amount = (flt(row.value_after_depreciation) -
+ flt(row.expected_value_after_useful_life)) / (date_diff(asset.to_date, asset.available_for_use_date) / 365)
else:
depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100))
- return depreciation_amount
\ No newline at end of file
+ return depreciation_amount
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index 8845f24..e23a715 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -125,7 +125,6 @@
"frequency_of_depreciation": 12,
"depreciation_start_date": "2030-12-31"
})
- asset.insert()
self.assertEqual(asset.status, "Draft")
asset.save()
expected_schedules = [
@@ -154,9 +153,8 @@
"frequency_of_depreciation": 12,
"depreciation_start_date": '2030-12-31'
})
- asset.insert()
- self.assertEqual(asset.status, "Draft")
asset.save()
+ self.assertEqual(asset.status, "Draft")
expected_schedules = [
['2030-12-31', 66667.00, 66667.00],
@@ -185,7 +183,7 @@
"frequency_of_depreciation": 12,
"depreciation_start_date": "2030-12-31"
})
- asset.insert()
+ asset.save()
self.assertEqual(asset.status, "Draft")
expected_schedules = [
@@ -216,7 +214,6 @@
"depreciation_start_date": "2030-12-31"
})
- asset.insert()
asset.save()
expected_schedules = [
@@ -247,7 +244,6 @@
"frequency_of_depreciation": 10,
"depreciation_start_date": "2020-12-31"
})
- asset.insert()
asset.submit()
asset.load_from_db()
self.assertEqual(asset.status, "Submitted")
@@ -350,7 +346,6 @@
"frequency_of_depreciation": 10,
"depreciation_start_date": "2020-12-31"
})
- asset.insert()
asset.submit()
post_depreciation_entries(date="2021-01-01")
@@ -380,7 +375,6 @@
"total_number_of_depreciations": 10,
"frequency_of_depreciation": 1
})
- asset.insert()
asset.submit()
post_depreciation_entries(date=add_months('2020-01-01', 4))
@@ -424,7 +418,6 @@
"frequency_of_depreciation": 10,
"depreciation_start_date": "2020-12-31"
})
- asset.insert()
asset.submit()
post_depreciation_entries(date="2021-01-01")
@@ -468,7 +461,7 @@
"total_number_of_depreciations": 3,
"frequency_of_depreciation": 10
})
- asset.insert()
+ asset.save()
accumulated_depreciation_after_full_schedule = \
max(d.accumulated_depreciation_amount for d in asset.get("schedules"))
@@ -646,7 +639,7 @@
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
asset = frappe.get_doc('Asset', asset_name)
asset.calculate_depreciation = 1
- asset.available_for_use_date = '2030-06-12'
+ asset.available_for_use_date = '2030-07-12'
asset.purchase_date = '2030-01-01'
asset.append("finance_books", {
"expected_value_after_useful_life": 1000,
@@ -660,10 +653,10 @@
self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
expected_schedules = [
- ["2030-12-31", 1106.85, 1106.85],
- ["2031-12-31", 3446.58, 4553.43],
- ["2032-12-31", 1723.29, 6276.72],
- ["2033-06-12", 723.28, 7000.00]
+ ["2030-12-31", 942.47, 942.47],
+ ["2031-12-31", 3528.77, 4471.24],
+ ["2032-12-31", 1764.38, 6235.62],
+ ["2033-07-12", 764.38, 7000.00]
]
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
@@ -699,7 +692,7 @@
"item_code": args.item_code or "Macbook Pro",
"company": args.company or"_Test Company",
"purchase_date": "2015-01-01",
- "calculate_depreciation": 0,
+ "calculate_depreciation": args.calculate_depreciation or 0,
"gross_purchase_amount": 100000,
"purchase_receipt_amount": 100000,
"expected_value_after_useful_life": 10000,
@@ -707,9 +700,16 @@
"available_for_use_date": "2020-06-06",
"location": "Test Location",
"asset_owner": "Company",
- "is_existing_asset": args.is_existing_asset or 0
+ "is_existing_asset": 1
})
+ if asset.calculate_depreciation:
+ asset.append("finance_books", {
+ "depreciation_method": "Straight Line",
+ "frequency_of_depreciation": 12,
+ "total_number_of_depreciations": 5
+ })
+
try:
asset.save()
except frappe.DuplicateEntryError:
diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
index d9b7b69..e5a5f19 100644
--- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
+++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
@@ -67,7 +67,6 @@
{
"fieldname": "value_after_depreciation",
"fieldtype": "Currency",
- "hidden": 1,
"label": "Value After Depreciation",
"no_copy": 1,
"options": "Company:company:default_currency",
@@ -85,7 +84,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2020-11-05 16:30:09.213479",
+ "modified": "2021-06-17 12:59:05.743683",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Finance Book",
diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.js b/erpnext/assets/doctype/asset_repair/asset_repair.js
index 4ba2b44..1cebfff 100644
--- a/erpnext/assets/doctype/asset_repair/asset_repair.js
+++ b/erpnext/assets/doctype/asset_repair/asset_repair.js
@@ -2,6 +2,45 @@
// For license information, please see license.txt
frappe.ui.form.on('Asset Repair', {
+ setup: function(frm) {
+ frm.fields_dict.cost_center.get_query = function(doc) {
+ return {
+ filters: {
+ 'is_group': 0,
+ 'company': doc.company
+ }
+ };
+ };
+
+ frm.fields_dict.project.get_query = function(doc) {
+ return {
+ filters: {
+ 'company': doc.company
+ }
+ };
+ };
+
+ frm.fields_dict.warehouse.get_query = function(doc) {
+ return {
+ filters: {
+ 'is_group': 0,
+ 'company': doc.company
+ }
+ };
+ };
+ },
+
+ refresh: function(frm) {
+ if (frm.doc.docstatus) {
+ frm.add_custom_button("View General Ledger", function() {
+ frappe.route_options = {
+ "voucher_no": frm.doc.name
+ };
+ frappe.set_route("query-report", "General Ledger");
+ });
+ }
+ },
+
repair_status: (frm) => {
if (frm.doc.completion_date && frm.doc.repair_status == "Completed") {
frappe.call ({
@@ -17,5 +56,16 @@
}
});
}
+
+ if (frm.doc.repair_status == "Completed") {
+ frm.set_value('completion_date', frappe.datetime.now_datetime());
+ }
}
});
+
+frappe.ui.form.on('Asset Repair Consumed Item', {
+ consumed_quantity: function(frm, cdt, cdn) {
+ var row = locals[cdt][cdn];
+ frappe.model.set_value(cdt, cdn, 'total_value', row.consumed_quantity * row.valuation_rate);
+ },
+});
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.json b/erpnext/assets/doctype/asset_repair/asset_repair.json
index d338fc0..19528a2 100644
--- a/erpnext/assets/doctype/asset_repair/asset_repair.json
+++ b/erpnext/assets/doctype/asset_repair/asset_repair.json
@@ -7,39 +7,44 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
- "naming_series",
- "asset_name",
+ "asset",
+ "company",
"column_break_2",
- "item_code",
- "item_name",
+ "asset_name",
+ "naming_series",
"section_break_5",
"failure_date",
- "assign_to",
- "assign_to_name",
+ "repair_status",
"column_break_6",
"completion_date",
- "repair_status",
+ "accounting_dimensions_section",
+ "cost_center",
+ "column_break_14",
+ "project",
+ "accounting_details",
"repair_cost",
+ "capitalize_repair_cost",
+ "stock_consumption",
+ "column_break_8",
+ "purchase_invoice",
+ "stock_consumption_details_section",
+ "warehouse",
+ "stock_items",
+ "total_repair_cost",
+ "stock_entry",
+ "asset_depreciation_details_section",
+ "increase_in_asset_life",
"section_break_9",
"description",
"column_break_9",
"actions_performed",
- "section_break_17",
+ "section_break_23",
"downtime",
"column_break_19",
"amended_from"
],
"fields": [
{
- "columns": 1,
- "fieldname": "asset_name",
- "fieldtype": "Link",
- "in_list_view": 1,
- "label": "Asset",
- "options": "Asset",
- "reqd": 1
- },
- {
"fieldname": "naming_series",
"fieldtype": "Select",
"label": "Series",
@@ -51,18 +56,6 @@
"fieldtype": "Column Break"
},
{
- "fetch_from": "asset_name.item_code",
- "fieldname": "item_code",
- "fieldtype": "Read Only",
- "label": "Item Code"
- },
- {
- "fetch_from": "asset_name.item_name",
- "fieldname": "item_name",
- "fieldtype": "Read Only",
- "label": "Item Name"
- },
- {
"fieldname": "section_break_5",
"fieldtype": "Section Break",
"label": "Repair Details"
@@ -75,32 +68,19 @@
"reqd": 1
},
{
- "allow_on_submit": 1,
- "fieldname": "assign_to",
- "fieldtype": "Link",
- "label": "Assign To",
- "options": "User"
- },
- {
- "allow_on_submit": 1,
- "fetch_from": "assign_to.full_name",
- "fieldname": "assign_to_name",
- "fieldtype": "Read Only",
- "label": "Assign To Name"
- },
- {
"fieldname": "column_break_6",
"fieldtype": "Column Break"
},
{
- "allow_on_submit": 1,
+ "depends_on": "eval:!doc.__islocal",
"fieldname": "completion_date",
"fieldtype": "Datetime",
- "label": "Completion Date"
+ "label": "Completion Date",
+ "no_copy": 1
},
{
- "allow_on_submit": 1,
"default": "Pending",
+ "depends_on": "eval:!doc.__islocal",
"fieldname": "repair_status",
"fieldtype": "Select",
"label": "Repair Status",
@@ -116,25 +96,18 @@
{
"fieldname": "description",
"fieldtype": "Long Text",
- "label": "Error Description",
- "reqd": 1
+ "label": "Error Description"
},
{
"fieldname": "column_break_9",
"fieldtype": "Column Break"
},
{
- "allow_on_submit": 1,
"fieldname": "actions_performed",
"fieldtype": "Long Text",
"label": "Actions performed"
},
{
- "fieldname": "section_break_17",
- "fieldtype": "Section Break"
- },
- {
- "allow_on_submit": 1,
"fieldname": "downtime",
"fieldtype": "Data",
"in_list_view": 1,
@@ -146,7 +119,7 @@
"fieldtype": "Column Break"
},
{
- "allow_on_submit": 1,
+ "default": "0",
"fieldname": "repair_cost",
"fieldtype": "Currency",
"label": "Repair Cost"
@@ -159,12 +132,138 @@
"options": "Asset Repair",
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "columns": 1,
+ "fieldname": "asset",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Asset",
+ "options": "Asset",
+ "reqd": 1
+ },
+ {
+ "fetch_from": "asset.asset_name",
+ "fieldname": "asset_name",
+ "fieldtype": "Read Only",
+ "label": "Asset Name"
+ },
+ {
+ "fieldname": "column_break_8",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "0",
+ "depends_on": "eval:!doc.__islocal",
+ "fieldname": "capitalize_repair_cost",
+ "fieldtype": "Check",
+ "label": "Capitalize Repair Cost"
+ },
+ {
+ "fieldname": "accounting_details",
+ "fieldtype": "Section Break",
+ "label": "Accounting Details"
+ },
+ {
+ "fieldname": "stock_items",
+ "fieldtype": "Table",
+ "label": "Stock Items",
+ "mandatory_depends_on": "stock_consumption",
+ "options": "Asset Repair Consumed Item"
+ },
+ {
+ "fieldname": "section_break_23",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "accounting_dimensions_section",
+ "fieldtype": "Section Break",
+ "label": "Accounting Dimensions"
+ },
+ {
+ "fieldname": "cost_center",
+ "fieldtype": "Link",
+ "label": "Cost Center",
+ "options": "Cost Center"
+ },
+ {
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "label": "Project",
+ "options": "Project"
+ },
+ {
+ "fieldname": "column_break_14",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "0",
+ "depends_on": "eval:!doc.__islocal",
+ "fieldname": "stock_consumption",
+ "fieldtype": "Check",
+ "label": "Stock Consumed During Repair"
+ },
+ {
+ "depends_on": "stock_consumption",
+ "fieldname": "stock_consumption_details_section",
+ "fieldtype": "Section Break",
+ "label": "Stock Consumption Details"
+ },
+ {
+ "depends_on": "eval: doc.stock_consumption && doc.total_repair_cost > 0",
+ "description": "Sum of Repair Cost and Value of Consumed Stock Items.",
+ "fieldname": "total_repair_cost",
+ "fieldtype": "Currency",
+ "label": "Total Repair Cost",
+ "read_only": 1
+ },
+ {
+ "depends_on": "stock_consumption",
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "label": "Warehouse",
+ "options": "Warehouse"
+ },
+ {
+ "depends_on": "capitalize_repair_cost",
+ "fieldname": "asset_depreciation_details_section",
+ "fieldtype": "Section Break",
+ "label": "Asset Depreciation Details"
+ },
+ {
+ "fieldname": "increase_in_asset_life",
+ "fieldtype": "Int",
+ "label": "Increase In Asset Life(Months)",
+ "no_copy": 1
+ },
+ {
+ "depends_on": "eval:!doc.__islocal",
+ "fieldname": "purchase_invoice",
+ "fieldtype": "Link",
+ "label": "Purchase Invoice",
+ "mandatory_depends_on": "eval: doc.repair_status == 'Completed' && doc.repair_cost > 0",
+ "no_copy": 1,
+ "options": "Purchase Invoice"
+ },
+ {
+ "fetch_from": "asset.company",
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "options": "Company"
+ },
+ {
+ "fieldname": "stock_entry",
+ "fieldtype": "Link",
+ "label": "Stock Entry",
+ "options": "Stock Entry",
+ "read_only": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2021-01-22 15:08:12.495850",
+ "modified": "2021-06-25 13:14:38.307723",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Repair",
@@ -203,6 +302,7 @@
],
"sort_field": "modified",
"sort_order": "DESC",
+ "title_field": "asset_name",
"track_changes": 1,
"track_seen": 1
}
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py
index 049b931..d32fdf7 100644
--- a/erpnext/assets/doctype/asset_repair/asset_repair.py
+++ b/erpnext/assets/doctype/asset_repair/asset_repair.py
@@ -5,16 +5,252 @@
from __future__ import unicode_literals
import frappe
from frappe import _
-from frappe.utils import time_diff_in_hours
-from frappe.model.document import Document
+from frappe.utils import time_diff_in_hours, getdate, add_months, flt, cint
+from erpnext.accounts.general_ledger import make_gl_entries
+from erpnext.assets.doctype.asset.asset import get_asset_account
+from erpnext.controllers.accounts_controller import AccountsController
-class AssetRepair(Document):
+class AssetRepair(AccountsController):
def validate(self):
- if self.repair_status == "Completed" and not self.completion_date:
- frappe.throw(_("Please select Completion Date for Completed Repair"))
+ self.asset_doc = frappe.get_doc('Asset', self.asset)
+ self.update_status()
+ if self.get('stock_items'):
+ self.set_total_value()
+ self.calculate_total_repair_cost()
+
+ def update_status(self):
+ if self.repair_status == 'Pending':
+ frappe.db.set_value('Asset', self.asset, 'status', 'Out of Order')
+ else:
+ self.asset_doc.set_status()
+
+ def set_total_value(self):
+ for item in self.get('stock_items'):
+ item.total_value = flt(item.valuation_rate) * flt(item.consumed_quantity)
+
+ def calculate_total_repair_cost(self):
+ self.total_repair_cost = flt(self.repair_cost)
+
+ total_value_of_stock_consumed = self.get_total_value_of_stock_consumed()
+ self.total_repair_cost += total_value_of_stock_consumed
+
+ def before_submit(self):
+ self.check_repair_status()
+
+ if self.get('stock_consumption') or self.get('capitalize_repair_cost'):
+ self.increase_asset_value()
+ if self.get('stock_consumption'):
+ self.check_for_stock_items_and_warehouse()
+ self.decrease_stock_quantity()
+ if self.get('capitalize_repair_cost'):
+ self.make_gl_entries()
+ if frappe.db.get_value('Asset', self.asset, 'calculate_depreciation') and self.increase_in_asset_life:
+ self.modify_depreciation_schedule()
+
+ self.asset_doc.flags.ignore_validate_update_after_submit = True
+ self.asset_doc.prepare_depreciation_data()
+ self.asset_doc.save()
+
+ def before_cancel(self):
+ self.asset_doc = frappe.get_doc('Asset', self.asset)
+
+ if self.get('stock_consumption') or self.get('capitalize_repair_cost'):
+ self.decrease_asset_value()
+ if self.get('stock_consumption'):
+ self.increase_stock_quantity()
+ if self.get('capitalize_repair_cost'):
+ self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
+ self.make_gl_entries(cancel=True)
+ if frappe.db.get_value('Asset', self.asset, 'calculate_depreciation') and self.increase_in_asset_life:
+ self.revert_depreciation_schedule_on_cancellation()
+
+ self.asset_doc.flags.ignore_validate_update_after_submit = True
+ self.asset_doc.prepare_depreciation_data()
+ self.asset_doc.save()
+
+ def check_repair_status(self):
+ if self.repair_status == "Pending":
+ frappe.throw(_("Please update Repair Status."))
+
+ def check_for_stock_items_and_warehouse(self):
+ if not self.get('stock_items'):
+ frappe.throw(_("Please enter Stock Items consumed during the Repair."), title=_("Missing Items"))
+ if not self.warehouse:
+ frappe.throw(_("Please enter Warehouse from which Stock Items consumed during the Repair were taken."), title=_("Missing Warehouse"))
+
+ def increase_asset_value(self):
+ total_value_of_stock_consumed = self.get_total_value_of_stock_consumed()
+
+ if self.asset_doc.calculate_depreciation:
+ for row in self.asset_doc.finance_books:
+ row.value_after_depreciation += total_value_of_stock_consumed
+
+ if self.capitalize_repair_cost:
+ row.value_after_depreciation += self.repair_cost
+
+ def decrease_asset_value(self):
+ total_value_of_stock_consumed = self.get_total_value_of_stock_consumed()
+
+ if self.asset_doc.calculate_depreciation:
+ for row in self.asset_doc.finance_books:
+ row.value_after_depreciation -= total_value_of_stock_consumed
+
+ if self.capitalize_repair_cost:
+ row.value_after_depreciation -= self.repair_cost
+
+ def get_total_value_of_stock_consumed(self):
+ total_value_of_stock_consumed = 0
+ if self.get('stock_consumption'):
+ for item in self.get('stock_items'):
+ total_value_of_stock_consumed += item.total_value
+
+ return total_value_of_stock_consumed
+
+ def decrease_stock_quantity(self):
+ stock_entry = frappe.get_doc({
+ "doctype": "Stock Entry",
+ "stock_entry_type": "Material Issue",
+ "company": self.company
+ })
+
+ for stock_item in self.get('stock_items'):
+ stock_entry.append('items', {
+ "s_warehouse": self.warehouse,
+ "item_code": stock_item.item,
+ "qty": stock_item.consumed_quantity,
+ "basic_rate": stock_item.valuation_rate
+ })
+
+ stock_entry.insert()
+ stock_entry.submit()
+
+ self.db_set('stock_entry', stock_entry.name)
+
+ def increase_stock_quantity(self):
+ stock_entry = frappe.get_doc('Stock Entry', self.stock_entry)
+ stock_entry.flags.ignore_links = True
+ stock_entry.cancel()
+
+ def make_gl_entries(self, cancel=False):
+ if flt(self.repair_cost) > 0:
+ gl_entries = self.get_gl_entries()
+ make_gl_entries(gl_entries, cancel)
+
+ def get_gl_entries(self):
+ gl_entries = []
+ repair_and_maintenance_account = frappe.db.get_value('Company', self.company, 'repair_and_maintenance_account')
+ fixed_asset_account = get_asset_account("fixed_asset_account", asset=self.asset, company=self.company)
+ expense_account = frappe.get_doc('Purchase Invoice', self.purchase_invoice).items[0].expense_account
+
+ gl_entries.append(
+ self.get_gl_dict({
+ "account": expense_account,
+ "credit": self.repair_cost,
+ "credit_in_account_currency": self.repair_cost,
+ "against": repair_and_maintenance_account,
+ "voucher_type": self.doctype,
+ "voucher_no": self.name,
+ "cost_center": self.cost_center,
+ "posting_date": getdate(),
+ "company": self.company
+ }, item=self)
+ )
+
+ if self.get('stock_consumption'):
+ # creating GL Entries for each row in Stock Items based on the Stock Entry created for it
+ stock_entry = frappe.get_doc('Stock Entry', self.stock_entry)
+ for item in stock_entry.items:
+ gl_entries.append(
+ self.get_gl_dict({
+ "account": item.expense_account,
+ "credit": item.amount,
+ "credit_in_account_currency": item.amount,
+ "against": repair_and_maintenance_account,
+ "voucher_type": self.doctype,
+ "voucher_no": self.name,
+ "cost_center": self.cost_center,
+ "posting_date": getdate(),
+ "company": self.company
+ }, item=self)
+ )
+
+ gl_entries.append(
+ self.get_gl_dict({
+ "account": fixed_asset_account,
+ "debit": self.total_repair_cost,
+ "debit_in_account_currency": self.total_repair_cost,
+ "against": expense_account,
+ "voucher_type": self.doctype,
+ "voucher_no": self.name,
+ "cost_center": self.cost_center,
+ "posting_date": getdate(),
+ "against_voucher_type": "Purchase Invoice",
+ "against_voucher": self.purchase_invoice,
+ "company": self.company
+ }, item=self)
+ )
+
+ return gl_entries
+
+ def modify_depreciation_schedule(self):
+ for row in self.asset_doc.finance_books:
+ row.total_number_of_depreciations += self.increase_in_asset_life/row.frequency_of_depreciation
+
+ self.asset_doc.flags.increase_in_asset_life = False
+ extra_months = self.increase_in_asset_life % row.frequency_of_depreciation
+ if extra_months != 0:
+ self.calculate_last_schedule_date(self.asset_doc, row, extra_months)
+
+ # to help modify depreciation schedule when increase_in_asset_life is not a multiple of frequency_of_depreciation
+ def calculate_last_schedule_date(self, asset, row, extra_months):
+ asset.flags.increase_in_asset_life = True
+ number_of_pending_depreciations = cint(row.total_number_of_depreciations) - \
+ cint(asset.number_of_depreciations_booked)
+
+ # the Schedule Date in the final row of the old Depreciation Schedule
+ last_schedule_date = asset.schedules[len(asset.schedules)-1].schedule_date
+
+ # the Schedule Date in the final row of the new Depreciation Schedule
+ asset.to_date = add_months(last_schedule_date, extra_months)
+
+ # the latest possible date at which the depreciation can occur, without increasing the Total Number of Depreciations
+ # if depreciations happen yearly and the Depreciation Posting Date is 01-01-2020, this could be 01-01-2021, 01-01-2022...
+ schedule_date = add_months(row.depreciation_start_date,
+ number_of_pending_depreciations * cint(row.frequency_of_depreciation))
+
+ if asset.to_date > schedule_date:
+ row.total_number_of_depreciations += 1
+
+ def revert_depreciation_schedule_on_cancellation(self):
+ for row in self.asset_doc.finance_books:
+ row.total_number_of_depreciations -= self.increase_in_asset_life/row.frequency_of_depreciation
+
+ self.asset_doc.flags.increase_in_asset_life = False
+ extra_months = self.increase_in_asset_life % row.frequency_of_depreciation
+ if extra_months != 0:
+ self.calculate_last_schedule_date_before_modification(self.asset_doc, row, extra_months)
+
+ def calculate_last_schedule_date_before_modification(self, asset, row, extra_months):
+ asset.flags.increase_in_asset_life = True
+ number_of_pending_depreciations = cint(row.total_number_of_depreciations) - \
+ cint(asset.number_of_depreciations_booked)
+
+ # the Schedule Date in the final row of the modified Depreciation Schedule
+ last_schedule_date = asset.schedules[len(asset.schedules)-1].schedule_date
+
+ # the Schedule Date in the final row of the original Depreciation Schedule
+ asset.to_date = add_months(last_schedule_date, -extra_months)
+
+ # the latest possible date at which the depreciation can occur, without decreasing the Total Number of Depreciations
+ # if depreciations happen yearly and the Depreciation Posting Date is 01-01-2020, this could be 01-01-2021, 01-01-2022...
+ schedule_date = add_months(row.depreciation_start_date,
+ (number_of_pending_depreciations - 1) * cint(row.frequency_of_depreciation))
+
+ if asset.to_date < schedule_date:
+ row.total_number_of_depreciations -= 1
@frappe.whitelist()
def get_downtime(failure_date, completion_date):
downtime = time_diff_in_hours(completion_date, failure_date)
- return round(downtime, 2)
\ No newline at end of file
+ return round(downtime, 2)
diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py
index 3d325a9..30bbb37 100644
--- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py
+++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py
@@ -2,8 +2,167 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
-
+import frappe
+from frappe.utils import nowdate, flt
import unittest
+from erpnext.assets.doctype.asset.test_asset import create_asset_data, create_asset, set_depreciation_settings_in_company
class TestAssetRepair(unittest.TestCase):
- pass
+ def setUp(self):
+ set_depreciation_settings_in_company()
+ create_asset_data()
+ frappe.db.sql("delete from `tabTax Rule`")
+
+ def test_update_status(self):
+ asset = create_asset()
+ initial_status = asset.status
+ asset_repair = create_asset_repair(asset = asset)
+
+ if asset_repair.repair_status == "Pending":
+ asset.reload()
+ self.assertEqual(asset.status, "Out of Order")
+
+ asset_repair.repair_status = "Completed"
+ asset_repair.save()
+ asset_status = frappe.db.get_value("Asset", asset_repair.asset, "status")
+ self.assertEqual(asset_status, initial_status)
+
+ def test_stock_item_total_value(self):
+ asset_repair = create_asset_repair(stock_consumption = 1)
+
+ for item in asset_repair.stock_items:
+ total_value = flt(item.valuation_rate) * flt(item.consumed_quantity)
+ self.assertEqual(item.total_value, total_value)
+
+ def test_total_repair_cost(self):
+ asset_repair = create_asset_repair(stock_consumption = 1)
+
+ total_repair_cost = asset_repair.repair_cost
+ self.assertEqual(total_repair_cost, asset_repair.repair_cost)
+ for item in asset_repair.stock_items:
+ total_repair_cost += item.total_value
+
+ self.assertEqual(total_repair_cost, asset_repair.total_repair_cost)
+
+ def test_repair_status_after_submit(self):
+ asset_repair = create_asset_repair(submit = 1)
+ self.assertNotEqual(asset_repair.repair_status, "Pending")
+
+ def test_stock_items(self):
+ asset_repair = create_asset_repair(stock_consumption = 1)
+ self.assertTrue(asset_repair.stock_consumption)
+ self.assertTrue(asset_repair.stock_items)
+
+ def test_warehouse(self):
+ asset_repair = create_asset_repair(stock_consumption = 1)
+ self.assertTrue(asset_repair.stock_consumption)
+ self.assertTrue(asset_repair.warehouse)
+
+ def test_decrease_stock_quantity(self):
+ asset_repair = create_asset_repair(stock_consumption = 1, submit = 1)
+ stock_entry = frappe.get_last_doc('Stock Entry')
+
+ self.assertEqual(stock_entry.stock_entry_type, "Material Issue")
+ self.assertEqual(stock_entry.items[0].s_warehouse, asset_repair.warehouse)
+ self.assertEqual(stock_entry.items[0].item_code, asset_repair.stock_items[0].item)
+ self.assertEqual(stock_entry.items[0].qty, asset_repair.stock_items[0].consumed_quantity)
+
+ def test_increase_in_asset_value_due_to_stock_consumption(self):
+ asset = create_asset(calculate_depreciation = 1)
+ initial_asset_value = get_asset_value(asset)
+ asset_repair = create_asset_repair(asset= asset, stock_consumption = 1, submit = 1)
+ asset.reload()
+
+ increase_in_asset_value = get_asset_value(asset) - initial_asset_value
+ self.assertEqual(asset_repair.stock_items[0].total_value, increase_in_asset_value)
+
+ def test_increase_in_asset_value_due_to_repair_cost_capitalisation(self):
+ asset = create_asset(calculate_depreciation = 1)
+ initial_asset_value = get_asset_value(asset)
+ asset_repair = create_asset_repair(asset= asset, capitalize_repair_cost = 1, submit = 1)
+ asset.reload()
+
+ increase_in_asset_value = get_asset_value(asset) - initial_asset_value
+ self.assertEqual(asset_repair.repair_cost, increase_in_asset_value)
+
+ def test_purchase_invoice(self):
+ asset_repair = create_asset_repair(capitalize_repair_cost = 1, submit = 1)
+ self.assertTrue(asset_repair.purchase_invoice)
+
+ def test_gl_entries(self):
+ asset_repair = create_asset_repair(capitalize_repair_cost = 1, submit = 1)
+ gl_entry = frappe.get_last_doc('GL Entry')
+ self.assertEqual(asset_repair.name, gl_entry.voucher_no)
+
+ def test_increase_in_asset_life(self):
+ asset = create_asset(calculate_depreciation = 1)
+ initial_num_of_depreciations = num_of_depreciations(asset)
+ create_asset_repair(asset= asset, capitalize_repair_cost = 1, submit = 1)
+ asset.reload()
+
+ self.assertEqual((initial_num_of_depreciations + 1), num_of_depreciations(asset))
+ self.assertEqual(asset.schedules[-1].accumulated_depreciation_amount, asset.finance_books[0].value_after_depreciation)
+
+def get_asset_value(asset):
+ return asset.finance_books[0].value_after_depreciation
+
+def num_of_depreciations(asset):
+ return asset.finance_books[0].total_number_of_depreciations
+
+def create_asset_repair(**args):
+ from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
+ from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
+
+ args = frappe._dict(args)
+
+ if args.asset:
+ asset = args.asset
+ else:
+ asset = create_asset(is_existing_asset = 1)
+ asset_repair = frappe.new_doc("Asset Repair")
+ asset_repair.update({
+ "asset": asset.name,
+ "asset_name": asset.asset_name,
+ "failure_date": nowdate(),
+ "description": "Test Description",
+ "repair_cost": 0,
+ "company": asset.company
+ })
+
+ if args.stock_consumption:
+ asset_repair.stock_consumption = 1
+ asset_repair.warehouse = create_warehouse("Test Warehouse", company = asset.company)
+ asset_repair.append("stock_items", {
+ "item": args.item or args.item_code or "_Test Item",
+ "valuation_rate": args.rate if args.get("rate") is not None else 100,
+ "consumed_quantity": args.qty or 1
+ })
+
+ asset_repair.insert(ignore_if_duplicate=True)
+
+ if args.submit:
+ asset_repair.repair_status = "Completed"
+ asset_repair.cost_center = "_Test Cost Center - _TC"
+
+ if args.stock_consumption:
+ stock_entry = frappe.get_doc({
+ "doctype": "Stock Entry",
+ "stock_entry_type": "Material Receipt",
+ "company": asset.company
+ })
+ stock_entry.append('items', {
+ "t_warehouse": asset_repair.warehouse,
+ "item_code": asset_repair.stock_items[0].item,
+ "qty": asset_repair.stock_items[0].consumed_quantity
+ })
+ stock_entry.submit()
+
+ if args.capitalize_repair_cost:
+ asset_repair.capitalize_repair_cost = 1
+ asset_repair.repair_cost = 1000
+ if asset.calculate_depreciation:
+ asset_repair.increase_in_asset_life = 12
+ asset_repair.purchase_invoice = make_purchase_invoice().name
+
+ asset_repair.submit()
+ return asset_repair
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset_repair_consumed_item/__init__.py b/erpnext/assets/doctype/asset_repair_consumed_item/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/assets/doctype/asset_repair_consumed_item/__init__.py
diff --git a/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json b/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json
new file mode 100644
index 0000000..528f0ec
--- /dev/null
+++ b/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json
@@ -0,0 +1,55 @@
+{
+ "actions": [],
+ "creation": "2021-05-12 02:41:54.161024",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "item",
+ "valuation_rate",
+ "consumed_quantity",
+ "total_value"
+ ],
+ "fields": [
+ {
+ "fieldname": "item",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Item",
+ "options": "Item"
+ },
+ {
+ "fetch_from": "item.valuation_rate",
+ "fieldname": "valuation_rate",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Valuation Rate",
+ "read_only": 1
+ },
+ {
+ "fieldname": "consumed_quantity",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Consumed Quantity"
+ },
+ {
+ "fieldname": "total_value",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Total Value",
+ "read_only": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-05-12 03:19:55.006300",
+ "modified_by": "Administrator",
+ "module": "Assets",
+ "name": "Asset Repair Consumed Item",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.py b/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.py
new file mode 100644
index 0000000..fa22a57
--- /dev/null
+++ b/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+class AssetRepairConsumedItem(Document):
+ pass
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index eaa502f..a0b1e07 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -447,10 +447,11 @@
target.flags.ignore_permissions = ignore_permissions
set_missing_values(source, target)
#Get the advance paid Journal Entries in Purchase Invoice Advance
-
if target.get("allocate_advances_automatically"):
target.set_advances()
+ target.set_payment_schedule()
+
def update_item(obj, target, source_parent):
target.amount = flt(obj.amount) - flt(obj.billed_amt)
target.base_amount = target.amount * flt(source_parent.conversion_rate)
@@ -470,6 +471,7 @@
"party_account_currency": "party_account_currency",
"supplier_warehouse":"supplier_warehouse"
},
+ "field_no_map" : ["payment_terms_template"],
"validation": {
"docstatus": ["=", 1],
}
@@ -489,12 +491,6 @@
},
}
- if frappe.get_single("Accounts Settings").automatically_fetch_payment_terms == 1:
- fields["Payment Schedule"] = {
- "doctype": "Payment Schedule",
- "add_if_empty": True
- }
-
doc = get_mapped_doc("Purchase Order", source_name, fields,
target_doc, postprocess, ignore_permissions=ignore_permissions)
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 8563b97..d668c76 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -484,6 +484,9 @@
def test_make_purchase_invoice_with_terms(self):
+ from erpnext.selling.doctype.sales_order.test_sales_order import automatically_fetch_payment_terms, compare_payment_schedules
+
+ automatically_fetch_payment_terms()
po = create_purchase_order(do_not_save=True)
self.assertRaises(frappe.ValidationError, make_pi_from_po, po.name)
@@ -509,6 +512,7 @@
self.assertEqual(getdate(pi.payment_schedule[0].due_date), getdate(po.transaction_date))
self.assertEqual(pi.payment_schedule[1].payment_amount, 2500.0)
self.assertEqual(getdate(pi.payment_schedule[1].due_date), add_days(getdate(po.transaction_date), 30))
+ automatically_fetch_payment_terms(enable=0)
def test_subcontracting(self):
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes")
@@ -632,14 +636,18 @@
else:
raise Exception
- def test_terms_does_not_copy(self):
- po = create_purchase_order()
+ def test_terms_are_not_copied_if_automatically_fetch_payment_terms_is_unchecked(self):
+ po = create_purchase_order(do_not_save=1)
+ po.payment_terms_template = '_Test Payment Term Template'
+ po.save()
+ po.submit()
- self.assertTrue(po.get('payment_schedule'))
-
+ frappe.db.set_value('Company', '_Test Company', 'payment_terms', '_Test Payment Term Template 1')
pi = make_pi_from_po(po.name)
+ pi.save()
- self.assertFalse(pi.get('payment_schedule'))
+ self.assertEqual(pi.get('payment_terms_template'), '_Test Payment Term Template 1')
+ frappe.db.set_value('Company', '_Test Company', 'payment_terms', '')
def test_terms_copied(self):
po = create_purchase_order(do_not_save=1)
@@ -968,8 +976,27 @@
# To test if the PO does NOT have a Blanket Order
self.assertEqual(po_doc.items[0].blanket_order, None)
+ def test_payment_terms_are_fetched_when_creating_purchase_invoice(self):
+ from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template
+ from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
+ from erpnext.selling.doctype.sales_order.test_sales_order import automatically_fetch_payment_terms, compare_payment_schedules
+ automatically_fetch_payment_terms()
+ po = create_purchase_order(qty=10, rate=100, do_not_save=1)
+ create_payment_terms_template()
+ po.payment_terms_template = 'Test Receivable Template'
+ po.submit()
+
+ pi = make_purchase_invoice(qty=10, rate=100, do_not_save=1)
+ pi.items[0].purchase_order = po.name
+ pi.items[0].po_detail = po.items[0].name
+ pi.insert()
+
+ # self.assertEqual(po.payment_terms_template, pi.payment_terms_template)
+ compare_payment_schedules(self, po, pi)
+
+ automatically_fetch_payment_terms(enable=0)
def make_pr_against_po(po, received_qty=0):
pr = make_purchase_receipt(po)
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index a9860ed..d65efa2 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -674,19 +674,24 @@
if self.get('doctype') in ['Purchase Invoice', 'Sales Invoice']:
for d in self.get("advances"):
if d.exchange_gain_loss:
- party = self.supplier if self.get('doctype') == 'Purchase Invoice' else self.customer
- party_account = self.credit_to if self.get('doctype') == 'Purchase Invoice' else self.debit_to
- party_type = "Supplier" if self.get('doctype') == 'Purchase Invoice' else "Customer"
+ is_purchase_invoice = self.get('doctype') == 'Purchase Invoice'
+ party = self.supplier if is_purchase_invoice else self.customer
+ party_account = self.credit_to if is_purchase_invoice else self.debit_to
+ party_type = "Supplier" if is_purchase_invoice else "Customer"
gain_loss_account = frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account')
+ if not gain_loss_account:
+ frappe.throw(_("Please set Default Exchange Gain/Loss Account in Company {}")
+ .format(self.get('company')))
account_currency = get_account_currency(gain_loss_account)
if account_currency != self.company_currency:
- frappe.throw(_("Currency for {0} must be {1}").format(d.account, self.company_currency))
+ frappe.throw(_("Currency for {0} must be {1}").format(gain_loss_account, self.company_currency))
# for purchase
dr_or_cr = 'debit' if d.exchange_gain_loss > 0 else 'credit'
- # just reverse for sales?
- dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit'
+ if not is_purchase_invoice:
+ # just reverse for sales?
+ dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit'
gl_entries.append(
self.get_gl_dict({
@@ -808,6 +813,89 @@
tax_map[tax.account_head] -= allocated_amount
allocated_tax_map[tax.account_head] -= allocated_amount
+ def get_amount_and_base_amount(self, item, enable_discount_accounting):
+ amount = item.net_amount
+ base_amount = item.base_net_amount
+
+ if enable_discount_accounting and self.get('discount_amount') and self.get('additional_discount_account'):
+ amount = item.amount
+ base_amount = item.base_amount
+
+ return amount, base_amount
+
+ def get_tax_amounts(self, tax, enable_discount_accounting):
+ amount = tax.tax_amount_after_discount_amount
+ base_amount = tax.base_tax_amount_after_discount_amount
+
+ if enable_discount_accounting and self.get('discount_amount') and self.get('additional_discount_account') \
+ and self.get('apply_discount_on') == 'Grand Total':
+ amount = tax.tax_amount
+ base_amount = tax.base_tax_amount
+
+ return amount, base_amount
+
+ def make_discount_gl_entries(self, gl_entries):
+ enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
+
+ if enable_discount_accounting:
+ if self.doctype == "Purchase Invoice":
+ dr_or_cr = "credit"
+ rev_dr_cr = "debit"
+ supplier_or_customer = self.supplier
+
+ else:
+ dr_or_cr = "debit"
+ rev_dr_cr = "credit"
+ supplier_or_customer = self.customer
+
+ for item in self.get("items"):
+ if item.get('discount_amount') and item.get('discount_account'):
+ discount_amount = item.discount_amount * item.qty
+ if self.doctype == "Purchase Invoice":
+ income_or_expense_account = (item.expense_account
+ if (not item.enable_deferred_expense or self.is_return)
+ else item.deferred_expense_account)
+ else:
+ income_or_expense_account = (item.income_account
+ if (not item.enable_deferred_revenue or self.is_return)
+ else item.deferred_revenue_account)
+
+ account_currency = get_account_currency(item.discount_account)
+ gl_entries.append(
+ self.get_gl_dict({
+ "account": item.discount_account,
+ "against": supplier_or_customer,
+ dr_or_cr: flt(discount_amount, item.precision('discount_amount')),
+ dr_or_cr + "_in_account_currency": flt(discount_amount * self.get('conversion_rate'),
+ item.precision('discount_amount')),
+ "cost_center": item.cost_center,
+ "project": item.project
+ }, account_currency, item=item)
+ )
+
+ account_currency = get_account_currency(income_or_expense_account)
+ gl_entries.append(
+ self.get_gl_dict({
+ "account": income_or_expense_account,
+ "against": supplier_or_customer,
+ rev_dr_cr: flt(discount_amount, item.precision('discount_amount')),
+ rev_dr_cr + "_in_account_currency": flt(discount_amount * self.get('conversion_rate'),
+ item.precision('discount_amount')),
+ "cost_center": item.cost_center,
+ "project": item.project or self.project
+ }, account_currency, item=item)
+ )
+
+ if self.get('discount_amount') and self.get('additional_discount_account'):
+ gl_entries.append(
+ self.get_gl_dict({
+ "account": self.additional_discount_account,
+ "against": supplier_or_customer,
+ dr_or_cr: self.discount_amount,
+ "cost_center": self.cost_center
+ }, item=self)
+ )
+
def allocate_advance_taxes(self, gl_entries):
tax_map = self.get_tax_map()
for pe in self.get("advances"):
@@ -818,11 +906,11 @@
account_currency = get_account_currency(tax.account_head)
if self.doctype == "Purchase Invoice":
- dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit"
- rev_dr_cr = "debit" if tax.add_deduct_tax == "Add" else "credit"
- else:
dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit"
rev_dr_cr = "credit" if tax.add_deduct_tax == "Add" else "debit"
+ else:
+ dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit"
+ rev_dr_cr = "debit" if tax.add_deduct_tax == "Add" else "credit"
party = self.supplier if self.doctype == "Purchase Invoice" else self.customer
unallocated_amount = tax.tax_amount - tax.allocated_amount
@@ -1091,6 +1179,8 @@
if self.doctype in ("Sales Invoice", "Purchase Invoice"):
base_grand_total = base_grand_total - flt(self.base_write_off_amount)
grand_total = grand_total - flt(self.write_off_amount)
+ po_or_so, doctype, fieldname = self.get_order_details()
+ automatically_fetch_payment_terms = cint(frappe.db.get_single_value('Accounts Settings', 'automatically_fetch_payment_terms'))
if self.get("total_advance"):
if party_account_currency == self.company_currency:
@@ -1101,19 +1191,86 @@
base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total"))
if not self.get("payment_schedule"):
- if self.get("payment_terms_template"):
+ if self.doctype in ["Sales Invoice", "Purchase Invoice"] and automatically_fetch_payment_terms \
+ and self.linked_order_has_payment_terms(po_or_so, fieldname, doctype):
+ self.fetch_payment_terms_from_order(po_or_so, doctype)
+ if self.get('payment_terms_template'):
+ self.ignore_default_payment_terms_template = 1
+ elif self.get("payment_terms_template"):
data = get_payment_terms(self.payment_terms_template, posting_date, grand_total, base_grand_total)
for item in data:
self.append("payment_schedule", item)
- else:
+ elif self.doctype not in ["Purchase Receipt"]:
data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total, base_payment_amount=base_grand_total)
self.append("payment_schedule", data)
+
+ for d in self.get("payment_schedule"):
+ if d.invoice_portion:
+ d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
+ d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('base_payment_amount'))
+ d.outstanding = d.payment_amount
+ elif not d.invoice_portion:
+ d.base_payment_amount = flt(base_grand_total * self.get("conversion_rate"), d.precision('base_payment_amount'))
+
+
+ def get_order_details(self):
+ if self.doctype == "Sales Invoice":
+ po_or_so = self.get('items')[0].get('sales_order')
+ po_or_so_doctype = "Sales Order"
+ po_or_so_doctype_name = "sales_order"
+
else:
- for d in self.get("payment_schedule"):
- if d.invoice_portion:
- d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
- d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
- d.outstanding = d.payment_amount
+ po_or_so = self.get('items')[0].get('purchase_order')
+ po_or_so_doctype = "Purchase Order"
+ po_or_so_doctype_name = "purchase_order"
+
+ return po_or_so, po_or_so_doctype, po_or_so_doctype_name
+
+ def linked_order_has_payment_terms(self, po_or_so, fieldname, doctype):
+ if po_or_so and self.all_items_have_same_po_or_so(po_or_so, fieldname):
+ if self.linked_order_has_payment_terms_template(po_or_so, doctype):
+ return True
+ elif self.linked_order_has_payment_schedule(po_or_so):
+ return True
+
+ return False
+
+ def all_items_have_same_po_or_so(self, po_or_so, fieldname):
+ for item in self.get('items'):
+ if item.get(fieldname) != po_or_so:
+ return False
+
+ return True
+
+ def linked_order_has_payment_terms_template(self, po_or_so, doctype):
+ return frappe.get_value(doctype, po_or_so, 'payment_terms_template')
+
+ def linked_order_has_payment_schedule(self, po_or_so):
+ return frappe.get_all('Payment Schedule', filters={'parent': po_or_so})
+
+ def fetch_payment_terms_from_order(self, po_or_so, po_or_so_doctype):
+ """
+ Fetch Payment Terms from Purchase/Sales Order on creating a new Purchase/Sales Invoice.
+ """
+ po_or_so = frappe.get_cached_doc(po_or_so_doctype, po_or_so)
+
+ self.payment_schedule = []
+ self.payment_terms_template = po_or_so.payment_terms_template
+
+ for schedule in po_or_so.payment_schedule:
+ payment_schedule = {
+ 'payment_term': schedule.payment_term,
+ 'due_date': schedule.due_date,
+ 'invoice_portion': schedule.invoice_portion,
+ 'mode_of_payment': schedule.mode_of_payment,
+ 'description': schedule.description
+ }
+
+ if schedule.discount_type == 'Percentage':
+ payment_schedule['discount_type'] = schedule.discount_type
+ payment_schedule['discount'] = schedule.discount
+
+ self.append("payment_schedule", payment_schedule)
def set_due_date(self):
due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date]
@@ -1499,7 +1656,7 @@
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):
+def add_taxes_from_tax_template(child_item, parent_doc, db_insert=True):
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:
@@ -1522,7 +1679,8 @@
"category" : "Total",
"add_deduct_tax" : "Add"
})
- tax_row.db_insert()
+ if db_insert:
+ tax_row.db_insert()
def set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, trans_item):
"""
@@ -1799,4 +1957,4 @@
@erpnext.allow_regional
def validate_einvoice_fields(doc):
- pass
+ pass
\ No newline at end of file
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 6a550e0..974ade3 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -72,7 +72,8 @@
# set contact and address details for supplier, if they are not mentioned
if getattr(self, "supplier", None):
self.update_if_missing(get_party_details(self.supplier, party_type="Supplier", ignore_permissions=self.flags.ignore_permissions,
- doctype=self.doctype, company=self.company, party_address=self.supplier_address, shipping_address=self.get('shipping_address')))
+ doctype=self.doctype, company=self.company, party_address=self.supplier_address, shipping_address=self.get('shipping_address'),
+ fetch_payment_terms_template= not self.get('ignore_default_payment_terms_template')))
self.set_missing_item_details(for_validate)
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 2803193..21c052a 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -407,6 +407,7 @@
INNER JOIN `tabBatch` batch on sle.batch_no = batch.name
where
batch.disabled = 0
+ and sle.is_cancelled = 0
and sle.item_code = %(item_code)s
and sle.warehouse = %(warehouse)s
and (sle.batch_no like %(txt)s
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 2526e6d..17707ec 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -27,6 +27,7 @@
if not self.get('is_return'):
self.validate_inspection()
self.validate_serialized_batch()
+ self.clean_serial_nos()
self.validate_customer_provided_item()
self.set_rate_of_stock_uom()
self.validate_internal_transfer()
@@ -53,12 +54,17 @@
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
for d in self.get("items"):
if hasattr(d, 'serial_no') and hasattr(d, 'batch_no') and d.serial_no and d.batch_no:
- serial_nos = get_serial_nos(d.serial_no)
- for serial_no_data in frappe.get_all("Serial No",
- filters={"name": ("in", serial_nos)}, fields=["batch_no", "name"]):
- if serial_no_data.batch_no != d.batch_no:
+ serial_nos = frappe.get_all("Serial No",
+ fields=["batch_no", "name", "warehouse"],
+ filters={
+ "name": ("in", get_serial_nos(d.serial_no))
+ }
+ )
+
+ for row in serial_nos:
+ if row.warehouse and row.batch_no != d.batch_no:
frappe.throw(_("Row #{0}: Serial No {1} does not belong to Batch {2}")
- .format(d.idx, serial_no_data.name, d.batch_no))
+ .format(d.idx, row.name, d.batch_no))
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")
@@ -67,6 +73,12 @@
frappe.throw(_("Row #{0}: The batch {1} has already expired.")
.format(d.idx, get_link_to_form("Batch", d.get("batch_no"))))
+ def clean_serial_nos(self):
+ for row in self.get("items"):
+ if hasattr(row, "serial_no") and row.serial_no:
+ # replace commas by linefeed and remove all spaces in string
+ row.serial_no = row.serial_no.replace(",", "\n").replace(" ", "")
+
def get_gl_entries(self, warehouse_account=None, default_expense_account=None,
default_cost_center=None):
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 56da5b7..099c7d4 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -152,7 +152,7 @@
validate_taxes_and_charges(tax)
validate_inclusive_tax(tax, self.doc)
- if not self.doc.get('is_consolidated'):
+ if not (self.doc.get('is_consolidated') or tax.get("dont_recompute_tax")):
tax.item_wise_tax_detail = {}
tax_fields = ["total", "tax_amount_after_discount_amount",
@@ -347,7 +347,7 @@
elif tax.charge_type == "On Item Quantity":
current_tax_amount = tax_rate * item.qty
- if not self.doc.get("is_consolidated"):
+ if not (self.doc.get("is_consolidated") or tax.get("dont_recompute_tax")):
self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
return current_tax_amount
@@ -455,7 +455,8 @@
def _cleanup(self):
if not self.doc.get('is_consolidated'):
for tax in self.doc.get("taxes"):
- tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
+ if not tax.get("dont_recompute_tax"):
+ tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
def set_discount_amount(self):
if self.doc.additional_discount_percentage:
diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js
index ac374a9..089a63f 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.js
+++ b/erpnext/crm/doctype/opportunity/opportunity.js
@@ -53,6 +53,13 @@
frm.get_field("items").grid.set_multiple_add("item_code", "qty");
},
+ status:function(frm){
+ if (frm.doc.status == "Lost"){
+ frm.trigger('set_as_lost_dialog');
+ }
+
+ },
+
customer_address: function(frm, cdt, cdn) {
erpnext.utils.get_address_display(frm, 'customer_address', 'address_display', false);
},
@@ -91,11 +98,6 @@
frm.add_custom_button(__('Quotation'),
cur_frm.cscript.create_quotation, __('Create'));
- if(doc.status!=="Quotation") {
- frm.add_custom_button(__('Lost'), () => {
- frm.trigger('set_as_lost_dialog');
- });
- }
}
if(!frm.doc.__islocal && frm.perm[0].write && frm.doc.docstatus==0) {
diff --git a/erpnext/education/api.py b/erpnext/education/api.py
index afa0be9..4493a3f 100644
--- a/erpnext/education/api.py
+++ b/erpnext/education/api.py
@@ -34,11 +34,14 @@
}
}}, ignore_permissions=True)
student.save()
+
+ student_applicant = frappe.db.get_value("Student Applicant", source_name,
+ ["student_category", "program"], as_dict=True)
program_enrollment = frappe.new_doc("Program Enrollment")
program_enrollment.student = student.name
- program_enrollment.student_category = student.student_category
+ program_enrollment.student_category = student_applicant.student_category
program_enrollment.student_name = student.title
- program_enrollment.program = frappe.db.get_value("Student Applicant", source_name, "program")
+ program_enrollment.program = student_applicant.program
frappe.publish_realtime('enroll_student_progress', {"progress": [2, 4]}, user=frappe.session.user)
return program_enrollment
diff --git a/erpnext/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.json b/erpnext/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.json
index 9be292b..1d74973 100644
--- a/erpnext/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.json
+++ b/erpnext/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.json
@@ -1,195 +1,68 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2016-06-10 03:29:02.539914",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
+ "actions": [],
+ "creation": "2016-06-10 03:29:02.539914",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "student_applicant",
+ "student",
+ "student_name",
+ "column_break_3",
+ "student_batch_name",
+ "student_category"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "fieldname": "student_applicant",
- "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": "Student Applicant",
- "length": 0,
- "no_copy": 0,
- "options": "Student Applicant",
- "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,
- "unique": 0
- },
+ "fieldname": "student_applicant",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Student Applicant",
+ "options": "Student Applicant"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "fieldname": "student",
- "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": "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": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "student",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Student",
+ "options": "Student"
+ },
{
- "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,
- "unique": 0
- },
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "student_name",
- "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": "Student Name",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "student_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Student Name",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "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": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Student Batch Name",
- "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,
- "unique": 0
+ "fieldname": "student_batch_name",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Student Batch Name",
+ "options": "Student Batch Name"
+ },
+ {
+ "fieldname": "student_category",
+ "fieldtype": "Link",
+ "label": "Student Category",
+ "options": "Student Category",
+ "read_only": 1
}
- ],
- "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-01-02 12:03:53.890741",
- "modified_by": "Administrator",
- "module": "Education",
- "name": "Program Enrollment Tool Student",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Education",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2021-07-29 18:19:54.471594",
+ "modified_by": "Administrator",
+ "module": "Education",
+ "name": "Program Enrollment Tool Student",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "restrict_to_domain": "Education",
+ "sort_field": "modified",
+ "sort_order": "DESC"
}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.js b/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.js
index d3fe7d2b..12faeec 100644
--- a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.js
+++ b/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.js
@@ -18,5 +18,8 @@
})
}).addClass('btn-primary');
}
+
+ let app_link = "<a href='https://frappecloud.com/marketplace/apps/ecommerce-integrations' target='_blank'>Ecommerce Integrations</a>"
+ frm.dashboard.add_comment(__("Shopify Integration will be removed from ERPNext in Version 14. Please install {0} app to continue using it.", [app_link]), "yellow", true);
}
});
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.js b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.js
index 1574795..a926a7e 100644
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.js
+++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.js
@@ -36,6 +36,10 @@
frm.toggle_reqd("delivery_note_series", frm.doc.sync_delivery_note);
}
+
+ let app_link = "<a href='https://frappecloud.com/marketplace/apps/ecommerce-integrations' target='_blank'>Ecommerce Integrations</a>"
+ frm.dashboard.add_comment(__("Shopify Integration will be removed from ERPNext in Version 14. Please install {0} app to continue using it.", [app_link]), "yellow", true);
+
})
$.extend(erpnext_integrations.shopify_settings, {
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 52daec9..e6b6cc4 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -24,7 +24,8 @@
"Address": "public/js/address.js",
"Communication": "public/js/communication.js",
"Event": "public/js/event.js",
- "Newsletter": "public/js/newsletter.js"
+ "Newsletter": "public/js/newsletter.js",
+ "Contact": "public/js/contact.js"
}
override_doctype_class = {
@@ -429,7 +430,8 @@
'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption',
'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period',
'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields',
- 'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount'
+ 'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount',
+ 'erpnext.stock.doctype.item.item.set_item_tax_from_hsn_code': 'erpnext.regional.india.utils.set_item_tax_from_hsn_code'
},
'United Arab Emirates': {
'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data',
diff --git a/erpnext/hr/doctype/appraisal/appraisal.py b/erpnext/hr/doctype/appraisal/appraisal.py
index f760187..c2ed457 100644
--- a/erpnext/hr/doctype/appraisal/appraisal.py
+++ b/erpnext/hr/doctype/appraisal/appraisal.py
@@ -9,7 +9,7 @@
from frappe import _
from frappe.model.mapper import get_mapped_doc
from frappe.model.document import Document
-from erpnext.hr.utils import set_employee_name
+from erpnext.hr.utils import set_employee_name, validate_active_employee
class Appraisal(Document):
def validate(self):
@@ -19,6 +19,7 @@
if not self.goals:
frappe.throw(_("Goals cannot be empty"))
+ validate_active_employee(self.employee)
set_employee_name(self)
self.validate_dates()
self.validate_existing_appraisal()
diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py
index 3412675..f79f0fe 100644
--- a/erpnext/hr/doctype/attendance/attendance.py
+++ b/erpnext/hr/doctype/attendance/attendance.py
@@ -8,11 +8,13 @@
from frappe import _
from frappe.model.document import Document
from frappe.utils import cstr, get_datetime, formatdate
+from erpnext.hr.utils import validate_active_employee
class Attendance(Document):
def validate(self):
from erpnext.controllers.status_updater import validate_status
validate_status(self.status, ["Present", "Absent", "On Leave", "Half Day", "Work From Home"])
+ validate_active_employee(self.employee)
self.validate_attendance_date()
self.validate_duplicate_record()
self.validate_employee_status()
diff --git a/erpnext/hr/doctype/attendance_request/attendance_request.py b/erpnext/hr/doctype/attendance_request/attendance_request.py
index 090d532..7f88fed 100644
--- a/erpnext/hr/doctype/attendance_request/attendance_request.py
+++ b/erpnext/hr/doctype/attendance_request/attendance_request.py
@@ -8,10 +8,11 @@
from frappe.model.document import Document
from frappe.utils import date_diff, add_days, getdate
from erpnext.hr.doctype.employee.employee import is_holiday
-from erpnext.hr.utils import validate_dates
+from erpnext.hr.utils import validate_dates, validate_active_employee
class AttendanceRequest(Document):
def validate(self):
+ validate_active_employee(self.employee)
validate_dates(self, self.from_date, self.to_date)
if self.half_day:
if not getdate(self.from_date)<=getdate(self.half_day_date)<=getdate(self.to_date):
diff --git a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
index a6fe429..0d7fded 100644
--- a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
+++ b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
@@ -7,12 +7,13 @@
from frappe import _
from frappe.utils import date_diff, add_days, getdate, cint, format_date
from frappe.model.document import Document
-from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, \
+from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, validate_active_employee, \
get_holidays_for_employee, create_additional_leave_ledger_entry
class CompensatoryLeaveRequest(Document):
def validate(self):
+ validate_active_employee(self.employee)
validate_dates(self, self.work_from_date, self.work_end_date)
if self.half_day:
if not self.half_day_date:
diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py
index fa017d9..5ca4756 100755
--- a/erpnext/hr/doctype/employee/employee.py
+++ b/erpnext/hr/doctype/employee/employee.py
@@ -13,8 +13,10 @@
from erpnext.utilities.transaction_base import delete_events
from frappe.utils.nestedset import NestedSet
-class EmployeeUserDisabledError(frappe.ValidationError): pass
-class EmployeeLeftValidationError(frappe.ValidationError): pass
+class EmployeeUserDisabledError(frappe.ValidationError):
+ pass
+class InactiveEmployeeStatusError(frappe.ValidationError):
+ pass
class Employee(NestedSet):
nsm_parent_field = 'reports_to'
@@ -196,7 +198,7 @@
message += "<br><br><ul><li>" + "</li><li>".join(link_to_employees)
message += "</li></ul><br>"
message += _("Please make sure the employees above report to another Active employee.")
- throw(message, EmployeeLeftValidationError, _("Cannot Relieve Employee"))
+ throw(message, InactiveEmployeeStatusError, _("Cannot Relieve Employee"))
if not self.relieving_date:
throw(_("Please enter relieving date."))
diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py
index 7d652a7..8fc7cf1 100644
--- a/erpnext/hr/doctype/employee/test_employee.py
+++ b/erpnext/hr/doctype/employee/test_employee.py
@@ -7,7 +7,7 @@
import erpnext
import unittest
import frappe.utils
-from erpnext.hr.doctype.employee.employee import EmployeeLeftValidationError
+from erpnext.hr.doctype.employee.employee import InactiveEmployeeStatusError
test_records = frappe.get_test_records('Employee')
@@ -45,10 +45,33 @@
employee2_doc.save()
employee1_doc.reload()
employee1_doc.status = 'Left'
- self.assertRaises(EmployeeLeftValidationError, employee1_doc.save)
+ self.assertRaises(InactiveEmployeeStatusError, employee1_doc.save)
+
+ def test_employee_status_inactive(self):
+ from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
+ from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip
+ from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list
+
+ employee = make_employee("test_employee_status@company.com")
+ employee_doc = frappe.get_doc("Employee", employee)
+ employee_doc.status = "Inactive"
+ employee_doc.save()
+ employee_doc.reload()
+
+ make_holiday_list()
+ frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Slip Test Holiday List")
+
+ frappe.db.sql("""delete from `tabSalary Structure` where name='Test Inactive Employee Salary Slip'""")
+ salary_structure = make_salary_structure("Test Inactive Employee Salary Slip", "Monthly",
+ employee=employee_doc.name, company=employee_doc.company)
+ salary_slip = make_salary_slip(salary_structure.name, employee=employee_doc.name)
+
+ self.assertRaises(InactiveEmployeeStatusError, salary_slip.save)
+
+ def tearDown(self):
+ frappe.db.rollback()
def make_employee(user, company=None, **kwargs):
- ""
if not frappe.db.get_value("User", user):
frappe.get_doc({
"doctype": "User",
@@ -80,4 +103,5 @@
employee.insert()
return employee.name
else:
+ frappe.db.set_value("Employee", {"employee_name":user}, "status", "Active")
return frappe.get_value("Employee", {"employee_name":user}, "name")
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py
index cb72f6b..cbb3cc8 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.py
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.py
@@ -8,6 +8,7 @@
from frappe.model.document import Document
from frappe.utils import flt, nowdate
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
+from erpnext.hr.utils import validate_active_employee
class EmployeeAdvanceOverPayment(frappe.ValidationError):
pass
@@ -18,11 +19,11 @@
'make_payment_via_journal_entry')
def validate(self):
+ validate_active_employee(self.employee)
self.set_status()
def on_cancel(self):
self.ignore_linked_doctypes = ('GL Entry')
- self.set_status()
def set_status(self):
if self.docstatus == 0:
@@ -183,9 +184,9 @@
bank_cash_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment)
if not bank_cash_account:
frappe.throw(_("Please set a Default Cash Account in Company defaults"))
-
+
advance_account_currency = frappe.db.get_value('Account', advance_account, 'account_currency')
-
+
je = frappe.new_doc('Journal Entry')
je.posting_date = nowdate()
je.voucher_type = get_voucher_type(mode_of_payment)
@@ -229,4 +230,4 @@
if mode_of_payment_type == "Bank":
voucher_type = "Bank Entry"
- return voucher_type
\ No newline at end of file
+ return voucher_type
diff --git a/erpnext/hr/doctype/employee_checkin/employee_checkin.py b/erpnext/hr/doctype/employee_checkin/employee_checkin.py
index 15fbd4e..60ea0f9 100644
--- a/erpnext/hr/doctype/employee_checkin/employee_checkin.py
+++ b/erpnext/hr/doctype/employee_checkin/employee_checkin.py
@@ -9,9 +9,11 @@
from frappe import _
from erpnext.hr.doctype.shift_assignment.shift_assignment import get_actual_start_end_datetime_of_shift
+from erpnext.hr.utils import validate_active_employee
class EmployeeCheckin(Document):
def validate(self):
+ validate_active_employee(self.employee)
self.validate_duplicate_log()
self.fetch_shift()
@@ -122,7 +124,7 @@
def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type):
"""Given a set of logs in chronological order calculates the total working hours based on the parameters.
Zero is returned for all invalid cases.
-
+
:param logs: The List of 'Employee Checkin'.
:param check_in_out_type: One of: 'Alternating entries as IN and OUT during the same shift', 'Strictly based on Log Type in Employee Checkin'
:param working_hours_calc_type: One of: 'First Check-in and Last Check-out', 'Every Valid Check-in and Check-out'
diff --git a/erpnext/hr/doctype/employee_promotion/employee_promotion.py b/erpnext/hr/doctype/employee_promotion/employee_promotion.py
index 83fb235..a3a6183 100644
--- a/erpnext/hr/doctype/employee_promotion/employee_promotion.py
+++ b/erpnext/hr/doctype/employee_promotion/employee_promotion.py
@@ -7,12 +7,11 @@
from frappe import _
from frappe.model.document import Document
from frappe.utils import getdate
-from erpnext.hr.utils import update_employee
+from erpnext.hr.utils import update_employee, validate_active_employee
class EmployeePromotion(Document):
def validate(self):
- if frappe.get_value("Employee", self.employee, "status") != "Active":
- frappe.throw(_("Cannot promote Employee with status Left or Inactive"))
+ validate_active_employee(self.employee)
def before_submit(self):
if getdate(self.promotion_date) > getdate():
diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.py b/erpnext/hr/doctype/employee_referral/employee_referral.py
index 45d6872..0493306 100644
--- a/erpnext/hr/doctype/employee_referral/employee_referral.py
+++ b/erpnext/hr/doctype/employee_referral/employee_referral.py
@@ -7,9 +7,11 @@
from frappe import _
from frappe.utils import get_link_to_form
from frappe.model.document import Document
+from erpnext.hr.utils import validate_active_employee
class EmployeeReferral(Document):
def validate(self):
+ validate_active_employee(self.referrer)
self.set_full_name()
self.set_referral_bonus_payment_status()
diff --git a/erpnext/hr/doctype/employee_transfer/employee_transfer.py b/erpnext/hr/doctype/employee_transfer/employee_transfer.py
index 6eec9fa..c200774 100644
--- a/erpnext/hr/doctype/employee_transfer/employee_transfer.py
+++ b/erpnext/hr/doctype/employee_transfer/employee_transfer.py
@@ -10,10 +10,6 @@
from erpnext.hr.utils import update_employee
class EmployeeTransfer(Document):
- def validate(self):
- if frappe.get_value("Employee", self.employee, "status") != "Active":
- frappe.throw(_("Cannot transfer Employee with status Left or Inactive"))
-
def before_submit(self):
if getdate(self.transfer_date) > getdate():
frappe.throw(_("Employee Transfer cannot be submitted before Transfer Date"),
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py
index 5010fc3..95e2806 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.py
@@ -6,7 +6,7 @@
from frappe import _
from frappe.utils import get_fullname, flt, cstr, get_link_to_form
from frappe.model.document import Document
-from erpnext.hr.utils import set_employee_name, share_doc_with_approver
+from erpnext.hr.utils import set_employee_name, share_doc_with_approver, validate_active_employee
from erpnext.accounts.party import get_party_account
from erpnext.accounts.general_ledger import make_gl_entries
from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account
@@ -23,6 +23,7 @@
'make_payment_via_journal_entry')
def validate(self):
+ validate_active_employee(self.employee)
self.validate_advances()
self.validate_sanctioned_amount()
self.calculate_total_amount()
@@ -35,8 +36,8 @@
if self.task and not self.project:
self.project = frappe.db.get_value("Task", self.task, "project")
- def set_status(self):
- self.status = {
+ def set_status(self, update=False):
+ status = {
"0": "Draft",
"1": "Submitted",
"2": "Cancelled"
@@ -44,14 +45,18 @@
paid_amount = flt(self.total_amount_reimbursed) + flt(self.total_advance_amount)
precision = self.precision("grand_total")
- if (self.is_paid or (flt(self.total_sanctioned_amount) > 0
- and flt(self.grand_total, precision) == flt(paid_amount, precision))) \
- and self.docstatus == 1 and self.approval_status == 'Approved':
- self.status = "Paid"
+ if (self.is_paid or (flt(self.total_sanctioned_amount) > 0 and self.docstatus == 1
+ and flt(self.grand_total, precision) == flt(paid_amount, precision))) and self.approval_status == 'Approved':
+ status = "Paid"
elif flt(self.total_sanctioned_amount) > 0 and self.docstatus == 1 and self.approval_status == 'Approved':
- self.status = "Unpaid"
+ status = "Unpaid"
elif self.docstatus == 1 and self.approval_status == 'Rejected':
- self.status = 'Rejected'
+ status = 'Rejected'
+
+ if update:
+ self.db_set("status", status)
+ else:
+ self.status = status
def on_update(self):
share_doc_with_approver(self, self.expense_approver)
@@ -74,7 +79,7 @@
if self.is_paid:
update_reimbursed_amount(self)
- self.set_status()
+ self.set_status(update=True)
self.update_claimed_amount_in_employee_advance()
def on_cancel(self):
@@ -86,7 +91,6 @@
if self.is_paid:
update_reimbursed_amount(self)
- self.set_status()
self.update_claimed_amount_in_employee_advance()
def update_claimed_amount_in_employee_advance(self):
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index cee6f37..93fb19f 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -5,7 +5,7 @@
import frappe
from frappe import _
from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, get_fullname, add_days, nowdate
-from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver
+from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver, validate_active_employee
from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import daterange
@@ -22,6 +22,7 @@
return _("{0}: From {0} of type {1}").format(self.employee_name, self.leave_type)
def validate(self):
+ validate_active_employee(self.employee)
set_employee_name(self)
self.validate_dates()
self.validate_balance_leaves()
diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py
index e041b7f..912bd8a 100644
--- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py
+++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py
@@ -7,7 +7,7 @@
from frappe import _
from frappe.model.document import Document
from frappe.utils import getdate, nowdate, flt
-from erpnext.hr.utils import set_employee_name
+from erpnext.hr.utils import set_employee_name, validate_active_employee
from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry
from erpnext.hr.doctype.leave_allocation.leave_allocation import get_unused_leaves
@@ -15,6 +15,7 @@
class LeaveEncashment(Document):
def validate(self):
set_employee_name(self)
+ validate_active_employee(self.employee)
self.get_leave_details_for_encashment()
self.validate_salary_structure()
diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py
index ab65260..89ae4d5 100644
--- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py
+++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py
@@ -9,10 +9,12 @@
from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, now_datetime, nowdate
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
+from erpnext.hr.utils import validate_active_employee
from datetime import timedelta, datetime
class ShiftAssignment(Document):
def validate(self):
+ validate_active_employee(self.employee)
self.validate_overlapping_dates()
if self.end_date and self.end_date <= self.start_date:
diff --git a/erpnext/hr/doctype/shift_request/shift_request.py b/erpnext/hr/doctype/shift_request/shift_request.py
index 177c45e..6461f07 100644
--- a/erpnext/hr/doctype/shift_request/shift_request.py
+++ b/erpnext/hr/doctype/shift_request/shift_request.py
@@ -7,12 +7,13 @@
from frappe import _
from frappe.model.document import Document
from frappe.utils import formatdate, getdate
-from erpnext.hr.utils import share_doc_with_approver
+from erpnext.hr.utils import share_doc_with_approver, validate_active_employee
class OverlapError(frappe.ValidationError): pass
class ShiftRequest(Document):
def validate(self):
+ validate_active_employee(self.employee)
self.validate_dates()
self.validate_shift_request_overlap_dates()
self.validate_approver()
diff --git a/erpnext/hr/doctype/travel_request/travel_request.py b/erpnext/hr/doctype/travel_request/travel_request.py
index 01d3f34..60834d3 100644
--- a/erpnext/hr/doctype/travel_request/travel_request.py
+++ b/erpnext/hr/doctype/travel_request/travel_request.py
@@ -5,6 +5,8 @@
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
+from erpnext.hr.utils import validate_active_employee
class TravelRequest(Document):
- pass
+ def validate(self):
+ validate_active_employee(self.employee)
diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py
index ebb1734..a6a8406 100644
--- a/erpnext/hr/utils.py
+++ b/erpnext/hr/utils.py
@@ -3,13 +3,12 @@
import erpnext
import frappe
-from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
+from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee, InactiveEmployeeStatusError
from frappe import _
from frappe.desk.form import assign_to
from frappe.model.document import Document
from frappe.utils import (add_days, cstr, flt, format_datetime, formatdate,
- get_datetime, getdate, nowdate, today, unique)
-
+ get_datetime, getdate, nowdate, today, unique, get_link_to_form)
class DuplicateDeclarationError(frappe.ValidationError): pass
@@ -20,6 +19,7 @@
Assign to the concerned person and roles as per the onboarding/separation template
'''
def validate(self):
+ validate_active_employee(self.employee)
# remove the task if linked before submitting the form
if self.amended_from:
for activity in self.activities:
@@ -522,3 +522,8 @@
approver = approvers.get(doc.doctype)
if doc_before_save.get(approver) != doc.get(approver):
frappe.share.remove(doc.doctype, doc.name, doc_before_save.get(approver))
+
+def validate_active_employee(employee):
+ if frappe.db.get_value("Employee", employee, "status") == "Inactive":
+ frappe.throw(_("Transactions cannot be created for an Inactive Employee {0}.").format(
+ get_link_to_form("Employee", employee)), InactiveEmployeeStatusError)
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index 15a7c31..bfbc679 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -83,7 +83,7 @@
if (!frm.doc.__islocal && frm.doc.docstatus<2) {
frm.add_custom_button(__("Update Cost"), function() {
- frm.events.update_cost(frm);
+ frm.events.update_cost(frm, true);
});
frm.add_custom_button(__("Browse BOM"), function() {
frappe.route_options = {
@@ -318,14 +318,15 @@
})
},
- update_cost: function(frm) {
+ update_cost: function(frm, save_doc=false) {
return frappe.call({
doc: frm.doc,
method: "update_cost",
freeze: true,
args: {
update_parent: true,
- from_child_bom:false
+ save: save_doc,
+ from_child_bom: false
},
callback: function(r) {
refresh_field("items");
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index c32a8a9..0ba8507 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -330,7 +330,7 @@
frappe.get_doc("BOM", bom).update_cost(from_child_bom=True)
if not from_child_bom:
- frappe.msgprint(_("Cost Updated"))
+ frappe.msgprint(_("Cost Updated"), alert=True)
def update_parent_cost(self):
if self.total_cost:
@@ -713,12 +713,12 @@
"conversion_rate": 1, # Passed conversion rate as 1 purposefully, as conversion rate is applied at the end of the function
"conversion_factor": args.get("conversion_factor") or 1,
"plc_conversion_rate": 1,
- "ignore_party": True
+ "ignore_party": True,
+ "ignore_conversion_rate": True
})
item_doc = frappe.get_cached_doc("Item", args.get("item_code"))
- out = frappe._dict()
- get_price_list_rate(bom_args, item_doc, out)
- rate = out.price_list_rate
+ price_list_data = get_price_list_rate(bom_args, item_doc)
+ rate = price_list_data.price_list_rate
return rate
@@ -747,7 +747,7 @@
if valuation_rate <= 0:
last_valuation_rate = frappe.db.sql("""select valuation_rate
from `tabStock Ledger Entry`
- where item_code = %s and valuation_rate > 0
+ where item_code = %s and valuation_rate > 0 and is_cancelled = 0
order by posting_date desc, posting_time desc, creation desc limit 1""", args['item_code'])
valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0
@@ -773,7 +773,7 @@
item.image,
bom.project,
bom_item.rate,
- bom_item.amount,
+ sum(bom_item.{qty_field}/ifnull(bom.quantity, 1)) * bom_item.rate * %(qty)s as amount,
item.stock_uom,
item.item_group,
item.allow_alternative_item,
@@ -1068,13 +1068,6 @@
if barcodes:
or_cond_filters["name"] = ("in", barcodes)
- for cond in get_match_cond(doctype, as_condition=False):
- for key, value in cond.items():
- if key == doctype:
- key = "name"
-
- query_filters[key] = ("in", value)
-
if filters and filters.get("item_code"):
has_variants = frappe.get_cached_value("Item", filters.get("item_code"), "has_variants")
if not has_variants:
@@ -1083,7 +1076,7 @@
if filters and filters.get("is_stock_item"):
query_filters["is_stock_item"] = 1
- return frappe.get_all("Item",
+ return frappe.get_list("Item",
fields = fields, filters=query_filters,
or_filters = or_cond_filters, order_by=order_by,
limit_start=start, limit_page_length=page_len, as_list=1)
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 420bb00..66e2394 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -192,11 +192,11 @@
"completed_qty": args.get("completed_qty") or 0.0
})
elif args.get("start_time"):
- new_args = {
+ new_args = frappe._dict({
"from_time": get_datetime(args.get("start_time")),
"operation": args.get("sub_operation"),
"completed_qty": 0.0
- }
+ })
if employees:
for name in employees:
@@ -608,6 +608,11 @@
target.set_missing_values()
target.set_stock_entry_type()
+ wo_allows_alternate_item = frappe.db.get_value("Work Order", target.work_order, "allow_alternative_item")
+ for item in target.items:
+ item.allow_alternative_item = int(wo_allows_alternate_item and
+ frappe.get_cached_value("Item", item.item_code, "allow_alternative_item"))
+
doclist = get_mapped_doc("Job Card", source_name, {
"Job Card": {
"doctype": "Stock Entry",
@@ -698,4 +703,4 @@
}
}, target_doc, set_missing_values)
- return doclist
\ No newline at end of file
+ return doclist
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 38a0ee7..8c27d6c 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -109,6 +109,15 @@
so_mr_list = [d.get(field) for d in self.get(table) if d.get(field)]
return so_mr_list
+ def get_bom_item(self):
+ """Check if Item or if its Template has a BOM."""
+ bom_item = None
+ has_bom = frappe.db.exists({'doctype': 'BOM', 'item': self.item_code, 'docstatus': 1})
+ if not has_bom:
+ template_item = frappe.db.get_value('Item', self.item_code, ['variant_of'])
+ bom_item = "bom.item = {0}".format(frappe.db.escape(template_item)) if template_item else bom_item
+ return bom_item
+
def get_so_items(self):
# Check for empty table or empty rows
if not self.get("sales_orders") or not self.get_so_mr_list("sales_order", "sales_orders"):
@@ -117,16 +126,26 @@
so_list = self.get_so_mr_list("sales_order", "sales_orders")
item_condition = ""
- if self.item_code:
+ bom_item = "bom.item = so_item.item_code"
+ if self.item_code and frappe.db.exists('Item', self.item_code):
+ bom_item = self.get_bom_item() or bom_item
item_condition = ' and so_item.item_code = {0}'.format(frappe.db.escape(self.item_code))
- items = frappe.db.sql("""select distinct parent, item_code, warehouse,
- (qty - work_order_qty) * conversion_factor as pending_qty, description, name
- from `tabSales Order Item` so_item
- where parent in (%s) and docstatus = 1 and qty > work_order_qty
- and exists (select name from `tabBOM` bom where bom.item=so_item.item_code
- and bom.is_active = 1) %s""" % \
- (", ".join(["%s"] * len(so_list)), item_condition), tuple(so_list), as_dict=1)
+ items = frappe.db.sql("""
+ select
+ distinct parent, item_code, warehouse,
+ (qty - work_order_qty) * conversion_factor as pending_qty,
+ description, name
+ from
+ `tabSales Order Item` so_item
+ where
+ parent in (%s) and docstatus = 1 and qty > work_order_qty
+ and exists (select name from `tabBOM` bom where %s
+ and bom.is_active = 1) %s""" %
+ (", ".join(["%s"] * len(so_list)),
+ bom_item,
+ item_condition),
+ tuple(so_list), as_dict=1)
if self.item_code:
item_condition = ' and so_item.item_code = {0}'.format(frappe.db.escape(self.item_code))
@@ -683,6 +702,7 @@
def get_sales_orders(self):
so_filter = item_filter = ""
+ bom_item = "bom.item = so_item.item_code"
if self.from_date:
so_filter += " and so.transaction_date >= %(from_date)s"
if self.to_date:
@@ -694,7 +714,8 @@
if self.sales_order_status:
so_filter += "and so.status = %(sales_order_status)s"
- if self.item_code:
+ if self.item_code and frappe.db.exists('Item', self.item_code):
+ bom_item = self.get_bom_item() or bom_item
item_filter += " and so_item.item_code = %(item)s"
open_so = frappe.db.sql("""
@@ -704,13 +725,13 @@
and so.docstatus = 1 and so.status not in ("Stopped", "Closed")
and so.company = %(company)s
and so_item.qty > so_item.work_order_qty {0} {1}
- and (exists (select name from `tabBOM` bom where bom.item=so_item.item_code
+ and (exists (select name from `tabBOM` bom where {2}
and bom.is_active = 1)
or exists (select name from `tabPacked Item` pi
where pi.parent = so.name and pi.parent_item = so_item.item_code
and exists (select name from `tabBOM` bom where bom.item=pi.item_code
and bom.is_active = 1)))
- """.format(so_filter, item_filter), {
+ """.format(so_filter, item_filter, bom_item), {
"from_date": self.from_date,
"to_date": self.to_date,
"customer": self.customer,
@@ -747,9 +768,8 @@
group by item_code, warehouse
""".format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1)
-def get_warehouse_list(warehouses, warehouse_list=None):
- if not warehouse_list:
- warehouse_list = []
+def get_warehouse_list(warehouses):
+ warehouse_list = []
if isinstance(warehouses, str):
warehouses = json.loads(warehouses)
@@ -761,23 +781,19 @@
else:
warehouse_list.append(row.get("warehouse"))
+ return warehouse_list
+
@frappe.whitelist()
def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_data=None):
if isinstance(doc, str):
doc = frappe._dict(json.loads(doc))
- warehouse_list = []
if warehouses:
- get_warehouse_list(warehouses, warehouse_list)
-
- if warehouse_list:
- warehouses = list(set(warehouse_list))
+ warehouses = list(set(get_warehouse_list(warehouses)))
if doc.get("for_warehouse") and not get_parent_warehouse_data and doc.get("for_warehouse") in warehouses:
warehouses.remove(doc.get("for_warehouse"))
- warehouse_list = None
-
doc['mr_items'] = []
po_items = doc.get('po_items') if doc.get('po_items') else doc.get('items')
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index cce1bb6..af8de8e 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -10,7 +10,8 @@
from erpnext.manufacturing.doctype.production_plan.production_plan import get_sales_orders
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
-from erpnext.manufacturing.doctype.production_plan.production_plan import get_items_for_material_requests
+from erpnext.manufacturing.doctype.production_plan.production_plan import get_items_for_material_requests, get_warehouse_list
+from erpnext.controllers.item_variant import create_variant
class TestProductionPlan(unittest.TestCase):
def setUp(self):
@@ -251,6 +252,81 @@
pln.cancel()
frappe.delete_doc("Production Plan", pln.name)
+ def test_get_warehouse_list_group(self):
+ """Check if required warehouses are returned"""
+ warehouse_json = '[{\"warehouse\":\"_Test Warehouse Group - _TC\"}]'
+
+ warehouses = set(get_warehouse_list(warehouse_json))
+ expected_warehouses = {"_Test Warehouse Group-C1 - _TC", "_Test Warehouse Group-C2 - _TC"}
+
+ missing_warehouse = expected_warehouses - warehouses
+
+ self.assertTrue(len(missing_warehouse) == 0,
+ msg=f"Following warehouses were expected {', '.join(missing_warehouse)}")
+
+ def test_get_warehouse_list_single(self):
+ warehouse_json = '[{\"warehouse\":\"_Test Scrap Warehouse - _TC\"}]'
+
+ warehouses = set(get_warehouse_list(warehouse_json))
+ expected_warehouses = {"_Test Scrap Warehouse - _TC", }
+
+ self.assertEqual(warehouses, expected_warehouses)
+
+ def test_get_sales_order_with_variant(self):
+ if not frappe.db.exists('Item', {"item_code": 'PIV'}):
+ item = create_item('PIV', valuation_rate = 100)
+ variant_settings = {
+ "attributes": [
+ {
+ "attribute": "Colour"
+ },
+ ],
+ "has_variants": 1
+ }
+ item.update(variant_settings)
+ item.save()
+ parent_bom = make_bom(item = 'PIV', raw_materials = ['PIV'])
+ if not frappe.db.exists('BOM', {"item": 'PIV'}):
+ parent_bom = make_bom(item = 'PIV', raw_materials = ['PIV'])
+ else:
+ parent_bom = frappe.get_doc('BOM', {"item": 'PIV'})
+
+ if not frappe.db.exists('Item', {"item_code": 'PIV-RED'}):
+ variant = create_variant("PIV", {"Colour": "Red"})
+ variant.save()
+ variant_bom = make_bom(item = variant.item_code, raw_materials = [variant.item_code])
+ else:
+ variant = frappe.get_doc('Item', 'PIV-RED')
+ if not frappe.db.exists('BOM', {"item": 'PIV-RED'}):
+ variant_bom = make_bom(item = variant.item_code, raw_materials = [variant.item_code])
+
+ """Testing when item variant has a BOM"""
+ so = make_sales_order(item_code="PIV-RED", qty=5)
+ pln = frappe.new_doc('Production Plan')
+ pln.company = so.company
+ pln.get_items_from = 'Sales Order'
+ pln.item_code = 'PIV-RED'
+ pln.get_open_sales_orders()
+ self.assertEqual(pln.sales_orders[0].sales_order, so.name)
+ pln.get_so_items()
+ self.assertEqual(pln.po_items[0].item_code, 'PIV-RED')
+ self.assertEqual(pln.po_items[0].bom_no, variant_bom.name)
+ so.cancel()
+ frappe.delete_doc('Sales Order', so.name)
+ variant_bom.cancel()
+ frappe.delete_doc('BOM', variant_bom.name)
+
+ """Testing when item variant doesn't have a BOM"""
+ so = make_sales_order(item_code="PIV-RED", qty=5)
+ pln.get_open_sales_orders()
+ self.assertEqual(pln.sales_orders[0].sales_order, so.name)
+ pln.po_items = []
+ pln.get_so_items()
+ self.assertEqual(pln.po_items[0].item_code, 'PIV-RED')
+ self.assertEqual(pln.po_items[0].bom_no, parent_bom.name)
+
+ frappe.db.rollback()
+
def create_production_plan(**args):
args = frappe._dict(args)
diff --git a/erpnext/manufacturing/doctype/sub_operation/sub_operation.json b/erpnext/manufacturing/doctype/sub_operation/sub_operation.json
index f63d2b9..10cee32 100644
--- a/erpnext/manufacturing/doctype/sub_operation/sub_operation.json
+++ b/erpnext/manufacturing/doctype/sub_operation/sub_operation.json
@@ -19,6 +19,7 @@
"options": "Operation"
},
{
+ "default": "0",
"description": "Time in mins",
"fieldname": "time_in_mins",
"fieldtype": "Float",
@@ -38,7 +39,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2020-12-07 18:09:18.005578",
+ "modified": "2021-07-15 16:39:41.635362",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Sub Operation",
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index 68de0b2..bf1ccb7 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -513,6 +513,60 @@
work_order1.save()
self.assertEqual(work_order1.operations[0].time_in_mins, 40.0)
+ def test_batch_size_for_fg_item(self):
+ fg_item = "Test Batch Size Item For BOM 3"
+ rm1 = "Test Batch Size Item RM 1 For BOM 3"
+
+ frappe.db.set_value('Manufacturing Settings', None, 'make_serial_no_batch_from_work_order', 0)
+ for item in ["Test Batch Size Item For BOM 3", "Test Batch Size Item RM 1 For BOM 3"]:
+ item_args = {
+ "include_item_in_manufacturing": 1,
+ "is_stock_item": 1
+ }
+
+ if item == fg_item:
+ item_args['has_batch_no'] = 1
+ item_args['create_new_batch'] = 1
+ item_args['batch_number_series'] = 'TBSI3.#####'
+
+ make_item(item, item_args)
+
+ 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.save()
+ bom.submit()
+ bom_name = bom.name
+
+ work_order = make_wo_order_test_record(item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1)
+ ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1))
+ for row in ste1.get('items'):
+ if row.is_finished_item:
+ self.assertEqual(row.item_code, fg_item)
+
+ work_order = make_wo_order_test_record(item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1)
+ frappe.db.set_value('Manufacturing Settings', None, 'make_serial_no_batch_from_work_order', 1)
+ ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1))
+ for row in ste1.get('items'):
+ if row.is_finished_item:
+ self.assertEqual(row.item_code, fg_item)
+
+ work_order = make_wo_order_test_record(item=fg_item, skip_transfer=True, planned_start_date=now(),
+ qty=30, do_not_save = True)
+ work_order.batch_size = 10
+ work_order.insert()
+ work_order.submit()
+ self.assertEqual(work_order.has_batch_no, 1)
+ ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 30))
+ for row in ste1.get('items'):
+ if row.is_finished_item:
+ self.assertEqual(row.item_code, fg_item)
+ self.assertEqual(row.qty, 10)
+
+ frappe.db.set_value('Manufacturing Settings', None, 'make_serial_no_batch_from_work_order', 0)
+
def test_partial_material_consumption(self):
frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 1)
wo_order = make_wo_order_test_record(planned_start_date=now(), qty=4)
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 779ae42..282b5d0 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -239,7 +239,7 @@
self.create_serial_no_batch_no()
def on_submit(self):
- if not self.wip_warehouse:
+ if not self.wip_warehouse and not self.skip_transfer:
frappe.throw(_("Work-in-Progress Warehouse is required before Submit"))
if not self.fg_warehouse:
frappe.throw(_("For Warehouse is required before Submit"))
@@ -487,21 +487,20 @@
return
operations = []
- if not self.use_multi_level_bom:
- bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity")
- operations.extend(_get_operations(self.bom_no, qty=1.0/bom_qty))
- else:
+
+ if self.use_multi_level_bom:
bom_tree = frappe.get_doc("BOM", self.bom_no).get_tree_representation()
- bom_traversal = list(reversed(bom_tree.level_order_traversal()))
- bom_traversal.append(bom_tree) # add operation on top level item last
+ bom_traversal = reversed(bom_tree.level_order_traversal())
- for d in bom_traversal:
- if d.is_bom:
- operations.extend(_get_operations(d.name, qty=d.exploded_qty))
+ for node in bom_traversal:
+ if node.is_bom:
+ operations.extend(_get_operations(node.name, qty=node.exploded_qty))
- for correct_index, operation in enumerate(operations, start=1):
- operation.idx = correct_index
+ bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity")
+ operations.extend(_get_operations(self.bom_no, qty=1.0/bom_qty))
+ for correct_index, operation in enumerate(operations, start=1):
+ operation.idx = correct_index
self.set('operations', operations)
self.calculate_time()
@@ -656,7 +655,7 @@
for item in sorted(item_dict.values(), key=lambda d: d['idx'] or 9999):
self.append('required_items', {
'rate': item.rate,
- 'amount': item.amount,
+ 'amount': item.rate * item.qty,
'operation': item.operation or operation,
'item_code': item.item_code,
'item_name': item.item_name,
diff --git a/erpnext/non_profit/doctype/member/member.json b/erpnext/non_profit/doctype/member/member.json
index f190cfa..7c1baf1 100644
--- a/erpnext/non_profit/doctype/member/member.json
+++ b/erpnext/non_profit/doctype/member/member.json
@@ -26,7 +26,7 @@
"razorpay_details_section",
"subscription_id",
"customer_id",
- "subscription_activated",
+ "subscription_status",
"column_break_21",
"subscription_start",
"subscription_end"
@@ -152,12 +152,6 @@
"fieldtype": "Column Break"
},
{
- "default": "0",
- "fieldname": "subscription_activated",
- "fieldtype": "Check",
- "label": "Subscription Activated"
- },
- {
"fieldname": "subscription_start",
"fieldtype": "Date",
"label": "Subscription Start "
@@ -166,11 +160,17 @@
"fieldname": "subscription_end",
"fieldtype": "Date",
"label": "Subscription End"
+ },
+ {
+ "fieldname": "subscription_status",
+ "fieldtype": "Select",
+ "label": "Subscription Status",
+ "options": "\nActive\nHalted"
}
],
"image_field": "image",
"links": [],
- "modified": "2020-11-09 12:12:10.174647",
+ "modified": "2021-07-11 14:27:26.368039",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Member",
diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py
index 30be585..67828d6 100644
--- a/erpnext/non_profit/doctype/member/member.py
+++ b/erpnext/non_profit/doctype/member/member.py
@@ -84,7 +84,9 @@
"email_id": user_details.email,
"pan_number": user_details.pan or None,
"membership_type": user_details.plan_id,
- "subscription_id": user_details.subscription_id or None
+ "customer_id": user_details.customer_id or None,
+ "subscription_id": user_details.subscription_id or None,
+ "subscription_status": user_details.subscription_status or ""
})
member.insert(ignore_permissions=True)
diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py
index e8ae618..b584116 100644
--- a/erpnext/non_profit/doctype/membership/membership.py
+++ b/erpnext/non_profit/doctype/membership/membership.py
@@ -196,11 +196,14 @@
return invoice
-def get_member_based_on_subscription(subscription_id, email):
- members = frappe.get_all("Member", filters={
- "subscription_id": subscription_id,
- "email_id": email
- }, order_by="creation desc")
+def get_member_based_on_subscription(subscription_id, email=None, customer_id=None):
+ filters = {"subscription_id": subscription_id}
+ if email:
+ filters.update({"email_id": email})
+ if customer_id:
+ filters.update({"customer_id": customer_id})
+
+ members = frappe.get_all("Member", filters=filters, order_by="creation desc")
try:
return frappe.get_doc("Member", members[0]["name"])
@@ -209,8 +212,6 @@
def verify_signature(data, endpoint="Membership"):
- if frappe.flags.in_test or os.environ.get("CI"):
- return True
signature = frappe.request.headers.get("X-Razorpay-Signature")
settings = frappe.get_doc("Non Profit Settings")
@@ -225,16 +226,7 @@
@frappe.whitelist(allow_guest=True)
def trigger_razorpay_subscription(*args, **kwargs):
data = frappe.request.get_data(as_text=True)
- try:
- verify_signature(data)
- except Exception as e:
- log = frappe.log_error(e, "Membership Webhook Verification Error")
- notify_failure(log)
- return { "status": "Failed", "reason": e}
-
- if isinstance(data, six.string_types):
- data = json.loads(data)
- data = frappe._dict(data)
+ data = process_request_data(data)
subscription = data.payload.get("subscription", {}).get("entity", {})
subscription = frappe._dict(subscription)
@@ -281,7 +273,7 @@
# Update membership values
member.subscription_start = datetime.fromtimestamp(subscription.start_at)
member.subscription_end = datetime.fromtimestamp(subscription.end_at)
- member.subscription_activated = 1
+ member.subscription_status = "Active"
member.flags.ignore_mandatory = True
member.save()
@@ -294,9 +286,67 @@
message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), _("Payment ID"), payment.id)
log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name))
notify_failure(log)
- return { "status": "Failed", "reason": e}
+ return {"status": "Failed", "reason": e}
- return { "status": "Success" }
+ return {"status": "Success"}
+
+
+@frappe.whitelist(allow_guest=True)
+def update_halted_razorpay_subscription(*args, **kwargs):
+ """
+ When all retries have been exhausted, Razorpay moves the subscription to the halted state.
+ The customer has to manually retry the charge or change the card linked to the subscription,
+ for the subscription to move back to the active state.
+ """
+ if frappe.request:
+ data = frappe.request.get_data(as_text=True)
+ data = process_request_data(data)
+ elif frappe.flags.in_test:
+ data = kwargs.get("data")
+ data = frappe._dict(data)
+ else:
+ return
+
+ if not data.event == "subscription.halted":
+ return
+
+ subscription = data.payload.get("subscription", {}).get("entity", {})
+ subscription = frappe._dict(subscription)
+
+ try:
+ member = get_member_based_on_subscription(subscription.id, customer_id=subscription.customer_id)
+ if not member:
+ frappe.throw(_("Member with Razorpay Subscription ID {0} not found").format(subscription.id))
+
+ member.subscription_status = "Halted"
+ member.flags.ignore_mandatory = True
+ member.save()
+
+ if subscription.get("notes"):
+ member = get_additional_notes(member, subscription)
+
+ except Exception as e:
+ message = "{0}\n\n{1}".format(e, frappe.get_traceback())
+ log = frappe.log_error(message, _("Error updating halted status for member {0}").format(member.name))
+ notify_failure(log)
+ return {"status": "Failed", "reason": e}
+
+ return {"status": "Success"}
+
+
+def process_request_data(data):
+ try:
+ verify_signature(data)
+ except Exception as e:
+ log = frappe.log_error(e, "Membership Webhook Verification Error")
+ notify_failure(log)
+ return {"status": "Failed", "reason": e}
+
+ if isinstance(data, six.string_types):
+ data = json.loads(data)
+ data = frappe._dict(data)
+
+ return data
def get_company_for_memberships():
@@ -362,4 +412,4 @@
`tabMembership` SET `status` = 'Expired'
WHERE
`status` not in ('Cancelled') AND `to_date` < %s
- """, (nowdate()))
\ No newline at end of file
+ """, (nowdate()))
diff --git a/erpnext/non_profit/doctype/membership/test_membership.py b/erpnext/non_profit/doctype/membership/test_membership.py
index 31da792..0f5a9be 100644
--- a/erpnext/non_profit/doctype/membership/test_membership.py
+++ b/erpnext/non_profit/doctype/membership/test_membership.py
@@ -6,6 +6,7 @@
import frappe
import erpnext
from erpnext.non_profit.doctype.member.member import create_member
+from erpnext.non_profit.doctype.membership.membership import update_halted_razorpay_subscription
from frappe.utils import nowdate, add_months
class TestMembership(unittest.TestCase):
@@ -13,11 +14,16 @@
plan = setup_membership()
# make test member
- self.member_doc = create_member(frappe._dict({
- 'fullname': "_Test_Member",
- 'email': "_test_member_erpnext@example.com",
- 'plan_id': plan.name
- }))
+ self.member_doc = create_member(
+ frappe._dict({
+ "fullname": "_Test_Member",
+ "email": "_test_member_erpnext@example.com",
+ "plan_id": plan.name,
+ "subscription_id": "sub_DEX6xcJ1HSW4CR",
+ "customer_id": "cust_C0WlbKhp3aLA7W",
+ "subscription_status": "Active"
+ })
+ )
self.member_doc.make_customer_and_link()
self.member = self.member_doc.name
@@ -51,6 +57,20 @@
"to_date": add_months(nowdate(), 3),
})
+ def test_halted_memberships(self):
+ make_membership(self.member, {
+ "from_date": add_months(nowdate(), 2),
+ "to_date": add_months(nowdate(), 3)
+ })
+
+ self.assertEqual(frappe.db.get_value("Member", self.member, "subscription_status"), "Active")
+ payload = get_subscription_payload()
+ update_halted_razorpay_subscription(data=payload)
+ self.assertEqual(frappe.db.get_value("Member", self.member, "subscription_status"), "Halted")
+
+ def tearDown(self):
+ frappe.db.rollback()
+
def set_config(key, value):
frappe.db.set_value("Non Profit Settings", None, key, value)
@@ -115,4 +135,28 @@
else:
plan = frappe.get_doc("Membership Type", "_rzpy_test_milythm")
- return plan
\ No newline at end of file
+ return plan
+
+def get_subscription_payload():
+ return {
+ "entity": "event",
+ "account_id": "acc_BFQ7uQEaa7j2z7",
+ "event": "subscription.halted",
+ "contains": [
+ "subscription"
+ ],
+ "payload": {
+ "subscription": {
+ "entity": {
+ "id": "sub_DEX6xcJ1HSW4CR",
+ "entity": "subscription",
+ "plan_id": "_rzpy_test_milythm",
+ "customer_id": "cust_C0WlbKhp3aLA7W",
+ "status": "halted",
+ "notes": {
+ "Important": "Notes for Internal Reference"
+ },
+ }
+ }
+ }
+ }
\ No newline at end of file
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 29376f0..ada3bad 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -291,3 +291,11 @@
erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice
erpnext.patches.v13_0.update_job_card_details
erpnext.patches.v13_0.update_level_in_bom #1234sswef
+erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry
+erpnext.patches.v13_0.update_subscription_status_in_memberships
+erpnext.patches.v13_0.update_amt_in_work_order_required_items
+erpnext.patches.v13_0.delete_orphaned_tables
+erpnext.patches.v13_0.update_export_type_for_gst
+erpnext.patches.v13_0.update_tds_check_field #3
+erpnext.patches.v13_0.update_recipient_email_digest
+erpnext.patches.v13_0.shopify_deprecation_warning
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py b/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py
new file mode 100644
index 0000000..0d8109c
--- /dev/null
+++ b/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py
@@ -0,0 +1,112 @@
+# Copyright (c) 2020, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe
+from frappe.utils import cstr, flt, cint
+from erpnext.stock.stock_ledger import make_sl_entries
+from erpnext.controllers.stock_controller import create_repost_item_valuation_entry
+
+def execute():
+ if not frappe.db.has_column('Work Order', 'has_batch_no'):
+ return
+
+ frappe.reload_doc('manufacturing', 'doctype', 'manufacturing_settings')
+ if cint(frappe.db.get_single_value('Manufacturing Settings', 'make_serial_no_batch_from_work_order')):
+ return
+
+ frappe.reload_doc('manufacturing', 'doctype', 'work_order')
+ filters = {
+ 'docstatus': 1,
+ 'produced_qty': ('>', 0),
+ 'creation': ('>=', '2021-06-29 00:00:00'),
+ 'has_batch_no': 1
+ }
+
+ fields = ['name', 'production_item']
+
+ work_orders = [d.name for d in frappe.get_all('Work Order', filters = filters, fields=fields)]
+
+ if not work_orders:
+ return
+
+ repost_stock_entries = []
+
+ stock_entries = frappe.db.sql_list('''
+ SELECT
+ se.name
+ FROM
+ `tabStock Entry` se
+ WHERE
+ se.purpose = 'Manufacture' and se.docstatus < 2 and se.work_order in %s
+ and not exists(
+ select name from `tabStock Entry Detail` sed where sed.parent = se.name and sed.is_finished_item = 1
+ )
+ ORDER BY
+ se.posting_date, se.posting_time
+ ''', (work_orders,))
+
+ if stock_entries:
+ print('Length of stock entries', len(stock_entries))
+
+ for stock_entry in stock_entries:
+ doc = frappe.get_doc('Stock Entry', stock_entry)
+ doc.set_work_order_details()
+ doc.load_items_from_bom()
+ doc.calculate_rate_and_amount()
+ set_expense_account(doc)
+ doc.make_batches('t_warehouse')
+
+ if doc.docstatus == 0:
+ doc.save()
+ else:
+ repost_stock_entry(doc)
+ repost_stock_entries.append(doc)
+
+ for repost_doc in repost_stock_entries:
+ repost_future_sle_and_gle(repost_doc)
+
+def set_expense_account(doc):
+ for row in doc.items:
+ if row.is_finished_item and not row.expense_account:
+ row.expense_account = frappe.get_cached_value('Company', doc.company, 'stock_adjustment_account')
+
+def repost_stock_entry(doc):
+ doc.db_update()
+ for child_row in doc.items:
+ if child_row.is_finished_item:
+ child_row.db_update()
+
+ sl_entries = []
+ finished_item_row = doc.get_finished_item_row()
+ get_sle_for_target_warehouse(doc, sl_entries, finished_item_row)
+
+ if sl_entries:
+ try:
+ make_sl_entries(sl_entries, True)
+ except Exception:
+ print(f'SLE entries not posted for the stock entry {doc.name}')
+ traceback = frappe.get_traceback()
+ frappe.log_error(traceback)
+
+def get_sle_for_target_warehouse(doc, sl_entries, finished_item_row):
+ for d in doc.get('items'):
+ if cstr(d.t_warehouse) and finished_item_row and d.name == finished_item_row.name:
+ sle = doc.get_sl_entries(d, {
+ "warehouse": cstr(d.t_warehouse),
+ "actual_qty": flt(d.transfer_qty),
+ "incoming_rate": flt(d.valuation_rate)
+ })
+
+ sle.recalculate_rate = 1
+ sl_entries.append(sle)
+
+def repost_future_sle_and_gle(doc):
+ args = frappe._dict({
+ "posting_date": doc.posting_date,
+ "posting_time": doc.posting_time,
+ "voucher_type": doc.doctype,
+ "voucher_no": doc.name,
+ "company": doc.company
+ })
+
+ create_repost_item_valuation_entry(args)
diff --git a/erpnext/patches/v13_0/delete_orphaned_tables.py b/erpnext/patches/v13_0/delete_orphaned_tables.py
new file mode 100644
index 0000000..1d6eebe
--- /dev/null
+++ b/erpnext/patches/v13_0/delete_orphaned_tables.py
@@ -0,0 +1,69 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+from frappe.utils import getdate
+
+def execute():
+ frappe.reload_doc('setup', 'doctype', 'transaction_deletion_record')
+
+ if has_deleted_company_transactions():
+ child_doctypes = get_child_doctypes_whose_parent_doctypes_were_affected()
+
+ for doctype in child_doctypes:
+ docs = frappe.get_all(doctype, fields=['name', 'parent', 'parenttype', 'creation'])
+
+ for doc in docs:
+ if not frappe.db.exists(doc['parenttype'], doc['parent']):
+ frappe.db.delete(doctype, {'name': doc['name']})
+
+ elif check_for_new_doc_with_same_name_as_deleted_parent(doc):
+ frappe.db.delete(doctype, {'name': doc['name']})
+
+def has_deleted_company_transactions():
+ return frappe.get_all('Transaction Deletion Record')
+
+def get_child_doctypes_whose_parent_doctypes_were_affected():
+ parent_doctypes = get_affected_doctypes()
+ child_doctypes = frappe.get_all(
+ 'DocField',
+ filters={
+ 'fieldtype': 'Table',
+ 'parent':['in', parent_doctypes]
+ }, pluck='options')
+
+ return child_doctypes
+
+def get_affected_doctypes():
+ affected_doctypes = []
+ tdr_docs = frappe.get_all('Transaction Deletion Record', pluck="name")
+
+ for tdr in tdr_docs:
+ tdr_doc = frappe.get_doc("Transaction Deletion Record", tdr)
+
+ for doctype in tdr_doc.doctypes:
+ if is_not_child_table(doctype.doctype_name):
+ affected_doctypes.append(doctype.doctype_name)
+
+ affected_doctypes = remove_duplicate_items(affected_doctypes)
+ return affected_doctypes
+
+def is_not_child_table(doctype):
+ return not bool(frappe.get_value('DocType', doctype, 'istable'))
+
+def remove_duplicate_items(affected_doctypes):
+ return list(set(affected_doctypes))
+
+def check_for_new_doc_with_same_name_as_deleted_parent(doc):
+ """
+ Compares creation times of parent and child docs.
+ Since Transaction Deletion Record resets the naming series after deletion,
+ it allows the creation of new docs with the same names as the deleted ones.
+ """
+
+ parent_creation_time = frappe.db.get_value(doc['parenttype'], doc['parent'], 'creation')
+ child_creation_time = doc['creation']
+
+ return getdate(parent_creation_time) > getdate(child_creation_time)
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/rename_issue_doctype_fields.py b/erpnext/patches/v13_0/rename_issue_doctype_fields.py
index fa1dfed..41c51c3 100644
--- a/erpnext/patches/v13_0/rename_issue_doctype_fields.py
+++ b/erpnext/patches/v13_0/rename_issue_doctype_fields.py
@@ -37,7 +37,7 @@
if frappe.db.exists('DocType', 'Opportunity'):
opportunities = frappe.db.get_all('Opportunity', fields=['name', 'mins_to_first_response'], order_by='creation desc')
- frappe.reload_doc('crm', 'doctype', 'opportunity')
+ frappe.reload_doctype('Opportunity', force=True)
rename_field('Opportunity', 'mins_to_first_response', 'first_response_time')
# change fieldtype to duration
diff --git a/erpnext/patches/v13_0/shopify_deprecation_warning.py b/erpnext/patches/v13_0/shopify_deprecation_warning.py
new file mode 100644
index 0000000..8b0f193
--- /dev/null
+++ b/erpnext/patches/v13_0/shopify_deprecation_warning.py
@@ -0,0 +1,15 @@
+import click
+import frappe
+
+
+def execute():
+
+ frappe.reload_doc("erpnext_integrations", "doctype", "shopify_settings")
+ if not frappe.db.get_single_value("Shopify Settings", "enable_shopify"):
+ return
+
+ click.secho(
+ "Shopify Integration is moved to a separate app and will be removed from ERPNext in version-14.\n"
+ "Please install the app to continue using the integration: https://github.com/frappe/ecommerce_integrations",
+ fg="yellow",
+ )
diff --git a/erpnext/patches/v13_0/update_amt_in_work_order_required_items.py b/erpnext/patches/v13_0/update_amt_in_work_order_required_items.py
new file mode 100644
index 0000000..eae5ff6
--- /dev/null
+++ b/erpnext/patches/v13_0/update_amt_in_work_order_required_items.py
@@ -0,0 +1,10 @@
+import frappe
+
+def execute():
+ """ Correct amount in child table of required items table."""
+
+ frappe.reload_doc("manufacturing", "doctype", "work_order")
+ frappe.reload_doc("manufacturing", "doctype", "work_order_item")
+
+ frappe.db.sql("""UPDATE `tabWork Order Item` SET amount = rate * required_qty""")
+
diff --git a/erpnext/patches/v13_0/update_export_type_for_gst.py b/erpnext/patches/v13_0/update_export_type_for_gst.py
new file mode 100644
index 0000000..478a2a6
--- /dev/null
+++ b/erpnext/patches/v13_0/update_export_type_for_gst.py
@@ -0,0 +1,24 @@
+import frappe
+
+def execute():
+ company = frappe.get_all('Company', filters = {'country': 'India'})
+ if not company:
+ return
+
+ # Update custom fields
+ fieldname = frappe.db.get_value('Custom Field', {'dt': 'Customer', 'fieldname': 'export_type'})
+ if fieldname:
+ frappe.db.set_value('Custom Field', fieldname, 'default', '')
+
+ fieldname = frappe.db.get_value('Custom Field', {'dt': 'Supplier', 'fieldname': 'export_type'})
+ if fieldname:
+ frappe.db.set_value('Custom Field', fieldname, 'default', '')
+
+ # Update Customer/Supplier Masters
+ frappe.db.sql("""
+ UPDATE `tabCustomer` set export_type = '' WHERE gst_category NOT IN ('SEZ', 'Overseas', 'Deemed Export')
+ """)
+
+ frappe.db.sql("""
+ UPDATE `tabSupplier` set export_type = '' WHERE gst_category NOT IN ('SEZ', 'Overseas')
+ """)
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/update_recipient_email_digest.py b/erpnext/patches/v13_0/update_recipient_email_digest.py
new file mode 100644
index 0000000..d9aa03f
--- /dev/null
+++ b/erpnext/patches/v13_0/update_recipient_email_digest.py
@@ -0,0 +1,21 @@
+# Copyright (c) 2020, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ frappe.reload_doc("setup", "doctype", "Email Digest")
+ frappe.reload_doc("setup", "doctype", "Email Digest Recipient")
+ email_digests = frappe.db.get_list('Email Digest', fields=['name', 'recipient_list'])
+ for email_digest in email_digests:
+ if email_digest.recipient_list:
+ for recipient in email_digest.recipient_list.split("\n"):
+ doc = frappe.get_doc({
+ 'doctype': 'Email Digest Recipient',
+ 'parenttype': 'Email Digest',
+ 'parentfield': 'recipients',
+ 'parent': email_digest.name,
+ 'recipient': recipient
+ })
+ doc.insert()
diff --git a/erpnext/patches/v13_0/update_subscription_status_in_memberships.py b/erpnext/patches/v13_0/update_subscription_status_in_memberships.py
new file mode 100644
index 0000000..28e650e
--- /dev/null
+++ b/erpnext/patches/v13_0/update_subscription_status_in_memberships.py
@@ -0,0 +1,9 @@
+import frappe
+
+def execute():
+ if frappe.db.exists('DocType', 'Member'):
+ frappe.reload_doc('Non Profit', 'doctype', 'Member')
+
+ if frappe.db.has_column('Member', 'subscription_activated'):
+ frappe.db.sql('UPDATE `tabMember` SET subscription_status = "Active" WHERE subscription_activated = 1')
+ frappe.db.sql_ddl('ALTER table `tabMember` DROP COLUMN subscription_activated')
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/update_tds_check_field.py b/erpnext/patches/v13_0/update_tds_check_field.py
new file mode 100644
index 0000000..3d14958
--- /dev/null
+++ b/erpnext/patches/v13_0/update_tds_check_field.py
@@ -0,0 +1,9 @@
+import frappe
+
+def execute():
+ if frappe.db.has_table("Tax Withholding Category") \
+ and frappe.db.has_column("Tax Withholding Category", "round_off_tax_amount"):
+ frappe.db.sql("""
+ UPDATE `tabTax Withholding Category` set round_off_tax_amount = 0
+ WHERE round_off_tax_amount IS NULL
+ """)
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py
index ebeddf9..381f399 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.py
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py
@@ -7,6 +7,7 @@
from frappe.model.document import Document
from frappe import _, bold
from frappe.utils import getdate, date_diff, comma_and, formatdate
+from erpnext.hr.utils import validate_active_employee
class AdditionalSalary(Document):
def on_submit(self):
@@ -19,6 +20,7 @@
self.update_employee_referral(cancel=True)
def validate(self):
+ validate_active_employee(self.employee)
self.validate_dates()
self.validate_salary_structure()
self.validate_recurring_additional_salary_overlap()
diff --git a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py
index 27df30a..5ebe514 100644
--- a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py
+++ b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py
@@ -9,10 +9,11 @@
from frappe.model.document import Document
from erpnext.payroll.doctype.payroll_period.payroll_period import get_payroll_period_days, get_period_factor
from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure
-from erpnext.hr.utils import get_sal_slip_total_benefit_given, get_holidays_for_employee, get_previous_claimed_amount
+from erpnext.hr.utils import get_sal_slip_total_benefit_given, get_holidays_for_employee, get_previous_claimed_amount, validate_active_employee
class EmployeeBenefitApplication(Document):
def validate(self):
+ validate_active_employee(self.employee)
self.validate_duplicate_on_payroll_period()
if not self.max_benefits:
self.max_benefits = get_max_benefits_remaining(self.employee, self.date, self.payroll_period)
diff --git a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py
index d9937a7..c6713f3 100644
--- a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py
+++ b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py
@@ -8,12 +8,13 @@
from frappe.utils import flt
from frappe.model.document import Document
from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_max_benefits
-from erpnext.hr.utils import get_previous_claimed_amount
+from erpnext.hr.utils import get_previous_claimed_amount, validate_active_employee
from erpnext.payroll.doctype.payroll_period.payroll_period import get_payroll_period
from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure
class EmployeeBenefitClaim(Document):
def validate(self):
+ validate_active_employee(self.employee)
max_benefits = get_max_benefits(self.employee, self.claim_date)
if not max_benefits or max_benefits <= 0:
frappe.throw(_("Employee {0} has no maximum benefit amount").format(self.employee))
diff --git a/erpnext/payroll/doctype/employee_incentive/employee_incentive.py b/erpnext/payroll/doctype/employee_incentive/employee_incentive.py
index ead3db1..6b918ba 100644
--- a/erpnext/payroll/doctype/employee_incentive/employee_incentive.py
+++ b/erpnext/payroll/doctype/employee_incentive/employee_incentive.py
@@ -6,9 +6,11 @@
import frappe
from frappe import _
from frappe.model.document import Document
+from erpnext.hr.utils import validate_active_employee
class EmployeeIncentive(Document):
def validate(self):
+ validate_active_employee(self.employee)
self.validate_salary_structure()
def validate_salary_structure(self):
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py
index fb71a28..e11d60a 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py
+++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py
@@ -8,11 +8,12 @@
from frappe import _
from frappe.utils import flt
from frappe.model.mapper import get_mapped_doc
-from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, \
+from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, validate_active_employee, \
calculate_annual_eligible_hra_exemption, validate_duplicate_exemption_for_payroll_period
class EmployeeTaxExemptionDeclaration(Document):
def validate(self):
+ validate_active_employee(self.employee)
validate_tax_declaration(self.declarations)
validate_duplicate_exemption_for_payroll_period(self.doctype, self.name, self.payroll_period, self.employee)
self.set_total_declared_amount()
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py
index 5bc33a6..8131ae0 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py
+++ b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py
@@ -7,11 +7,12 @@
from frappe.model.document import Document
from frappe import _
from frappe.utils import flt
-from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, \
+from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, validate_active_employee, \
calculate_hra_exemption_for_period, validate_duplicate_exemption_for_payroll_period
class EmployeeTaxExemptionProofSubmission(Document):
def validate(self):
+ validate_active_employee(self.employee)
validate_tax_declaration(self.tax_exemption_proofs)
self.set_total_actual_amount()
self.set_total_exemption_amount()
diff --git a/erpnext/payroll/doctype/retention_bonus/retention_bonus.py b/erpnext/payroll/doctype/retention_bonus/retention_bonus.py
index 049ea26..055bea7 100644
--- a/erpnext/payroll/doctype/retention_bonus/retention_bonus.py
+++ b/erpnext/payroll/doctype/retention_bonus/retention_bonus.py
@@ -7,11 +7,10 @@
from frappe.model.document import Document
from frappe import _
from frappe.utils import getdate
-
+from erpnext.hr.utils import validate_active_employee
class RetentionBonus(Document):
def validate(self):
- if frappe.get_value('Employee', self.employee, 'status') != 'Active':
- frappe.throw(_('Cannot create Retention Bonus for Left or Inactive Employees'))
+ validate_active_employee(self.employee)
if getdate(self.bonus_payment_date) < getdate():
frappe.throw(_('Bonus Payment Date cannot be a past date'))
diff --git a/erpnext/payroll/doctype/salary_component/salary_component.js b/erpnext/payroll/doctype/salary_component/salary_component.js
index dbf7514..e9e6f81 100644
--- a/erpnext/payroll/doctype/salary_component/salary_component.js
+++ b/erpnext/payroll/doctype/salary_component/salary_component.js
@@ -4,11 +4,18 @@
frappe.ui.form.on('Salary Component', {
setup: function(frm) {
frm.set_query("account", "accounts", function(doc, cdt, cdn) {
- var d = locals[cdt][cdn];
+ let d = frappe.get_doc(cdt, cdn);
+
+ let root_type = "Liability";
+ if (frm.doc.type == "Deduction") {
+ root_type = "Expense";
+ }
+
return {
filters: {
"is_group": 0,
- "company": d.company
+ "company": d.company,
+ "root_type": root_type
}
};
});
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index bead880..7e1fb06 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -19,6 +19,7 @@
from erpnext.payroll.doctype.employee_benefit_claim.employee_benefit_claim import get_benefit_claim_amount, get_last_payroll_period_benefits
from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts, create_repayment_entry
from erpnext.accounts.utils import get_fiscal_year
+from erpnext.hr.utils import validate_active_employee
from six import iteritems
class SalarySlip(TransactionBase):
@@ -39,6 +40,7 @@
def validate(self):
self.status = self.get_status()
+ validate_active_employee(self.employee)
self.validate_dates()
self.check_existing()
if not self.salary_slip_based_on_timesheet:
diff --git a/erpnext/payroll/report/bank_remittance/bank_remittance.py b/erpnext/payroll/report/bank_remittance/bank_remittance.py
index 500543c..05a5366 100644
--- a/erpnext/payroll/report/bank_remittance/bank_remittance.py
+++ b/erpnext/payroll/report/bank_remittance/bank_remittance.py
@@ -95,6 +95,7 @@
"amount": salary.net_pay,
}
data.append(row)
+
return columns, data
def get_bank_accounts():
@@ -116,7 +117,7 @@
entries = get_all("Payroll Entry", payroll_filter, ["name", "payment_account"])
payment_accounts = [d.payment_account for d in entries]
- set_company_account(payment_accounts, entries)
+ entries = set_company_account(payment_accounts, entries)
return entries
def get_salary_slips(payroll_entries):
diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py
index d77eb2c..d60b1a2 100644
--- a/erpnext/portal/product_configurator/utils.py
+++ b/erpnext/portal/product_configurator/utils.py
@@ -2,6 +2,7 @@
from frappe.utils import cint
from erpnext.portal.product_configurator.item_variants_cache import ItemVariantsCacheManager
from erpnext.shopping_cart.product_info import get_product_info_for_website
+from erpnext.setup.doctype.item_group.item_group import get_child_groups
def get_field_filter_data():
product_settings = get_product_settings()
@@ -89,6 +90,7 @@
def get_products_html_for_website(field_filters=None, attribute_filters=None):
field_filters = frappe.parse_json(field_filters)
attribute_filters = frappe.parse_json(attribute_filters)
+ set_item_group_filters(field_filters)
items = get_products_for_website(field_filters, attribute_filters)
html = ''.join(get_html_for_items(items))
@@ -98,6 +100,10 @@
return html
+def set_item_group_filters(field_filters):
+ if field_filters is not None and 'item_group' in field_filters:
+ field_filters['item_group'] = [ig[0] for ig in get_child_groups(field_filters['item_group'])]
+
def get_item_codes_by_attributes(attribute_filters, template_item_code=None):
items = []
diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py
index c8bd80f..ae38d4c 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -15,12 +15,15 @@
WorkstationHolidayError)
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
from erpnext.setup.utils import get_exchange_rate
+from erpnext.hr.utils import validate_active_employee
class OverlapError(frappe.ValidationError): pass
class OverWorkLoggedError(frappe.ValidationError): pass
class Timesheet(Document):
def validate(self):
+ if self.employee:
+ validate_active_employee(self.employee)
self.set_employee_name()
self.set_status()
self.validate_dates()
diff --git a/erpnext/public/js/contact.js b/erpnext/public/js/contact.js
new file mode 100644
index 0000000..41a0e8a
--- /dev/null
+++ b/erpnext/public/js/contact.js
@@ -0,0 +1,16 @@
+
+
+frappe.ui.form.on("Contact", {
+ refresh(frm) {
+ frm.set_query('link_doctype', "links", function() {
+ return {
+ query: "frappe.contacts.address_and_contact.filter_dynamic_link_doctypes",
+ filters: {
+ fieldtype: ["in", ["HTML", "Text Editor"]],
+ fieldname: ["in", ["contact_html", "company_description"]],
+ }
+ };
+ });
+ frm.refresh_field("links");
+ }
+});
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index 1de9ec1..9d8fcb6 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -47,7 +47,10 @@
if (in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype) && this.frm.doc.is_pos &&
this.frm.doc.is_return) {
- this.update_paid_amount_for_return();
+ if (this.frm.doc.doctype == "Sales Invoice") {
+ this.set_total_amount_to_default_mop();
+ }
+ this.calculate_paid_amount();
}
// Sales person's commission
@@ -65,7 +68,7 @@
this.frm.refresh_fields();
},
- calculate_discount_amount: function(){
+ calculate_discount_amount: function() {
if (frappe.meta.get_docfield(this.frm.doc.doctype, "discount_amount")) {
this.set_discount_amount();
this.apply_discount_amount();
@@ -73,18 +76,15 @@
},
_calculate_taxes_and_totals: function() {
- frappe.run_serially([
- () => this.validate_conversion_rate(),
- () => this.calculate_item_values(),
- () => this.update_item_tax_map(),
- () => this.initialize_taxes(),
- () => this.determine_exclusive_rate(),
- () => this.calculate_net_total(),
- () => this.calculate_taxes(),
- () => this.manipulate_grand_total_for_inclusive_tax(),
- () => this.calculate_totals(),
- () => this._cleanup()
- ]);
+ this.validate_conversion_rate();
+ this.calculate_item_values();
+ this.initialize_taxes();
+ this.determine_exclusive_rate();
+ this.calculate_net_total();
+ this.calculate_taxes();
+ this.manipulate_grand_total_for_inclusive_tax();
+ this.calculate_totals();
+ this._cleanup();
},
validate_conversion_rate: function() {
@@ -105,7 +105,7 @@
},
calculate_item_values: function() {
- var me = this;
+ let me = this;
if (!this.discount_amount_applied) {
$.each(this.frm.doc["items"] || [], function(i, item) {
frappe.model.round_floats_in(item);
@@ -266,46 +266,6 @@
frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]);
},
- update_item_tax_map: function() {
- let me = this;
- let item_codes = [];
- let item_rates = {};
- let item_tax_templates = {};
-
- $.each(this.frm.doc.items || [], function(i, item) {
- if (item.item_code) {
- // Use combination of name and item code in case same item is added multiple times
- item_codes.push([item.item_code, item.name]);
- item_rates[item.name] = item.net_rate;
- item_tax_templates[item.name] = item.item_tax_template;
- }
- });
-
- if (item_codes.length) {
- return this.frm.call({
- method: "erpnext.stock.get_item_details.get_item_tax_info",
- args: {
- company: me.frm.doc.company,
- tax_category: cstr(me.frm.doc.tax_category),
- item_codes: item_codes,
- item_rates: item_rates,
- item_tax_templates: item_tax_templates
- },
- callback: function(r) {
- if (!r.exc) {
- $.each(me.frm.doc.items || [], function(i, item) {
- if (item.name && r.message.hasOwnProperty(item.name) && r.message[item.name].item_tax_template) {
- item.item_tax_template = r.message[item.name].item_tax_template;
- item.item_tax_rate = r.message[item.name].item_tax_rate;
- me.add_taxes_from_item_tax_template(item.item_tax_rate);
- }
- });
- }
- }
- });
- }
- },
-
add_taxes_from_item_tax_template: function(item_tax_map) {
let me = this;
@@ -630,8 +590,6 @@
tax.item_wise_tax_detail = JSON.stringify(tax.item_wise_tax_detail);
});
}
-
- this.frm.refresh_fields();
},
set_discount_amount: function() {
@@ -775,7 +733,7 @@
}
},
- update_paid_amount_for_return: function() {
+ set_total_amount_to_default_mop: function() {
var grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total;
if(this.frm.doc.party_account_currency == this.frm.doc.currency) {
@@ -788,17 +746,14 @@
precision("base_grand_total")
);
}
-
this.frm.doc.payments.find(pay => {
if (pay.default) {
pay.amount = total_amount_to_pay;
} else {
- pay.amount = 0.0
+ pay.amount = 0.0;
}
});
this.frm.refresh_fields();
-
- this.calculate_paid_amount();
},
set_default_payment: function(total_amount_to_pay, update_paid_amount) {
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index b3af3d6..b9fa9b7 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -732,7 +732,7 @@
this.frm.trigger("item_code", cdt, cdn);
}
else {
- // Replacing all occurences of comma with carriage return
+ // Replace all occurences of comma with line feed
item.serial_no = item.serial_no.replace(/,/g, '\n');
item.conversion_factor = item.conversion_factor || 1;
refresh_field("serial_no", item.name, item.parentfield);
@@ -826,9 +826,9 @@
frappe.run_serially([
() => me.frm.script_manager.trigger("currency"),
+ () => me.update_item_tax_map(),
() => me.apply_default_taxes(),
- () => me.apply_pricing_rule(),
- () => me.calculate_taxes_and_totals()
+ () => me.apply_pricing_rule()
]);
}
}
@@ -1787,6 +1787,46 @@
]);
},
+ update_item_tax_map: function() {
+ let me = this;
+ let item_codes = [];
+ let item_rates = {};
+ let item_tax_templates = {};
+
+ $.each(this.frm.doc.items || [], function(i, item) {
+ if (item.item_code) {
+ // Use combination of name and item code in case same item is added multiple times
+ item_codes.push([item.item_code, item.name]);
+ item_rates[item.name] = item.net_rate;
+ item_tax_templates[item.name] = item.item_tax_template;
+ }
+ });
+
+ if (item_codes.length) {
+ return this.frm.call({
+ method: "erpnext.stock.get_item_details.get_item_tax_info",
+ args: {
+ company: me.frm.doc.company,
+ tax_category: cstr(me.frm.doc.tax_category),
+ item_codes: item_codes,
+ item_rates: item_rates,
+ item_tax_templates: item_tax_templates
+ },
+ callback: function(r) {
+ if (!r.exc) {
+ $.each(me.frm.doc.items || [], function(i, item) {
+ if (item.name && r.message.hasOwnProperty(item.name) && r.message[item.name].item_tax_template) {
+ item.item_tax_template = r.message[item.name].item_tax_template;
+ item.item_tax_rate = r.message[item.name].item_tax_rate;
+ me.add_taxes_from_item_tax_template(item.item_tax_rate);
+ }
+ });
+ }
+ }
+ });
+ }
+ },
+
item_tax_template: function(doc, cdt, cdn) {
var me = this;
if(me.frm.updating_party_details) return;
diff --git a/erpnext/public/js/help_links.js b/erpnext/public/js/help_links.js
index aa9bba1..d0c935f 100644
--- a/erpnext/public/js/help_links.js
+++ b/erpnext/public/js/help_links.js
@@ -54,7 +54,7 @@
frappe.help.help_links["Form/System Settings"] = [
{
- label: "Naming Series",
+ label: "System Settings",
url: docsUrl + "user/manual/en/setting-up/settings/system-settings",
},
];
@@ -206,7 +206,7 @@
label: "PayPal Settings",
url:
docsUrl +
- "user/manual/en/setting-up/integrations/paypal-integration",
+ "user/manual/en/erpnext_integration/paypal-integration",
},
];
@@ -215,14 +215,14 @@
label: "Razorpay Settings",
url:
docsUrl +
- "user/manual/en/setting-up/integrations/razorpay-integration",
+ "user/manual/en/erpnext_integration/razorpay-integration",
},
];
frappe.help.help_links["Form/Dropbox Settings"] = [
{
label: "Dropbox Settings",
- url: docsUrl + "user/manual/en/setting-up/integrations/dropbox-backup",
+ url: docsUrl + "user/manual/en/erpnext_integration/dropbox-backup",
},
];
@@ -230,7 +230,7 @@
{
label: "LDAP Settings",
url:
- docsUrl + "user/manual/en/setting-up/integrations/ldap-integration",
+ docsUrl + "user/manual/en/erpnext_integration/ldap-integration",
},
];
@@ -239,7 +239,7 @@
label: "Stripe Settings",
url:
docsUrl +
- "user/manual/en/setting-up/integrations/stripe-integration",
+ "user/manual/en/erpnext_integration/stripe-integration",
},
];
@@ -991,7 +991,7 @@
label: "Nested BOM Structure",
url:
docsUrl +
- "user/manual/en/manufacturing/articles/nested-bom-structure",
+ "user/manual/en/manufacturing/articles/managing-multi-level-bom",
},
];
diff --git a/erpnext/public/js/utils/customer_quick_entry.js b/erpnext/public/js/utils/customer_quick_entry.js
index ebe6cd9..7bd21df 100644
--- a/erpnext/public/js/utils/customer_quick_entry.js
+++ b/erpnext/public/js/utils/customer_quick_entry.js
@@ -1,9 +1,9 @@
frappe.provide('frappe.ui.form');
frappe.ui.form.CustomerQuickEntryForm = frappe.ui.form.QuickEntryForm.extend({
- init: function(doctype, after_insert) {
+ init: function(doctype, after_insert, init_callback, doc, force) {
+ this._super(doctype, after_insert, init_callback, doc, force);
this.skip_redirect_on_error = true;
- this._super(doctype, after_insert);
},
render_dialog: function() {
diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js
index a79eadc..4d432e3 100644
--- a/erpnext/public/js/utils/party.js
+++ b/erpnext/public/js/utils/party.js
@@ -76,6 +76,7 @@
if (args) {
args.posting_date = frm.doc.posting_date || frm.doc.transaction_date;
+ args.fetch_payment_terms_template = cint(!frm.doc.ignore_default_payment_terms_template);
}
}
if (!args || !args.party) return;
diff --git a/erpnext/regional/address_template/templates/france.html b/erpnext/regional/address_template/templates/france.html
new file mode 100644
index 0000000..752331e
--- /dev/null
+++ b/erpnext/regional/address_template/templates/france.html
@@ -0,0 +1,5 @@
+{% if address_line1 %}{{ address_line1 }}{% endif -%}
+{% if address_line2 %}<br>{{ address_line2 }}{% endif -%}
+{% if pincode %}<br>{{ pincode }}{% endif -%}
+{% if city %} {{ city }}{% endif -%}
+{% if country %}<br>{{ country }}{% endif -%}
diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js
index cc2d9f0..54e4886 100644
--- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js
+++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js
@@ -3,7 +3,7 @@
frappe.ui.form.on('E Invoice Settings', {
refresh(frm) {
- const docs_link = 'https://docs.erpnext.com/docs/user/manual/en/regional/india/setup-e-invoicing';
+ const docs_link = 'https://docs.erpnext.com/docs/v13/user/manual/en/regional/india/setup-e-invoicing';
frm.dashboard.set_headline(
__("Read {0} for more information on E Invoicing features.", [`<a href='${docs_link}'>documentation</a>`])
);
diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
index 6415204..0ee5b09 100644
--- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
@@ -214,9 +214,8 @@
for d in item_details:
if d.item_code not in self.invoice_items.get(d.parent, {}):
- self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code,
- sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in item_details
- if i.item_code == d.item_code and i.parent == d.parent))
+ self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0)
+ self.invoice_items[d.parent][d.item_code] += d.get('taxable_value', 0) or d.get('base_net_amount', 0)
if d.is_nil_exempt and d.item_code not in self.is_nil_exempt:
self.is_nil_exempt.append(d.item_code)
@@ -281,9 +280,15 @@
if self.get('invoice_items'):
# Build itemised tax for export invoices, nil and exempted where tax table is blank
for invoice, items in iteritems(self.invoice_items):
- if invoice not in self.items_based_on_tax_rate and (self.invoice_detail_map.get(invoice, {}).get('export_type')
- == "Without Payment of Tax"):
+ if invoice not in self.items_based_on_tax_rate and self.invoice_detail_map.get(invoice, {}).get('export_type') \
+ == "Without Payment of Tax" and self.invoice_detail_map.get(invoice, {}).get('gst_category') == "Overseas":
self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys())
+ else:
+ for item in items.keys():
+ if item in self.is_nil_exempt + self.is_non_gst and \
+ item not in self.items_based_on_tax_rate.get(invoice, {}).get(0, []):
+ self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, [])
+ self.items_based_on_tax_rate[invoice][0].append(item)
def set_outward_taxable_supplies(self):
inter_state_supply_details = {}
@@ -322,6 +327,9 @@
inter_state_supply_details[(gst_category, place_of_supply)]['txval'] += taxable_value
inter_state_supply_details[(gst_category, place_of_supply)]['iamt'] += (taxable_value * rate /100)
+ if self.invoice_cess.get(inv):
+ self.report_dict['sup_details']['osup_det']['csamt'] += flt(self.invoice_cess.get(inv), 2)
+
self.set_inter_state_supply(inter_state_supply_details)
def set_supplies_liable_to_reverse_charge(self):
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
index ea600d9..e65442d 100644
--- a/erpnext/regional/india/e_invoice/utils.py
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -316,10 +316,6 @@
))
def get_return_doc_reference(invoice):
- if not invoice.return_against:
- frappe.throw(_('For generating IRN, reference to the original invoice is mandatory for a credit note. Please set {} field to generate e-invoice.')
- .format(frappe.bold('Return Against')), title=_('Missing Field'))
-
invoice_date = frappe.db.get_value('Sales Invoice', invoice.return_against, 'posting_date')
return frappe._dict(dict(
invoice_name=invoice.return_against, invoice_date=format_date(invoice_date, 'dd/mm/yyyy')
@@ -435,7 +431,7 @@
if invoice.is_pos and invoice.base_paid_amount:
payment_details = get_payment_details(invoice)
- if invoice.is_return:
+ if invoice.is_return and invoice.return_against:
prev_doc_details = get_return_doc_reference(invoice)
if invoice.transporter and not invoice.is_return:
@@ -966,7 +962,7 @@
"attached_to_doctype": doctype,
"attached_to_name": docname,
"attached_to_field": "qrcode_image",
- "is_private": 1,
+ "is_private": 0,
"content": qr_image.getvalue()})
_file.save()
frappe.db.commit()
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index 5f9d5ed..e9372f9 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -12,7 +12,10 @@
from frappe.utils import today
def setup(company=None, patch=True):
- setup_company_independent_fixtures(patch=patch)
+ # Company independent fixtures should be called only once at the first company setup
+ if frappe.db.count('Company', {'country': 'India'}) <=1:
+ setup_company_independent_fixtures(patch=patch)
+
if not patch:
make_fixtures(company)
@@ -122,10 +125,12 @@
def make_property_setters(patch=False):
# GST rules do not allow for an invoice no. bigger than 16 characters
journal_entry_types = frappe.get_meta("Journal Entry").get_options("voucher_type").split("\n") + ['Reversal Of ITC']
+ sales_invoice_series = ['SINV-.YY.-', 'SRET-.YY.-', ''] + frappe.get_meta("Sales Invoice").get_options("naming_series").split("\n")
+ purchase_invoice_series = ['PINV-.YY.-', 'PRET-.YY.-', ''] + frappe.get_meta("Purchase Invoice").get_options("naming_series").split("\n")
if not patch:
- make_property_setter('Sales Invoice', 'naming_series', 'options', 'SINV-.YY.-\nSRET-.YY.-', '')
- make_property_setter('Purchase Invoice', 'naming_series', 'options', 'PINV-.YY.-\nPRET-.YY.-', '')
+ make_property_setter('Sales Invoice', 'naming_series', 'options', '\n'.join(sales_invoice_series), '')
+ make_property_setter('Purchase Invoice', 'naming_series', 'options', '\n'.join(purchase_invoice_series), '')
make_property_setter('Journal Entry', 'voucher_type', 'options', '\n'.join(journal_entry_types), '')
def make_custom_fields(update=True):
@@ -636,7 +641,6 @@
'label': 'Export Type',
'fieldtype': 'Select',
'insert_after': 'gst_category',
- 'default': 'Without Payment of Tax',
'depends_on':'eval:in_list(["SEZ", "Overseas"], doc.gst_category)',
'options': '\nWith Payment of Tax\nWithout Payment of Tax'
}
@@ -655,7 +659,6 @@
'label': 'Export Type',
'fieldtype': 'Select',
'insert_after': 'gst_category',
- 'default': 'Without Payment of Tax',
'depends_on':'eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)',
'options': '\nWith Payment of Tax\nWithout Payment of Tax'
}
@@ -786,7 +789,7 @@
doc.flags.ignore_mandatory = True
doc.insert()
else:
- doc = frappe.get_doc("Tax Withholding Category", d.get("name"))
+ doc = frappe.get_doc("Tax Withholding Category", d.get("name"), for_update=True)
if accounts:
doc.append("accounts", accounts[0])
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index a4466e7..a152797 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -431,9 +431,11 @@
company_address = frappe.get_doc('Address', doc.company_address)
billing_address = frappe.get_doc('Address', doc.customer_address)
+ #added dispatch address
+ dispatch_address = frappe.get_doc('Address', doc.dispatch_address_name) if doc.dispatch_address_name else company_address
shipping_address = frappe.get_doc('Address', doc.shipping_address_name)
- data = get_address_details(data, doc, company_address, billing_address)
+ data = get_address_details(data, doc, company_address, billing_address, dispatch_address)
data.itemList = []
data.totalValue = doc.total
@@ -519,10 +521,10 @@
`tabDynamic Link`.link_name = %(company)s""", {"company": company})
return company_gstins
-def get_address_details(data, doc, company_address, billing_address):
+def get_address_details(data, doc, company_address, billing_address, dispatch_address):
data.fromPincode = validate_pincode(company_address.pincode, 'Company Address')
- data.fromStateCode = data.actualFromStateCode = validate_state_code(
- company_address.gst_state_number, 'Company Address')
+ data.fromStateCode = validate_state_code(company_address.gst_state_number, 'Company Address')
+ data.actualFromStateCode = validate_state_code(dispatch_address.gst_state_number, 'Dispatch Address')
if not doc.billing_address_gstin or len(doc.billing_address_gstin) < 15:
data.toGstin = 'URP'
@@ -834,14 +836,22 @@
depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked)
if row.depreciation_method in ("Straight Line", "Manual"):
- depreciation_amount = (flt(row.value_after_depreciation) -
- flt(row.expected_value_after_useful_life)) / depreciation_left
+ # if the Depreciation Schedule is being prepared for the first time
+ if not asset.flags.increase_in_asset_life:
+ depreciation_amount = (flt(row.value_after_depreciation) -
+ flt(row.expected_value_after_useful_life)) / depreciation_left
+
+ # if the Depreciation Schedule is being modified after Asset Repair
+ else:
+ depreciation_amount = (flt(row.value_after_depreciation) -
+ flt(row.expected_value_after_useful_life)) / (date_diff(asset.to_date, asset.available_for_use_date) / 365)
+
else:
rate_of_depreciation = row.rate_of_depreciation
# if its the first depreciation
if depreciable_value == asset.gross_purchase_amount:
# as per IT act, if the asset is purchased in the 2nd half of fiscal year, then rate is divided by 2
- diff = date_diff(asset.available_for_use_date, row.depreciation_start_date)
+ diff = date_diff(row.depreciation_start_date, asset.available_for_use_date)
if diff <= 180:
rate_of_depreciation = rate_of_depreciation / 2
frappe.msgprint(
@@ -849,4 +859,15 @@
depreciation_amount = flt(depreciable_value * (flt(rate_of_depreciation) / 100))
- return depreciation_amount
\ No newline at end of file
+ return depreciation_amount
+
+def set_item_tax_from_hsn_code(item):
+ if not item.taxes and item.gst_hsn_code:
+ hsn_doc = frappe.get_doc("GST HSN Code", item.gst_hsn_code)
+
+ for tax in hsn_doc.taxes:
+ item.append('taxes', {
+ 'item_tax_template': tax.item_tax_template,
+ 'tax_category': tax.tax_category,
+ 'valid_from': tax.valid_from
+ })
\ No newline at end of file
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index 1096159..4b73094 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -217,9 +217,8 @@
for d in items:
if d.item_code not in self.invoice_items.get(d.parent, {}):
- self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code,
- sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in items
- if i.item_code == d.item_code and i.parent == d.parent))
+ self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0)
+ self.invoice_items[d.parent][d.item_code] += d.get('taxable_value', 0) or d.get('base_net_amount', 0)
item_tax_rate = {}
@@ -287,7 +286,8 @@
# Build itemised tax for export invoices where tax table is blank
for invoice, items in iteritems(self.invoice_items):
if invoice not in self.items_based_on_tax_rate and invoice not in unidentified_gst_accounts_invoice \
- and frappe.db.get_value(self.doctype, invoice, "export_type") == "Without Payment of Tax":
+ and self.invoices.get(invoice, {}).get('export_type') == "Without Payment of Tax" \
+ and self.invoices.get(invoice, {}).get('gst_category') == "Overseas":
self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys())
def get_columns(self):
@@ -584,7 +584,7 @@
def get_json(filters, report_name, data):
filters = json.loads(filters)
report_data = json.loads(data)
- gstin = get_company_gstin_number(filters["company"], filters["company_address"])
+ gstin = get_company_gstin_number(filters.get("company"), filters.get("company_address"))
fp = "%02d%s" % (getdate(filters["to_date"]).month, getdate(filters["to_date"]).year)
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 818888c..9785f6c 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -134,9 +134,7 @@
'''If Customer created from Lead, update lead status to "Converted"
update Customer link in Quotation, Opportunity'''
if self.lead_name:
- lead = frappe.get_doc('Lead', self.lead_name)
- lead.status = 'Converted'
- lead.save()
+ frappe.db.set_value("Lead", self.lead_name, "status", "Converted")
def create_lead_address_contact(self):
if self.lead_name:
diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json
index 762b6f1..d31db82 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.json
+++ b/erpnext/selling/doctype/sales_order/sales_order.json
@@ -38,6 +38,8 @@
"col_break46",
"shipping_address_name",
"shipping_address",
+ "dispatch_address_name",
+ "dispatch_address",
"customer_group",
"territory",
"currency_and_price_list",
@@ -1486,13 +1488,29 @@
"fieldname": "disable_rounded_total",
"fieldtype": "Check",
"label": "Disable Rounded Total"
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "dispatch_address_name",
+ "fieldtype": "Link",
+ "label": "Dispatch Address Name",
+ "options": "Address",
+ "print_hide": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "depends_on": "dispatch_address_name",
+ "fieldname": "dispatch_address",
+ "fieldtype": "Small Text",
+ "label": "Dispatch Address",
+ "read_only": 1
}
],
"icon": "fa fa-file-text",
"idx": 105,
"is_submittable": 1,
"links": [],
- "modified": "2021-04-15 23:55:13.439068",
+ "modified": "2021-07-08 21:37:44.177493",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order",
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 41f57a3..bba5401 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -670,6 +670,7 @@
"party_account_currency": "party_account_currency",
"payment_terms_template": "payment_terms_template"
},
+ "field_no_map": ["payment_terms_template"],
"validation": {
"docstatus": ["=", 1]
}
@@ -693,6 +694,10 @@
}
}, target_doc, postprocess, ignore_permissions=ignore_permissions)
+ automatically_fetch_payment_terms = cint(frappe.db.get_single_value('Accounts Settings', 'automatically_fetch_payment_terms'))
+ if automatically_fetch_payment_terms:
+ doclist.set_payment_schedule()
+
return doclist
@frappe.whitelist()
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index 974648d..a226da7 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -5,7 +5,7 @@
import unittest
import frappe
import frappe.permissions
-from frappe.utils import flt, add_days, nowdate
+from frappe.utils import flt, add_days, nowdate, getdate
from frappe.core.doctype.user_permission.test_user_permission import create_user
from erpnext.selling.doctype.sales_order.sales_order \
import make_material_request, make_delivery_note, make_sales_invoice, WarehouseRequired
@@ -1229,7 +1229,38 @@
self.assertRaises(frappe.ValidationError, so.cancel)
+ def test_payment_terms_are_fetched_when_creating_sales_invoice(self):
+ from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template
+ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
+ automatically_fetch_payment_terms()
+
+ so = make_sales_order(uom="Nos", do_not_save=1)
+ create_payment_terms_template()
+ so.payment_terms_template = 'Test Receivable Template'
+ so.submit()
+
+ si = create_sales_invoice(qty=10, do_not_save=1)
+ si.items[0].sales_order = so.name
+ si.items[0].so_detail = so.items[0].name
+ si.insert()
+
+ self.assertEqual(so.payment_terms_template, si.payment_terms_template)
+ compare_payment_schedules(self, so, si)
+
+ automatically_fetch_payment_terms(enable=0)
+
+def automatically_fetch_payment_terms(enable=1):
+ accounts_settings = frappe.get_doc("Accounts Settings")
+ accounts_settings.automatically_fetch_payment_terms = enable
+ accounts_settings.save()
+
+def compare_payment_schedules(doc, doc1, doc2):
+ for index, schedule in enumerate(doc1.get('payment_schedule')):
+ doc.assertEqual(schedule.payment_term, doc2.payment_schedule[index].payment_term)
+ doc.assertEqual(getdate(schedule.due_date), doc2.payment_schedule[index].due_date)
+ doc.assertEqual(schedule.invoice_portion, doc2.payment_schedule[index].invoice_portion)
+ doc.assertEqual(schedule.payment_amount, doc2.payment_schedule[index].payment_amount)
def make_sales_order(**args):
so = frappe.new_doc("Sales Order")
diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js
index 38508c2..a4a4b0e 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_cart.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js
@@ -367,15 +367,16 @@
`<div class="add-discount-field"></div>`
);
const me = this;
+ const frm = me.events.get_frm();
+ let discount = frm.doc.additional_discount_percentage;
this.discount_field = frappe.ui.form.make_control({
df: {
label: __('Discount'),
fieldtype: 'Data',
- placeholder: __('Enter discount percentage.'),
+ placeholder: ( discount ? discount + '%' : __('Enter discount percentage.') ),
input_class: 'input-xs',
onchange: function() {
- const frm = me.events.get_frm();
if (flt(this.value) != 0) {
frappe.model.set_value(frm.doc.doctype, frm.doc.name, 'additional_discount_percentage', flt(this.value));
me.hide_discount_control(this.value);
@@ -563,7 +564,6 @@
)
set_dynamic_rate_header_width();
- this.scroll_to_item($item_to_update);
function set_dynamic_rate_header_width() {
const rate_cols = Array.from(me.$cart_items_wrapper.find(".item-rate-amount"));
@@ -638,12 +638,6 @@
$($img).parent().replaceWith(`<div class="item-image item-abbr">${item_abbr}</div>`);
}
- scroll_to_item($item) {
- if ($item.length === 0) return;
- const scrollTop = $item.offset().top - this.$cart_items_wrapper.offset().top + this.$cart_items_wrapper.scrollTop();
- this.$cart_items_wrapper.animate({ scrollTop });
- }
-
update_selector_value_in_cart_item(selector, value, item) {
const $item_to_update = this.get_cart_item(item);
$item_to_update.attr(`data-${selector}`, escape(value));
@@ -965,8 +959,23 @@
});
}
+ attach_refresh_field_event(frm) {
+ $(frm.wrapper).off('refresh-fields');
+ $(frm.wrapper).on('refresh-fields', () => {
+ if (frm.doc.items.length) {
+ frm.doc.items.forEach(item => {
+ this.update_item_html(item);
+ });
+ }
+ this.update_totals_section(frm);
+ });
+ }
+
load_invoice() {
const frm = this.events.get_frm();
+
+ this.attach_refresh_field_event(frm);
+
this.fetch_customer_details(frm.doc.customer).then(() => {
this.events.customer_details_updated(this.customer_info);
this.update_customer_section();
diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js
index f1a166b..63306ad 100644
--- a/erpnext/selling/page/point_of_sale/pos_payment.js
+++ b/erpnext/selling/page/point_of_sale/pos_payment.js
@@ -198,6 +198,7 @@
const is_cash_shortcuts_invisible = !this.$payment_modes.find('.cash-shortcuts').is(':visible');
this.attach_cash_shortcuts(frm.doc);
!is_cash_shortcuts_invisible && this.$payment_modes.find('.cash-shortcuts').css('display', 'grid');
+ this.render_payment_mode_dom();
});
frappe.ui.form.on('POS Invoice', 'loyalty_amount', (frm) => {
diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json
index 061986d..e6ec496 100644
--- a/erpnext/setup/doctype/company/company.json
+++ b/erpnext/setup/doctype/company/company.json
@@ -74,7 +74,7 @@
"stock_received_but_not_billed",
"service_received_but_not_billed",
"expenses_included_in_valuation",
- "fixed_asset_depreciation_settings",
+ "fixed_asset_defaults",
"accumulated_depreciation_account",
"depreciation_expense_account",
"series_for_depreciation_entry",
@@ -83,6 +83,7 @@
"disposal_account",
"depreciation_cost_center",
"capital_work_in_progress_account",
+ "repair_and_maintenance_account",
"asset_received_but_not_billed",
"budget_detail",
"exception_budget_approver_role",
@@ -520,12 +521,6 @@
"options": "Account"
},
{
- "collapsible": 1,
- "fieldname": "fixed_asset_depreciation_settings",
- "fieldtype": "Section Break",
- "label": "Fixed Asset Depreciation Settings"
- },
- {
"fieldname": "accumulated_depreciation_account",
"fieldtype": "Link",
"label": "Accumulated Depreciation Account",
@@ -734,6 +729,18 @@
"fieldtype": "Link",
"label": "Default Payment Discount Account",
"options": "Account"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "fixed_asset_defaults",
+ "fieldtype": "Section Break",
+ "label": "Fixed Asset Defaults"
+ },
+ {
+ "fieldname": "repair_and_maintenance_account",
+ "fieldtype": "Link",
+ "label": "Repair and Maintenance Account",
+ "options": "Account"
}
],
"icon": "fa fa-building",
@@ -741,7 +748,7 @@
"image_field": "company_logo",
"is_tree": 1,
"links": [],
- "modified": "2021-05-07 03:11:28.189740",
+ "modified": "2021-05-12 16:51:08.187233",
"modified_by": "Administrator",
"module": "Setup",
"name": "Company",
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 36a7d20..8755125 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -291,7 +291,7 @@
cash = frappe.db.get_value('Mode of Payment', {'type': 'Cash'}, 'name')
if cash and self.default_cash_account \
and not frappe.db.get_value('Mode of Payment Account', {'company': self.name, 'parent': cash}):
- mode_of_payment = frappe.get_doc('Mode of Payment', cash)
+ mode_of_payment = frappe.get_doc('Mode of Payment', cash, for_update=True)
mode_of_payment.append('accounts', {
'company': self.name,
'default_account': self.default_cash_account
diff --git a/erpnext/setup/doctype/email_digest/email_digest.js b/erpnext/setup/doctype/email_digest/email_digest.js
index 1071ea2..2e415af 100644
--- a/erpnext/setup/doctype/email_digest/email_digest.js
+++ b/erpnext/setup/doctype/email_digest/email_digest.js
@@ -1,78 +1,31 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
-cur_frm.cscript.refresh = function(doc, dt, dn) {
- doc = locals[dt][dn];
- cur_frm.add_custom_button(__('View Now'), function() {
- frappe.call({
- method: 'erpnext.setup.doctype.email_digest.email_digest.get_digest_msg',
- args: {
- name: doc.name
- },
- callback: function(r) {
- var d = new frappe.ui.Dialog({
- title: __('Email Digest: ') + dn,
- width: 800
+frappe.ui.form.on("Email Digest", {
+ refresh: function(frm) {
+ if (!frm.is_new()) {
+ frm.add_custom_button(__('View Now'), function() {
+ frappe.call({
+ method: 'erpnext.setup.doctype.email_digest.email_digest.get_digest_msg',
+ args: {
+ name: frm.doc.name
+ },
+ callback: function(r) {
+ let d = new frappe.ui.Dialog({
+ title: __('Email Digest: {0}', [frm.doc.name]),
+ width: 800
+ });
+ $(d.body).html(r.message);
+ d.show();
+ }
});
- $(d.body).html(r.message);
- d.show();
- }
- });
- }, "fa fa-eye-open", "btn-default");
-
- if (!cur_frm.is_new()) {
- cur_frm.add_custom_button(__('Send Now'), function() {
- return cur_frm.call('send', null, (r) => {
- frappe.show_alert(__('Message Sent'));
});
- });
+
+ frm.add_custom_button(__('Send Now'), function() {
+ return frm.call('send', null, () => {
+ frappe.show_alert({ message: __("Message Sent"), indicator: 'green'});
+ });
+ });
+ }
}
-};
-
-cur_frm.cscript.addremove_recipients = function(doc, dt, dn) {
- // Get user list
-
- return cur_frm.call('get_users', null, function(r) {
- // Open a dialog and display checkboxes against email addresses
- doc = locals[dt][dn];
- var d = new frappe.ui.Dialog({
- title: __('Add/Remove Recipients'),
- width: 400
- });
-
- $.each(r.user_list, function(i, v) {
- var fullname = frappe.user.full_name(v.name);
- if(fullname !== v.name) fullname = fullname + " <" + v.name + ">";
-
- if(v.enabled==0) {
- fullname = repl("<span style='color: red'> %(name)s (" + __("disabled user") + ")</span>", {name: v.name});
- }
-
- $('<div class="checkbox"><label>\
- <input type="checkbox" data-id="' + v.name + '"'+
- (v.checked ? 'checked' : '') +
- '> '+ fullname +'</label></div>').appendTo(d.body);
- });
-
- // Display add recipients button
- d.set_primary_action("Update", function() {
- cur_frm.cscript.add_to_rec_list(doc, d.body, r.user_list.length);
- });
-
- cur_frm.rec_dialog = d;
- d.show();
- });
-}
-
-cur_frm.cscript.add_to_rec_list = function(doc, dialog, length) {
- // add checked users to list of recipients
- var rec_list = [];
- $(dialog).find('input:checked').each(function(i, input) {
- rec_list.push($(input).attr('data-id'));
- });
-
- doc.recipient_list = rec_list.join('\n');
- cur_frm.rec_dialog.hide();
- cur_frm.save();
- cur_frm.refresh_fields();
-}
+});
\ No newline at end of file
diff --git a/erpnext/setup/doctype/email_digest/email_digest.json b/erpnext/setup/doctype/email_digest/email_digest.json
index 125aca1..06c98e5 100644
--- a/erpnext/setup/doctype/email_digest/email_digest.json
+++ b/erpnext/setup/doctype/email_digest/email_digest.json
@@ -1,1482 +1,338 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "Prompt",
- "beta": 0,
- "creation": "2018-09-16 22:00:00",
- "custom": 0,
- "description": "Send regular summary reports via Email.",
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "System",
- "editable_grid": 0,
+ "actions": [],
+ "autoname": "Prompt",
+ "creation": "2018-09-16 22:00:00",
+ "description": "Send regular summary reports via Email.",
+ "doctype": "DocType",
+ "document_type": "System",
+ "engine": "InnoDB",
+ "field_order": [
+ "settings",
+ "column_break0",
+ "enabled",
+ "company",
+ "frequency",
+ "next_send",
+ "column_break1",
+ "recipients",
+ "accounts",
+ "accounts_module",
+ "income",
+ "expenses_booked",
+ "income_year_to_date",
+ "expense_year_to_date",
+ "column_break_16",
+ "bank_balance",
+ "credit_balance",
+ "invoiced_amount",
+ "payables",
+ "work_in_progress",
+ "sales_orders_to_bill",
+ "purchase_orders_to_bill",
+ "operation",
+ "column_break_21",
+ "sales_order",
+ "purchase_order",
+ "sales_orders_to_deliver",
+ "purchase_orders_to_receive",
+ "sales_invoice",
+ "purchase_invoice",
+ "column_break_operation",
+ "new_quotations",
+ "pending_quotations",
+ "issue",
+ "project",
+ "purchase_orders_items_overdue",
+ "other",
+ "tools",
+ "calendar_events",
+ "todo_list",
+ "notifications",
+ "column_break_32",
+ "add_quote"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "settings",
- "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": "Email Digest Settings",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "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
- },
+ "fieldname": "settings",
+ "fieldtype": "Section Break",
+ "label": "Email Digest Settings"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break0",
- "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,
- "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
- },
+ "fieldname": "column_break0",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "enabled",
- "fieldtype": "Check",
- "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": "Enabled",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "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
- },
+ "default": "0",
+ "fieldname": "enabled",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Enabled"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "company",
- "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": "For Company",
- "length": 0,
- "no_copy": 0,
- "options": "Company",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 1,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "For Company",
+ "options": "Company",
+ "remember_last_selected_value": 1,
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "frequency",
- "fieldtype": "Select",
- "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": "How frequently?",
- "length": 0,
- "no_copy": 0,
- "options": "Daily\nWeekly\nMonthly",
- "permlevel": 0,
- "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
- },
+ "fieldname": "frequency",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "How frequently?",
+ "options": "Daily\nWeekly\nMonthly",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.enabled",
- "fieldname": "next_send",
- "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": "Next email will be sent on:",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "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
- },
+ "depends_on": "eval:doc.enabled",
+ "fieldname": "next_send",
+ "fieldtype": "Data",
+ "label": "Next email will be sent on:",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break1",
- "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,
- "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
- },
+ "fieldname": "column_break1",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "Note: Email will not be sent to disabled users",
- "fieldname": "recipient_list",
- "fieldtype": "Code",
- "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": "Recipients",
- "length": 0,
- "no_copy": 0,
- "options": "Email",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "accounts",
+ "fieldtype": "Section Break",
+ "label": "Accounts"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "addremove_recipients",
- "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": "Add/Remove Recipients",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "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
- },
+ "fieldname": "accounts_module",
+ "fieldtype": "Column Break",
+ "hidden": 1,
+ "label": "Profit & Loss"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "accounts",
- "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": "Accounts",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "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
- },
+ "default": "0",
+ "fieldname": "income",
+ "fieldtype": "Check",
+ "label": "New Income"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "accounts_module",
- "fieldtype": "Column Break",
- "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": "Profit & Loss",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "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
- },
+ "default": "0",
+ "fieldname": "expenses_booked",
+ "fieldtype": "Check",
+ "label": "New Expenses"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "",
- "fieldname": "income",
- "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": "New Income",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "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
- },
+ "default": "0",
+ "fieldname": "income_year_to_date",
+ "fieldtype": "Check",
+ "label": "Annual Income"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "",
- "fieldname": "expenses_booked",
- "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": "New Expenses",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "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
- },
+ "default": "0",
+ "fieldname": "expense_year_to_date",
+ "fieldtype": "Check",
+ "label": "Annual Expenses"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "income_year_to_date",
- "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": "Annual Income",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "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
- },
+ "fieldname": "column_break_16",
+ "fieldtype": "Column Break",
+ "label": "Balance Sheet"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "expense_year_to_date",
- "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": "Annual Expenses",
- "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
- },
+ "default": "0",
+ "fieldname": "bank_balance",
+ "fieldtype": "Check",
+ "label": "Bank Balance"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "",
- "fieldname": "column_break_16",
- "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,
- "label": "Balance Sheet",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "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
- },
+ "default": "0",
+ "fieldname": "credit_balance",
+ "fieldtype": "Check",
+ "label": "Bank Credit Balance"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "",
- "fieldname": "bank_balance",
- "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": "Bank Balance",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "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
- },
+ "default": "0",
+ "fieldname": "invoiced_amount",
+ "fieldtype": "Check",
+ "label": "Receivables"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "credit_balance",
- "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": "Bank Credit Balance",
- "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
- },
+ "default": "0",
+ "fieldname": "payables",
+ "fieldtype": "Check",
+ "label": "Payables"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "",
- "fieldname": "invoiced_amount",
- "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": "Receivables",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "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
- },
+ "fieldname": "work_in_progress",
+ "fieldtype": "Column Break",
+ "label": "Work in Progress"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "",
- "fieldname": "payables",
- "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": "Payables",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "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
- },
+ "default": "0",
+ "fieldname": "sales_orders_to_bill",
+ "fieldtype": "Check",
+ "label": "Sales Orders to Bill"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "work_in_progress",
- "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,
- "label": "Work in Progress",
- "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
- },
+ "default": "0",
+ "fieldname": "purchase_orders_to_bill",
+ "fieldtype": "Check",
+ "label": "Purchase Orders to Bill"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "sales_orders_to_bill",
- "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": "Sales Orders to Bill",
- "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
- },
+ "fieldname": "operation",
+ "fieldtype": "Section Break",
+ "label": "Operations"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "purchase_orders_to_bill",
- "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": "Purchase Orders to Bill",
- "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
- },
+ "fieldname": "column_break_21",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "operation",
- "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": "Operations",
- "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
- },
+ "default": "0",
+ "fieldname": "sales_order",
+ "fieldtype": "Check",
+ "label": "New Sales Orders"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_21",
- "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
- },
+ "default": "0",
+ "fieldname": "purchase_order",
+ "fieldtype": "Check",
+ "label": "New Purchase Orders"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "sales_order",
- "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": "New Sales Orders",
- "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
- },
+ "default": "0",
+ "fieldname": "sales_orders_to_deliver",
+ "fieldtype": "Check",
+ "label": "Sales Orders to Deliver"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "purchase_order",
- "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": "New Purchase Orders",
- "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
- },
+ "default": "0",
+ "fieldname": "purchase_orders_to_receive",
+ "fieldtype": "Check",
+ "label": "Purchase Orders to Receive"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "sales_orders_to_deliver",
- "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": "Sales Orders to Deliver",
- "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
- },
+ "default": "0",
+ "fieldname": "sales_invoice",
+ "fieldtype": "Check",
+ "label": "New Sales Invoice"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "purchase_orders_to_receive",
- "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": "Purchase Orders to Receive",
- "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
- },
+ "default": "0",
+ "fieldname": "purchase_invoice",
+ "fieldtype": "Check",
+ "label": "New Purchase Invoice"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "sales_invoice",
- "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": "New Sales Invoice",
- "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
- },
+ "fieldname": "column_break_operation",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "purchase_invoice",
- "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": "New Purchase Invoice",
- "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
- },
+ "default": "0",
+ "fieldname": "new_quotations",
+ "fieldtype": "Check",
+ "label": "New Quotations"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_operation",
- "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,
- "label": "",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "pending_quotations",
+ "fieldtype": "Check",
+ "label": "Open Quotations"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "new_quotations",
- "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": "New Quotations",
- "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
- },
+ "default": "0",
+ "fieldname": "issue",
+ "fieldtype": "Check",
+ "label": "Open Issues"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "pending_quotations",
- "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": "Open Quotations",
- "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
- },
+ "default": "0",
+ "fieldname": "project",
+ "fieldtype": "Check",
+ "label": "Open Projects"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "issue",
- "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": "Open Issues",
- "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
- },
+ "default": "0",
+ "fieldname": "purchase_orders_items_overdue",
+ "fieldtype": "Check",
+ "label": "Purchase Orders Items Overdue"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "project",
- "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": "Open Projects",
- "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
- },
+ "fieldname": "other",
+ "fieldtype": "Section Break",
+ "label": "Other"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "purchase_orders_items_overdue",
- "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": "Purchase Orders Items Overdue",
- "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
- },
+ "fieldname": "tools",
+ "fieldtype": "Column Break",
+ "label": "Tools"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "other",
- "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": "Other",
- "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
- },
+ "default": "0",
+ "fieldname": "calendar_events",
+ "fieldtype": "Check",
+ "label": "Upcoming Calendar Events"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "tools",
- "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,
- "label": "Tools",
- "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
- },
+ "default": "0",
+ "fieldname": "todo_list",
+ "fieldtype": "Check",
+ "label": "Open To Do"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "calendar_events",
- "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": "Upcoming Calendar Events",
- "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
- },
+ "default": "0",
+ "fieldname": "notifications",
+ "fieldtype": "Check",
+ "label": "Open Notifications"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "todo_list",
- "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": "Open To Do",
- "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
- },
+ "fieldname": "column_break_32",
+ "fieldtype": "Column Break",
+ "label": " "
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "notifications",
- "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": "Open Notifications",
- "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
- },
+ "default": "0",
+ "fieldname": "add_quote",
+ "fieldtype": "Check",
+ "label": "Add Quote"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_32",
- "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,
- "label": " ",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "add_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": "Add 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
+ "description": "Note: Email will not be sent to disabled users",
+ "fieldname": "recipients",
+ "fieldtype": "Table MultiSelect",
+ "label": "Recipients",
+ "options": "Email Digest Recipient",
+ "reqd": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-envelope",
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "menu_index": 0,
- "modified": "2019-01-16 09:52:15.149908",
- "modified_by": "Administrator",
- "module": "Setup",
- "name": "Email Digest",
- "owner": "Administrator",
+ ],
+ "icon": "fa fa-envelope",
+ "idx": 1,
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2020-08-24 23:49:00.081695",
+ "modified_by": "Administrator",
+ "module": "Setup",
+ "name": "Email Digest",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 0,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 1,
- "print": 0,
- "read": 1,
- "report": 0,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
+ "permlevel": 1,
+ "read": 1,
+ "role": "System Manager"
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC"
}
\ No newline at end of file
diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py
index 340d89b..6fbd4cd 100644
--- a/erpnext/setup/doctype/email_digest/email_digest.py
+++ b/erpnext/setup/doctype/email_digest/email_digest.py
@@ -47,19 +47,13 @@
# send email only to enabled users
valid_users = [p[0] for p in frappe.db.sql("""select name from `tabUser`
where enabled=1""")]
- recipients = list(filter(lambda r: r in valid_users,
- self.recipient_list.split("\n")))
- original_user = frappe.session.user
-
- if recipients:
- for user_id in recipients:
- frappe.set_user(user_id)
- frappe.set_user_lang(user_id)
+ if self.recipients:
+ for row in self.recipients:
msg_for_this_recipient = self.get_msg_html()
- if msg_for_this_recipient:
+ if msg_for_this_recipient and row.recipient in valid_users:
frappe.sendmail(
- recipients=user_id,
+ recipients=row.recipient,
subject=_("{0} Digest").format(self.frequency),
message=msg_for_this_recipient,
reference_doctype = self.doctype,
diff --git a/erpnext/setup/doctype/email_digest_recipient/__init__.py b/erpnext/setup/doctype/email_digest_recipient/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/setup/doctype/email_digest_recipient/__init__.py
diff --git a/erpnext/setup/doctype/email_digest_recipient/email_digest_recipient.json b/erpnext/setup/doctype/email_digest_recipient/email_digest_recipient.json
new file mode 100644
index 0000000..8b2a6dc
--- /dev/null
+++ b/erpnext/setup/doctype/email_digest_recipient/email_digest_recipient.json
@@ -0,0 +1,33 @@
+{
+ "actions": [],
+ "creation": "2020-06-08 12:19:40.428949",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "recipient"
+ ],
+ "fields": [
+ {
+ "fieldname": "recipient",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Recipient",
+ "options": "User",
+ "reqd": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2020-08-24 23:10:23.217572",
+ "modified_by": "Administrator",
+ "module": "Setup",
+ "name": "Email Digest Recipient",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/setup/doctype/email_digest_recipient/email_digest_recipient.py b/erpnext/setup/doctype/email_digest_recipient/email_digest_recipient.py
new file mode 100644
index 0000000..968c51c
--- /dev/null
+++ b/erpnext/setup/doctype/email_digest_recipient/email_digest_recipient.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class EmailDigestRecipient(Document):
+ pass
diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py
index 1c72ceb..5fcad00 100644
--- a/erpnext/setup/doctype/item_group/item_group.py
+++ b/erpnext/setup/doctype/item_group/item_group.py
@@ -87,8 +87,8 @@
if not field_filters:
field_filters = {}
- # Ensure the query remains within current item group
- field_filters['item_group'] = self.name
+ # Ensure the query remains within current item group & sub group
+ field_filters['item_group'] = [ig[0] for ig in get_child_groups(self.name)]
engine = ProductQuery()
context.items = engine.query(attribute_filters, field_filters, search, start, item_group=self.name)
diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json
index 9313f95..23e5947 100644
--- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json
+++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json
@@ -54,7 +54,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2021-05-08 23:13:48.049879",
+ "modified": "2021-08-04 20:15:59.071493",
"modified_by": "Administrator",
"module": "Setup",
"name": "Transaction Deletion Record",
@@ -70,6 +70,7 @@
"report": 1,
"role": "System Manager",
"share": 1,
+ "submit": 1,
"write": 1
}
],
diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py
index ece9fb5..c3db27f 100644
--- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py
+++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py
@@ -12,10 +12,14 @@
class TransactionDeletionRecord(Document):
def validate(self):
frappe.only_for('System Manager')
+ self.validate_doctypes_to_be_ignored()
+
+ def validate_doctypes_to_be_ignored(self):
doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
for doctype in self.doctypes_to_be_ignored:
if doctype.doctype_name not in doctypes_to_be_ignored_list:
- frappe.throw(_("DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it. "), title=_("Not Allowed"))
+ frappe.throw(_("DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it."),
+ title=_("Not Allowed"))
def before_submit(self):
if not self.doctypes_to_be_ignored:
@@ -23,54 +27,9 @@
self.delete_bins()
self.delete_lead_addresses()
-
- company_obj = frappe.get_doc('Company', self.company)
- # reset company values
- company_obj.total_monthly_sales = 0
- company_obj.sales_monthly_history = None
- company_obj.save()
- # Clear notification counts
+ self.reset_company_values()
clear_notifications()
-
- singles = frappe.get_all('DocType', filters = {'issingle': 1}, pluck = 'name')
- tables = frappe.get_all('DocType', filters = {'istable': 1}, pluck = 'name')
- doctypes_to_be_ignored_list = singles
- for doctype in self.doctypes_to_be_ignored:
- doctypes_to_be_ignored_list.append(doctype.doctype_name)
-
- docfields = frappe.get_all('DocField',
- filters = {
- 'fieldtype': 'Link',
- 'options': 'Company',
- 'parent': ['not in', doctypes_to_be_ignored_list]},
- fields=['parent', 'fieldname'])
-
- for docfield in docfields:
- if docfield['parent'] != self.doctype:
- no_of_docs = frappe.db.count(docfield['parent'], {
- docfield['fieldname'] : self.company
- })
-
- if no_of_docs > 0:
- self.delete_version_log(docfield['parent'], docfield['fieldname'])
- self.delete_communications(docfield['parent'], docfield['fieldname'])
-
- # populate DocTypes table
- if docfield['parent'] not in tables:
- self.append('doctypes', {
- 'doctype_name' : docfield['parent'],
- 'no_of_docs' : no_of_docs
- })
-
- # delete the docs linked with the specified company
- frappe.db.delete(docfield['parent'], {
- docfield['fieldname'] : self.company
- })
-
- naming_series = frappe.db.get_value('DocType', docfield['parent'], 'autoname')
- if naming_series:
- if '#' in naming_series:
- self.update_naming_series(naming_series, docfield['parent'])
+ self.delete_company_transactions()
def populate_doctypes_to_be_ignored_table(self):
doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
@@ -79,6 +38,111 @@
'doctype_name' : doctype
})
+ def delete_bins(self):
+ frappe.db.sql("""delete from tabBin where warehouse in
+ (select name from tabWarehouse where company=%s)""", self.company)
+
+ def delete_lead_addresses(self):
+ """Delete addresses to which leads are linked"""
+ leads = frappe.get_all('Lead', filters={'company': self.company})
+ leads = ["'%s'" % row.get("name") for row in leads]
+ addresses = []
+ if leads:
+ addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name
+ in ({leads})""".format(leads=",".join(leads)))
+
+ if addresses:
+ addresses = ["%s" % frappe.db.escape(addr) for addr in addresses]
+
+ frappe.db.sql("""delete from tabAddress where name in ({addresses}) and
+ name not in (select distinct dl1.parent from `tabDynamic Link` dl1
+ inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent
+ and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses)))
+
+ frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead'
+ and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads)))
+
+ frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads)))
+
+ def reset_company_values(self):
+ company_obj = frappe.get_doc('Company', self.company)
+ company_obj.total_monthly_sales = 0
+ company_obj.sales_monthly_history = None
+ company_obj.save()
+
+ def delete_company_transactions(self):
+ doctypes_to_be_ignored_list = self.get_doctypes_to_be_ignored_list()
+ docfields = self.get_doctypes_with_company_field(doctypes_to_be_ignored_list)
+
+ tables = self.get_all_child_doctypes()
+ for docfield in docfields:
+ if docfield['parent'] != self.doctype:
+ no_of_docs = self.get_number_of_docs_linked_with_specified_company(docfield['parent'], docfield['fieldname'])
+
+ if no_of_docs > 0:
+ self.delete_version_log(docfield['parent'], docfield['fieldname'])
+ self.delete_communications(docfield['parent'], docfield['fieldname'])
+ self.populate_doctypes_table(tables, docfield['parent'], no_of_docs)
+
+ self.delete_child_tables(docfield['parent'], docfield['fieldname'])
+ self.delete_docs_linked_with_specified_company(docfield['parent'], docfield['fieldname'])
+
+ naming_series = frappe.db.get_value('DocType', docfield['parent'], 'autoname')
+ if naming_series:
+ if '#' in naming_series:
+ self.update_naming_series(naming_series, docfield['parent'])
+
+ def get_doctypes_to_be_ignored_list(self):
+ singles = frappe.get_all('DocType', filters = {'issingle': 1}, pluck = 'name')
+ doctypes_to_be_ignored_list = singles
+ for doctype in self.doctypes_to_be_ignored:
+ doctypes_to_be_ignored_list.append(doctype.doctype_name)
+
+ return doctypes_to_be_ignored_list
+
+ def get_doctypes_with_company_field(self, doctypes_to_be_ignored_list):
+ docfields = frappe.get_all('DocField',
+ filters = {
+ 'fieldtype': 'Link',
+ 'options': 'Company',
+ 'parent': ['not in', doctypes_to_be_ignored_list]},
+ fields=['parent', 'fieldname'])
+
+ return docfields
+
+ def get_all_child_doctypes(self):
+ return frappe.get_all('DocType', filters = {'istable': 1}, pluck = 'name')
+
+ def get_number_of_docs_linked_with_specified_company(self, doctype, company_fieldname):
+ return frappe.db.count(doctype, {company_fieldname : self.company})
+
+ def populate_doctypes_table(self, tables, doctype, no_of_docs):
+ if doctype not in tables:
+ self.append('doctypes', {
+ 'doctype_name' : doctype,
+ 'no_of_docs' : no_of_docs
+ })
+
+ def delete_child_tables(self, doctype, company_fieldname):
+ parent_docs_to_be_deleted = frappe.get_all(doctype, {
+ company_fieldname : self.company
+ }, pluck = 'name')
+
+ child_tables = frappe.get_all('DocField', filters = {
+ 'fieldtype': 'Table',
+ 'parent': doctype
+ }, pluck = 'options')
+
+ for table in child_tables:
+ frappe.db.delete(table, {
+ 'parent': ['in', parent_docs_to_be_deleted]
+ })
+
+ def delete_docs_linked_with_specified_company(self, doctype, company_fieldname):
+ frappe.db.delete(doctype, {
+ company_fieldname : self.company
+ })
+
def update_naming_series(self, naming_series, doctype_name):
if '.' in naming_series:
prefix, hashes = naming_series.rsplit('.', 1)
@@ -107,32 +171,6 @@
frappe.delete_doc('Communication', communication_names, ignore_permissions=True)
- def delete_bins(self):
- frappe.db.sql("""delete from tabBin where warehouse in
- (select name from tabWarehouse where company=%s)""", self.company)
-
- def delete_lead_addresses(self):
- """Delete addresses to which leads are linked"""
- leads = frappe.get_all('Lead', filters={'company': self.company})
- leads = ["'%s'" % row.get("name") for row in leads]
- addresses = []
- if leads:
- addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name
- in ({leads})""".format(leads=",".join(leads)))
-
- if addresses:
- addresses = ["%s" % frappe.db.escape(addr) for addr in addresses]
-
- frappe.db.sql("""delete from tabAddress where name in ({addresses}) and
- name not in (select distinct dl1.parent from `tabDynamic Link` dl1
- inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent
- and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses)))
-
- frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead'
- and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads)))
-
- frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads)))
-
@frappe.whitelist()
def get_doctypes_to_be_ignored():
doctypes_to_be_ignored_list = ['Account', 'Cost Center', 'Warehouse', 'Budget',
diff --git a/erpnext/setup/setup_wizard/operations/company_setup.py b/erpnext/setup/setup_wizard/operations/company_setup.py
index 4edf948..4833d93 100644
--- a/erpnext/setup/setup_wizard/operations/company_setup.py
+++ b/erpnext/setup/setup_wizard/operations/company_setup.py
@@ -45,9 +45,16 @@
def create_email_digest():
from frappe.utils.user import get_system_managers
system_managers = get_system_managers(only_name=True)
+
if not system_managers:
return
+ recipients = []
+ for d in system_managers:
+ recipients.append({
+ 'recipient': d
+ })
+
companies = frappe.db.sql_list("select name FROM `tabCompany`")
for company in companies:
if not frappe.db.exists("Email Digest", "Default Weekly Digest - " + company):
@@ -56,7 +63,7 @@
"name": "Default Weekly Digest - " + company,
"company": company,
"frequency": "Weekly",
- "recipient_list": "\n".join(system_managers)
+ "recipients": recipients
})
for df in edigest.meta.get("fields", {"fieldtype": "Check"}):
@@ -72,7 +79,7 @@
"name": "Scheduler Errors",
"company": companies[0],
"frequency": "Daily",
- "recipient_list": "\n".join(system_managers),
+ "recipients": recipients,
"scheduler_errors": 1,
"enabled": 1
})
diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py
index b6eef6c..b37ae3f 100644
--- a/erpnext/stock/doctype/batch/batch.py
+++ b/erpnext/stock/doctype/batch/batch.py
@@ -162,19 +162,19 @@
out = float(frappe.db.sql("""select sum(actual_qty)
from `tabStock Ledger Entry`
- where warehouse=%s and batch_no=%s {0}""".format(cond),
+ where is_cancelled = 0 and warehouse=%s and batch_no=%s {0}""".format(cond),
(warehouse, batch_no))[0][0] or 0)
if batch_no and not warehouse:
out = frappe.db.sql('''select warehouse, sum(actual_qty) as qty
from `tabStock Ledger Entry`
- where batch_no=%s
+ where is_cancelled = 0 and batch_no=%s
group by warehouse''', batch_no, as_dict=1)
if not batch_no and item_code and warehouse:
out = frappe.db.sql('''select batch_no, sum(actual_qty) as qty
from `tabStock Ledger Entry`
- where item_code = %s and warehouse=%s
+ where is_cancelled = 0 and item_code = %s and warehouse=%s
group by batch_no''', (item_code, warehouse), as_dict=1)
return out
diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py
index cbd272d..a85a022 100644
--- a/erpnext/stock/doctype/batch/test_batch.py
+++ b/erpnext/stock/doctype/batch/test_batch.py
@@ -269,11 +269,14 @@
batch2 = create_batch('_Test Batch Price Item', 300, 1)
batch3 = create_batch('_Test Batch Price Item', 400, 0)
+ company = "_Test Company with perpetual inventory"
+ currency = frappe.get_cached_value("Company", company, "default_currency")
+
args = frappe._dict({
"item_code": "_Test Batch Price Item",
- "company": "_Test Company with perpetual inventory",
+ "company": company,
"price_list": "_Test Price List",
- "currency": "_Test Currency",
+ "currency": currency,
"doctype": "Sales Invoice",
"conversion_rate": 1,
"price_list_currency": "_Test Currency",
@@ -333,4 +336,4 @@
except frappe.DuplicateEntryError:
batch = frappe.get_doc("Batch", args.batch_id)
- return batch
\ No newline at end of file
+ return batch
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json
index f20e76f..dbfeb4a 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.json
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.json
@@ -32,6 +32,8 @@
"contact_info",
"shipping_address_name",
"shipping_address",
+ "dispatch_address_name",
+ "dispatch_address",
"contact_person",
"contact_display",
"contact_mobile",
@@ -1282,13 +1284,28 @@
"fieldname": "disable_rounded_total",
"fieldtype": "Check",
"label": "Disable Rounded Total"
+ },
+ {
+ "fieldname": "dispatch_address_name",
+ "fieldtype": "Link",
+ "label": "Dispatch Address Name",
+ "options": "Address",
+ "print_hide": 1
+ },
+ {
+ "depends_on": "dispatch_address_name",
+ "fieldname": "dispatch_address",
+ "fieldtype": "Small Text",
+ "label": "Dispatch Address",
+ "print_hide": 1,
+ "read_only": 1
}
],
"icon": "fa fa-truck",
"idx": 146,
"is_submittable": 1,
"links": [],
- "modified": "2021-06-11 19:27:30.901112",
+ "modified": "2021-07-08 21:37:20.802652",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index 4808e94..f99a01b 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -503,6 +503,10 @@
}
}, target_doc, set_missing_values)
+ automatically_fetch_payment_terms = cint(frappe.db.get_single_value('Accounts Settings', 'automatically_fetch_payment_terms'))
+ if automatically_fetch_payment_terms:
+ doc.set_payment_schedule()
+
return doc
@frappe.whitelist()
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index f981aeb..756825e 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -17,7 +17,8 @@
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, SerialNoWarehouseError
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation \
import create_stock_reconciliation, set_valuation_method
-from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order, create_dn_against_so
+from erpnext.selling.doctype.sales_order.test_sales_order \
+ import make_sales_order, create_dn_against_so, automatically_fetch_payment_terms, compare_payment_schedules
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse
from erpnext.stock.doctype.item.test_item import make_item
@@ -759,6 +760,32 @@
self.assertTrue("TESTBATCH" in dn.packed_items[0].batch_no, "Batch number not added in packed item")
+ def test_payment_terms_are_fetched_when_creating_sales_invoice(self):
+ from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template
+ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
+
+ automatically_fetch_payment_terms()
+
+ so = make_sales_order(uom="Nos", do_not_save=1)
+ create_payment_terms_template()
+ so.payment_terms_template = 'Test Receivable Template'
+ so.submit()
+
+ dn = create_dn_against_so(so.name, delivered_qty=10)
+
+ si = create_sales_invoice(qty=10, do_not_save=1)
+ si.items[0].delivery_note= dn.name
+ si.items[0].dn_detail = dn.items[0].name
+ si.items[0].sales_order = so.name
+ si.items[0].so_detail = so.items[0].name
+
+ si.insert()
+ si.submit()
+
+ self.assertEqual(so.payment_terms_template, si.payment_terms_template)
+ compare_payment_schedules(self, so, si)
+
+ automatically_fetch_payment_terms(enable=0)
def create_delivery_note(**args):
dn = frappe.new_doc("Delivery Note")
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index b55374b..6c5ef8b 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -100,10 +100,11 @@
frm.add_custom_button(__('Duplicate'), function() {
var new_item = frappe.model.copy_doc(frm.doc);
- if(new_item.item_name===new_item.item_code) {
+ // Duplicate item could have different name, causing "copy paste" error.
+ if (new_item.item_name===new_item.item_code) {
new_item.item_name = null;
}
- if(new_item.description===new_item.description) {
+ if (new_item.item_code===new_item.description || new_item.item_code===new_item.description) {
new_item.description = null;
}
frappe.set_route('Form', 'Item', new_item.name);
@@ -137,20 +138,6 @@
frm.toggle_reqd('customer', frm.doc.is_customer_provided_item ? 1:0);
},
- gst_hsn_code: function(frm) {
- if((!frm.doc.taxes || !frm.doc.taxes.length) && frm.doc.gst_hsn_code) {
- frappe.db.get_doc("GST HSN Code", frm.doc.gst_hsn_code).then(hsn_doc => {
- $.each(hsn_doc.taxes || [], function(i, tax) {
- let a = frappe.model.add_child(cur_frm.doc, 'Item Tax', 'taxes');
- a.item_tax_template = tax.item_tax_template;
- a.tax_category = tax.tax_category;
- a.valid_from = tax.valid_from;
- frm.refresh_field('taxes');
- });
- });
- }
- },
-
is_fixed_asset: function(frm) {
// set serial no to false & toggles its visibility
frm.set_value('has_serial_no', 0);
@@ -186,8 +173,6 @@
item_code: function(frm) {
if(!frm.doc.item_name)
frm.set_value("item_name", frm.doc.item_code);
- if(!frm.doc.description)
- frm.set_value("description", frm.doc.item_code);
},
is_stock_item: function(frm) {
@@ -275,6 +260,17 @@
}
}
+ frm.fields_dict["item_defaults"].grid.get_field("default_discount_account").get_query = function(doc, cdt, cdn) {
+ const row = locals[cdt][cdn];
+ return {
+ filters: {
+ 'report_type': 'Profit and Loss',
+ 'company': row.company,
+ "is_group": 0
+ }
+ };
+ };
+
frm.fields_dict["item_defaults"].grid.get_field("buying_cost_center").get_query = function(doc, cdt, cdn) {
const row = locals[cdt][cdn];
return {
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index 6fed9ef..f662bbd 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -1067,7 +1067,7 @@
"index_web_pages_for_search": 1,
"links": [],
"max_attachments": 1,
- "modified": "2021-03-18 14:04:38.575519",
+ "modified": "2021-07-13 01:29:06.071827",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",
@@ -1138,4 +1138,4 @@
"sort_order": "DESC",
"title_field": "item_name",
"track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index fbd30cf..9bf4dbf 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -123,6 +123,7 @@
self.cant_change()
self.update_show_in_website()
self.validate_item_tax_net_rate_range()
+ set_item_tax_from_hsn_code(self)
if not self.is_new():
self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
@@ -1305,3 +1306,7 @@
def on_doctype_update():
# since route is a Text column, it needs a length for indexing
frappe.db.add_index("Item", ["route(500)"])
+
+@erpnext.allow_regional
+def set_item_tax_from_hsn_code(item):
+ pass
\ No newline at end of file
diff --git a/erpnext/stock/doctype/item/regional/india.js b/erpnext/stock/doctype/item/regional/india.js
new file mode 100644
index 0000000..77ae51f
--- /dev/null
+++ b/erpnext/stock/doctype/item/regional/india.js
@@ -0,0 +1,15 @@
+frappe.ui.form.on('Item', {
+ gst_hsn_code: function(frm) {
+ if ((!frm.doc.taxes || !frm.doc.taxes.length) && frm.doc.gst_hsn_code) {
+ frappe.db.get_doc("GST HSN Code", frm.doc.gst_hsn_code).then(hsn_doc => {
+ $.each(hsn_doc.taxes || [], function(i, tax) {
+ let a = frappe.model.add_child(cur_frm.doc, 'Item Tax', 'taxes');
+ a.item_tax_template = tax.item_tax_template;
+ a.tax_category = tax.tax_category;
+ a.valid_from = tax.valid_from;
+ frm.refresh_field('taxes');
+ });
+ });
+ }
+ },
+});
\ No newline at end of file
diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py
index c7467a5..9ec44d2 100644
--- a/erpnext/stock/doctype/item/test_item.py
+++ b/erpnext/stock/doctype/item/test_item.py
@@ -83,14 +83,17 @@
make_test_objects("Item Price")
+ company = "_Test Company"
+ currency = frappe.get_cached_value("Company", company, "default_currency")
+
details = get_item_details({
"item_code": "_Test Item",
- "company": "_Test Company",
+ "company": company,
"price_list": "_Test Price List",
- "currency": "_Test Currency",
+ "currency": currency,
"doctype": "Sales Order",
"conversion_rate": 1,
- "price_list_currency": "_Test Currency",
+ "price_list_currency": currency,
"plc_conversion_rate": 1,
"order_type": "Sales",
"customer": "_Test Customer",
diff --git a/erpnext/stock/doctype/item_default/item_default.json b/erpnext/stock/doctype/item_default/item_default.json
index 96b5dfd..bc17160 100644
--- a/erpnext/stock/doctype/item_default/item_default.json
+++ b/erpnext/stock/doctype/item_default/item_default.json
@@ -1,464 +1,118 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2018-05-03 02:29:24.444341",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "creation": "2018-05-03 02:29:24.444341",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "company",
+ "default_warehouse",
+ "column_break_3",
+ "default_price_list",
+ "default_discount_account",
+ "purchase_defaults",
+ "buying_cost_center",
+ "default_supplier",
+ "column_break_8",
+ "expense_account",
+ "selling_defaults",
+ "selling_cost_center",
+ "column_break_12",
+ "income_account"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "company",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 1,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Company",
- "length": 0,
- "no_copy": 0,
- "options": "Company",
- "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
- },
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "ignore_user_permissions": 1,
+ "in_list_view": 1,
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "default_warehouse",
- "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": "Default Warehouse",
- "length": 0,
- "no_copy": 0,
- "options": "Warehouse",
- "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
- },
+ "fieldname": "default_warehouse",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Default Warehouse",
+ "options": "Warehouse"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 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
- },
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "default_price_list",
- "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": "Default Price List",
- "length": 0,
- "no_copy": 0,
- "options": "Price List",
- "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
- },
+ "fieldname": "default_price_list",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Default Price List",
+ "options": "Price List"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "purchase_defaults",
- "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": "Purchase Defaults",
- "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
- },
+ "fieldname": "purchase_defaults",
+ "fieldtype": "Section Break",
+ "label": "Purchase Defaults"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "buying_cost_center",
- "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": "Default Buying Cost Center",
- "length": 0,
- "no_copy": 0,
- "options": "Cost Center",
- "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
- },
+ "fieldname": "buying_cost_center",
+ "fieldtype": "Link",
+ "label": "Default Buying Cost Center",
+ "options": "Cost Center"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "default_supplier",
- "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": "Default 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": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "default_supplier",
+ "fieldtype": "Link",
+ "label": "Default Supplier",
+ "options": "Supplier"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_8",
- "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
- },
+ "fieldname": "column_break_8",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "expense_account",
- "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": "Default Expense Account",
- "length": 0,
- "no_copy": 0,
- "options": "Account",
- "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
- },
+ "fieldname": "expense_account",
+ "fieldtype": "Link",
+ "label": "Default Expense Account",
+ "options": "Account"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "selling_defaults",
- "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": "Sales Defaults",
- "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
- },
+ "fieldname": "selling_defaults",
+ "fieldtype": "Section Break",
+ "label": "Sales Defaults"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "selling_cost_center",
- "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": "Default Selling Cost Center",
- "length": 0,
- "no_copy": 0,
- "options": "Cost Center",
- "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
- },
+ "fieldname": "selling_cost_center",
+ "fieldtype": "Link",
+ "label": "Default Selling Cost Center",
+ "options": "Cost Center"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_12",
- "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
- },
+ "fieldname": "column_break_12",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "income_account",
- "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": "Default Income Account",
- "length": 0,
- "no_copy": 0,
- "options": "Account",
- "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
+ "fieldname": "income_account",
+ "fieldtype": "Link",
+ "label": "Default Income Account",
+ "options": "Account"
+ },
+ {
+ "fieldname": "default_discount_account",
+ "fieldtype": "Link",
+ "label": "Default Discount Account",
+ "options": "Account"
}
- ],
- "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-12-07 11:48:07.638935",
- "modified_by": "Administrator",
- "module": "Stock",
- "name": "Item Default",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "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_views": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2021-07-13 01:26:03.860065",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Item Default",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json
index 9b1a47e..5de45cb 100644
--- a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json
+++ b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json
@@ -47,7 +47,8 @@
"description": "Simple Python formula applied on Reading fields.<br> Numeric eg. 1: <b>reading_1 > 0.2 and reading_1 < 0.5</b><br>\nNumeric eg. 2: <b>mean > 3.5</b> (mean of populated fields)<br>\nValue based eg.: <b>reading_value in (\"A\", \"B\", \"C\")</b>",
"fieldname": "acceptance_formula",
"fieldtype": "Code",
- "label": "Acceptance Criteria Formula"
+ "label": "Acceptance Criteria Formula",
+ "options": "PythonExpression"
},
{
"default": "0",
@@ -89,7 +90,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2021-02-04 18:50:02.056173",
+ "modified": "2021-08-06 15:08:20.911338",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item Quality Inspection Parameter",
diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
index 32b08f6..cb09d93 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
@@ -11,6 +11,7 @@
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.accounts.doctype.account.test_account import create_account
+from erpnext.assets.doctype.asset.test_asset import create_asset_category, create_fixed_asset_item
class TestLandedCostVoucher(unittest.TestCase):
def test_landed_cost_voucher(self):
@@ -250,6 +251,39 @@
self.assertEqual(entry.credit, amounts[0])
self.assertEqual(entry.credit_in_account_currency, amounts[1])
+ def test_asset_lcv(self):
+ "Check if LCV for an Asset updates the Assets Gross Purchase Amount correctly."
+ frappe.db.set_value("Company", "_Test Company", "capital_work_in_progress_account", "CWIP Account - _TC")
+
+ if not frappe.db.exists("Asset Category", "Computers"):
+ create_asset_category()
+
+ if not frappe.db.exists("Item", "Macbook Pro"):
+ create_fixed_asset_item()
+
+ pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=50000)
+
+ # check if draft asset was created
+ assets = frappe.db.get_all('Asset', filters={'purchase_receipt': pr.name})
+ self.assertEqual(len(assets), 1)
+
+ lcv = make_landed_cost_voucher(
+ company = pr.company,
+ receipt_document_type = "Purchase Receipt",
+ receipt_document=pr.name,
+ charges=80,
+ expense_account="Expenses Included In Valuation - _TC")
+
+ lcv.save()
+ lcv.submit()
+
+ # lcv updates amount in draft asset
+ self.assertEqual(frappe.db.get_value("Asset", assets[0].name, "gross_purchase_amount"), 50080)
+
+ # tear down
+ lcv.cancel()
+ pr.cancel()
+
def make_landed_cost_voucher(** args):
args = frappe._dict(args)
ref_doc = frappe.get_doc(args.receipt_document_type, args.receipt_document)
@@ -268,7 +302,7 @@
lcv.set("taxes", [{
"description": "Shipping Charges",
- "expense_account": "Expenses Included In Valuation - TCP1",
+ "expense_account": args.expense_account or "Expenses Included In Valuation - TCP1",
"amount": args.charges
}])
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index 3ad9909..026b85e 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -162,8 +162,15 @@
from `tabStock Entry Detail` where material_request = %s
and material_request_item = %s and docstatus = 1""",
(self.name, d.name))[0][0])
+ mr_qty_allowance = frappe.db.get_single_value('Stock Settings', 'mr_qty_allowance')
- if d.ordered_qty and d.ordered_qty > d.stock_qty:
+ if mr_qty_allowance:
+ allowed_qty = d.qty + (d.qty * (mr_qty_allowance/100))
+ if d.ordered_qty and d.ordered_qty > allowed_qty:
+ frappe.throw(_("The total Issue / Transfer quantity {0} in Material Request {1} \
+ cannot be greater than allowed requested quantity {2} for Item {3}").format(d.ordered_qty, d.parent, allowed_qty, d.item_code))
+
+ elif d.ordered_qty and d.ordered_qty > d.stock_qty:
frappe.throw(_("The total Issue / Transfer quantity {0} in Material Request {1} \
cannot be greater than requested quantity {2} for Item {3}").format(d.ordered_qty, d.parent, d.qty, d.item_code))
diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py
index 72a3a5e..b4776ba 100644
--- a/erpnext/stock/doctype/material_request/test_material_request.py
+++ b/erpnext/stock/doctype/material_request/test_material_request.py
@@ -329,6 +329,58 @@
self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 54.0)
self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 3.0)
+ def test_over_transfer_qty_allowance(self):
+ mr = frappe.new_doc('Material Request')
+ mr.company = "_Test Company"
+ mr.scheduled_date = today()
+ mr.append('items',{
+ "item_code": "_Test FG Item",
+ "item_name": "_Test FG Item",
+ "qty": 10,
+ "schedule_date": today(),
+ "uom": "_Test UOM 1",
+ "warehouse": "_Test Warehouse - _TC"
+ })
+
+ mr.material_request_type = "Material Transfer"
+ mr.insert()
+ mr.submit()
+
+ frappe.db.set_value('Stock Settings', None, 'mr_qty_allowance', 20)
+
+ # map a stock entry
+
+ se_doc = make_stock_entry(mr.name)
+ se_doc.update({
+ "posting_date": today(),
+ "posting_time": "00:00",
+ })
+ se_doc.get("items")[0].update({
+ "qty": 13,
+ "transfer_qty": 12.0,
+ "s_warehouse": "_Test Warehouse - _TC",
+ "t_warehouse": "_Test Warehouse 1 - _TC",
+ "basic_rate": 1.0
+ })
+
+ # make available the qty in _Test Warehouse 1 before transfer
+ sr = frappe.new_doc("Stock Reconciliation")
+ sr.company = "_Test Company"
+ sr.purpose = "Opening Stock"
+ sr.append('items', {
+ "item_code": "_Test FG Item",
+ "warehouse": "_Test Warehouse - _TC",
+ "qty": 20,
+ "valuation_rate": 0.01
+ })
+ sr.insert()
+ sr.submit()
+ se = frappe.copy_doc(se_doc)
+ se.insert()
+ self.assertRaises(frappe.ValidationError)
+ se.items[0].qty = 12
+ se.submit()
+
def test_completed_qty_for_over_transfer(self):
existing_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
existing_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index e795742..516ae43 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -239,6 +239,7 @@
and sle.`item_code`=%(item_code)s
and sle.`company` = %(company)s
and batch.disabled = 0
+ and sle.is_cancelled=0
and IFNULL(batch.`expiry_date`, '2200-01-01') > %(today)s
{warehouse_condition}
GROUP BY
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 82c87a8..4a4514f 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -286,8 +286,16 @@
and warehouse_account_name == supplier_warehouse_account:
continue
- self.add_gl_entry(gl_entries, warehouse_account_name, d.cost_center, stock_value_diff, 0.0, remarks,
- stock_rbnb, account_currency=warehouse_account_currency, item=d)
+ self.add_gl_entry(
+ gl_entries=gl_entries,
+ account=warehouse_account_name,
+ cost_center=d.cost_center,
+ debit=stock_value_diff,
+ credit=0.0,
+ remarks=remarks,
+ against_account=stock_rbnb,
+ account_currency=warehouse_account_currency,
+ item=d)
# GL Entry for from warehouse or Stock Received but not billed
# Intentionally passed negative debit amount to avoid incorrect GL Entry validation
@@ -300,9 +308,17 @@
account = warehouse_account[d.from_warehouse]['account'] \
if d.from_warehouse else stock_rbnb
- self.add_gl_entry(gl_entries, account, d.cost_center,
- -1 * flt(d.base_net_amount, d.precision("base_net_amount")), 0.0, remarks, warehouse_account_name,
- debit_in_account_currency=-1 * credit_amount, account_currency=credit_currency, item=d)
+ self.add_gl_entry(
+ gl_entries=gl_entries,
+ account=account,
+ cost_center=d.cost_center,
+ debit=-1 * flt(d.base_net_amount, d.precision("base_net_amount")),
+ credit=0.0,
+ remarks=remarks,
+ against_account=warehouse_account_name,
+ debit_in_account_currency=-1 * credit_amount,
+ account_currency=credit_currency,
+ item=d)
# Amount added through landed-cos-voucher
if d.landed_cost_voucher_amount and landed_cost_entries:
@@ -311,14 +327,31 @@
credit_amount = (flt(amount["base_amount"]) if (amount["base_amount"] or
account_currency!=self.company_currency) else flt(amount["amount"]))
- self.add_gl_entry(gl_entries, account, d.cost_center, 0.0, credit_amount, remarks,
- warehouse_account_name, credit_in_account_currency=flt(amount["amount"]),
- account_currency=account_currency, project=d.project, item=d)
+ self.add_gl_entry(
+ gl_entries=gl_entries,
+ account=account,
+ cost_center=d.cost_center,
+ debit=0.0,
+ credit=credit_amount,
+ remarks=remarks,
+ against_account=warehouse_account_name,
+ credit_in_account_currency=flt(amount["amount"]),
+ account_currency=account_currency,
+ project=d.project,
+ item=d)
# sub-contracting warehouse
if flt(d.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse):
- self.add_gl_entry(gl_entries, supplier_warehouse_account, d.cost_center, 0.0, flt(d.rm_supp_cost),
- remarks, warehouse_account_name, account_currency=supplier_warehouse_account_currency, item=d)
+ self.add_gl_entry(
+ gl_entries=gl_entries,
+ account=supplier_warehouse_account,
+ cost_center=d.cost_center,
+ debit=0.0,
+ credit=flt(d.rm_supp_cost),
+ remarks=remarks,
+ against_account=warehouse_account_name,
+ account_currency=supplier_warehouse_account_currency,
+ item=d)
# divisional loss adjustment
valuation_amount_as_per_doc = flt(d.base_net_amount, d.precision("base_net_amount")) + \
@@ -335,8 +368,17 @@
cost_center = d.cost_center or frappe.get_cached_value("Company", self.company, "cost_center")
- self.add_gl_entry(gl_entries, loss_account, cost_center, divisional_loss, 0.0, remarks,
- warehouse_account_name, account_currency=credit_currency, project=d.project, item=d)
+ self.add_gl_entry(
+ gl_entries=gl_entries,
+ account=loss_account,
+ cost_center=cost_center,
+ debit=divisional_loss,
+ credit=0.0,
+ remarks=remarks,
+ against_account=warehouse_account_name,
+ account_currency=credit_currency,
+ project=d.project,
+ item=d)
elif d.warehouse not in warehouse_with_no_account or \
d.rejected_warehouse not in warehouse_with_no_account:
@@ -347,12 +389,30 @@
debit_currency = get_account_currency(d.expense_account)
remarks = self.get("remarks") or _("Accounting Entry for Service")
- self.add_gl_entry(gl_entries, service_received_but_not_billed_account, d.cost_center, 0.0, d.amount,
- remarks, d.expense_account, account_currency=credit_currency, project=d.project,
+ self.add_gl_entry(
+ gl_entries=gl_entries,
+ account=service_received_but_not_billed_account,
+ cost_center=d.cost_center,
+ debit=0.0,
+ credit=d.amount,
+ remarks=remarks,
+ against_account=d.expense_account,
+ account_currency=credit_currency,
+ project=d.project,
voucher_detail_no=d.name, item=d)
- self.add_gl_entry(gl_entries, d.expense_account, d.cost_center, d.amount, 0.0, remarks, service_received_but_not_billed_account,
- account_currency = debit_currency, project=d.project, voucher_detail_no=d.name, item=d)
+ self.add_gl_entry(
+ gl_entries=gl_entries,
+ account=d.expense_account,
+ cost_center=d.cost_center,
+ debit=d.amount,
+ credit=0.0,
+ remarks=remarks,
+ against_account=service_received_but_not_billed_account,
+ account_currency = debit_currency,
+ project=d.project,
+ voucher_detail_no=d.name,
+ item=d)
if warehouse_with_no_account:
frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" +
@@ -402,8 +462,15 @@
applicable_amount = negative_expense_to_be_booked * (valuation_tax[tax.name] / total_valuation_amount)
amount_including_divisional_loss -= applicable_amount
- self.add_gl_entry(gl_entries, account, tax.cost_center, 0.0, applicable_amount, self.remarks or _("Accounting Entry for Stock"),
- against_account, item=tax)
+ self.add_gl_entry(
+ gl_entries=gl_entries,
+ account=account,
+ cost_center=tax.cost_center,
+ debit=0.0,
+ credit=applicable_amount,
+ remarks=self.remarks or _("Accounting Entry for Stock"),
+ against_account=against_account,
+ item=tax)
i += 1
@@ -415,7 +482,7 @@
"cost_center": cost_center,
"debit": debit,
"credit": credit,
- "against_account": against_account,
+ "against": against_account,
"remarks": remarks,
}
@@ -456,15 +523,31 @@
# debit cwip account
debit_in_account_currency = (base_asset_amount
if cwip_account_currency == self.company_currency else asset_amount)
- self.add_gl_entry(gl_entries, cwip_account, item.cost_center, base_asset_amount, 0.0, remarks,
- arbnb_account, debit_in_account_currency=debit_in_account_currency, item=item)
+ self.add_gl_entry(
+ gl_entries=gl_entries,
+ account=cwip_account,
+ cost_center=item.cost_center,
+ debit=base_asset_amount,
+ credit=0.0,
+ remarks=remarks,
+ against_account=arbnb_account,
+ debit_in_account_currency=debit_in_account_currency,
+ item=item)
asset_rbnb_currency = get_account_currency(arbnb_account)
# credit arbnb account
credit_in_account_currency = (base_asset_amount
if asset_rbnb_currency == self.company_currency else asset_amount)
- self.add_gl_entry(gl_entries, arbnb_account, item.cost_center, 0.0, base_asset_amount, remarks,
- cwip_account, credit_in_account_currency=credit_in_account_currency, item=item)
+ self.add_gl_entry(
+ gl_entries=gl_entries,
+ account=arbnb_account,
+ cost_center=item.cost_center,
+ debit=0.0,
+ credit=base_asset_amount,
+ remarks=remarks,
+ against_account=cwip_account,
+ credit_in_account_currency=credit_in_account_currency,
+ item=item)
def add_lcv_gl_entries(self, item, gl_entries):
expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation")
@@ -477,11 +560,27 @@
remarks = self.get("remarks") or _("Accounting Entry for Stock")
- self.add_gl_entry(gl_entries, expenses_included_in_asset_valuation, item.cost_center, 0.0, flt(item.landed_cost_voucher_amount),
- remarks, asset_account, project=item.project, item=item)
+ self.add_gl_entry(
+ gl_entries=gl_entries,
+ account=expenses_included_in_asset_valuation,
+ cost_center=item.cost_center,
+ debit=0.0,
+ credit=flt(item.landed_cost_voucher_amount),
+ remarks=remarks,
+ against_account=asset_account,
+ project=item.project,
+ item=item)
- self.add_gl_entry(gl_entries, asset_account, item.cost_center, 0.0, flt(item.landed_cost_voucher_amount),
- remarks, expenses_included_in_asset_valuation, project=item.project, item=item)
+ self.add_gl_entry(
+ gl_entries=gl_entries,
+ account=asset_account,
+ cost_center=item.cost_center,
+ debit=flt(item.landed_cost_voucher_amount),
+ credit=0.0,
+ remarks=remarks,
+ against_account=expenses_included_in_asset_valuation,
+ project=item.project,
+ item=item)
def update_assets(self, item, valuation_rate):
assets = frappe.db.get_all('Asset',
@@ -598,6 +697,7 @@
doc.run_method("onload")
doc.run_method("set_missing_values")
doc.run_method("calculate_taxes_and_totals")
+ doc.set_payment_schedule()
def update_item(source_doc, target_doc, source_parent):
target_doc.qty, returned_qty = get_pending_qty(source_doc)
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 2eb8bfd..0210702 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -23,9 +23,7 @@
def test_reverse_purchase_receipt_sle(self):
- frappe.db.set_value('UOM', '_Test UOM', 'must_be_whole_number', 0)
-
- pr = make_purchase_receipt(qty=0.5)
+ pr = make_purchase_receipt(qty=0.5, item_code="_Test Item Home Desktop 200")
sl_entry = frappe.db.get_all("Stock Ledger Entry", {"voucher_type": "Purchase Receipt",
"voucher_no": pr.name}, ['actual_qty'])
@@ -41,8 +39,6 @@
self.assertEqual(len(sl_entry_cancelled), 2)
self.assertEqual(sl_entry_cancelled[1].actual_qty, -0.5)
- frappe.db.set_value('UOM', '_Test UOM', 'must_be_whole_number', 1)
-
def test_make_purchase_invoice(self):
if not frappe.db.exists('Payment Terms Template', '_Test Payment Terms Template For Purchase Invoice'):
frappe.get_doc({
@@ -328,18 +324,7 @@
pr1.submit()
self.assertRaises(frappe.ValidationError, pr2.submit)
-
- pr1.cancel()
- se.cancel()
- se1.cancel()
- se2.cancel()
- se3.cancel()
- po.reload()
- pr2.load_from_db()
- pr2.cancel()
-
- po.load_from_db()
- po.cancel()
+ frappe.db.rollback()
def test_serial_no_supplier(self):
pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1)
@@ -1044,7 +1029,7 @@
'account': srbnb_account,
'voucher_detail_no': pr.items[1].name
}, pluck="name")
-
+
# check if the entries are not merged into one
# seperate entries should be made since voucher_detail_no is different
self.assertEqual(len(item_one_gl_entry), 1)
@@ -1052,6 +1037,33 @@
frappe.db.set_value('Company', company, 'enable_perpetual_inventory_for_non_stock_items', before_test_value)
+ def test_payment_terms_are_fetched_when_creating_purchase_invoice(self):
+ from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template
+ from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
+ from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order, make_pr_against_po
+ from erpnext.selling.doctype.sales_order.test_sales_order import automatically_fetch_payment_terms, compare_payment_schedules
+
+ automatically_fetch_payment_terms()
+
+ po = create_purchase_order(qty=10, rate=100, do_not_save=1)
+ create_payment_terms_template()
+ po.payment_terms_template = 'Test Receivable Template'
+ po.submit()
+
+ pr = make_pr_against_po(po.name, received_qty=10)
+
+ pi = make_purchase_invoice(qty=10, rate=100, do_not_save=1)
+ pi.items[0].purchase_receipt = pr.name
+ pi.items[0].pr_detail = pr.items[0].name
+ pi.items[0].purchase_order = po.name
+ pi.items[0].po_detail = po.items[0].name
+ pi.insert()
+
+ # self.assertEqual(po.payment_terms_template, pi.payment_terms_template)
+ compare_payment_schedules(self, po, pi)
+
+ automatically_fetch_payment_terms(enable=0)
+
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
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js
index b3e4286..4cd40bf 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js
@@ -29,13 +29,50 @@
};
});
}
+
+ frm.trigger('setup_realtime_progress');
},
+
+ setup_realtime_progress: function(frm) {
+ frappe.realtime.on('item_reposting_progress', data => {
+ if (frm.doc.name !== data.name) {
+ return;
+ }
+
+ if (frm.doc.status == 'In Progress') {
+ frm.doc.current_index = data.current_index;
+ frm.doc.items_to_be_repost = data.items_to_be_repost;
+
+ frm.dashboard.reset();
+ frm.trigger('show_reposting_progress');
+ }
+ });
+ },
+
refresh: function(frm) {
if (frm.doc.status == "Failed" && frm.doc.docstatus==1) {
frm.add_custom_button(__('Restart'), function () {
frm.trigger("restart_reposting");
}).addClass("btn-primary");
}
+
+ frm.trigger('show_reposting_progress');
+ },
+
+ show_reposting_progress: function(frm) {
+ var bars = [];
+
+ let total_count = frm.doc.items_to_be_repost ? JSON.parse(frm.doc.items_to_be_repost).length : 0;
+ let progress = flt(cint(frm.doc.current_index) / total_count * 100, 2) || 0.5;
+ var title = __('Reposting Completed {0}%', [progress]);
+
+ bars.push({
+ 'title': title,
+ 'width': progress + '%',
+ 'progress_class': 'progress-bar-success'
+ });
+
+ frm.dashboard.add_progress(__('Reposting Progress'), bars);
},
restart_reposting: function(frm) {
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
index 071fc86..a800bf8 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
@@ -21,7 +21,10 @@
"allow_zero_rate",
"amended_from",
"error_section",
- "error_log"
+ "error_log",
+ "items_to_be_repost",
+ "distinct_item_and_warehouse",
+ "current_index"
],
"fields": [
{
@@ -142,12 +145,39 @@
"fieldname": "allow_zero_rate",
"fieldtype": "Check",
"label": "Allow Zero Rate"
+ },
+ {
+ "fieldname": "items_to_be_repost",
+ "fieldtype": "Code",
+ "hidden": 1,
+ "label": "Items to Be Repost",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "distinct_item_and_warehouse",
+ "fieldtype": "Code",
+ "hidden": 1,
+ "label": "Distinct Item and Warehouse",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "current_index",
+ "fieldtype": "Int",
+ "hidden": 1,
+ "label": "Current Index",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-12-10 07:52:12.476589",
+ "modified": "2021-07-22 18:59:43.057878",
"modified_by": "Administrator",
"module": "Stock",
"name": "Repost Item Valuation",
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
index 55f2ebb..2e454a5 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
@@ -80,7 +80,7 @@
def repost_sl_entries(doc):
if doc.based_on == 'Transaction':
- repost_future_sle(voucher_type=doc.voucher_type, voucher_no=doc.voucher_no,
+ repost_future_sle(doc=doc, voucher_type=doc.voucher_type, voucher_no=doc.voucher_no,
allow_negative_stock=doc.allow_negative_stock, via_landed_cost_voucher=doc.via_landed_cost_voucher)
else:
repost_future_sle(args=[frappe._dict({
@@ -133,6 +133,6 @@
def get_repost_item_valuation_entries():
return frappe.db.sql(""" SELECT name from `tabRepost Item Valuation`
- WHERE status != 'Completed' and creation <= %s and docstatus = 1
+ WHERE status in ('Queued', 'In Progress') and creation <= %s and docstatus = 1
ORDER BY timestamp(posting_date, posting_time) asc, creation asc
""", now(), as_dict=1)
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index bad7b60..70312bc 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -165,8 +165,14 @@
)
ORDER BY
posting_date desc, posting_time desc, creation desc""",
- (self.item_code, self.company,
- serial_no, serial_no+'\n%', '%\n'+serial_no, '%\n'+serial_no+'\n%'), as_dict=1):
+ (
+ self.item_code, self.company,
+ serial_no,
+ serial_no+'\n%',
+ '%\n'+serial_no,
+ '%\n'+serial_no+'\n%'
+ ),
+ as_dict=1):
if serial_no.upper() in get_serial_nos(sle.serial_no):
if cint(sle.actual_qty) > 0:
sle_dict.setdefault("incoming", []).append(sle)
diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py
index cde7fe0..b9a58cf 100644
--- a/erpnext/stock/doctype/serial_no/test_serial_no.py
+++ b/erpnext/stock/doctype/serial_no/test_serial_no.py
@@ -174,5 +174,23 @@
self.assertEqual(sn_doc.warehouse, "_Test Warehouse - _TC")
self.assertEqual(sn_doc.purchase_document_no, se.name)
+ def test_serial_no_sanitation(self):
+ "Test if Serial No input is sanitised before entering the DB."
+ item_code = "_Test Serialized Item"
+ test_records = frappe.get_test_records('Stock Entry')
+
+ se = frappe.copy_doc(test_records[0])
+ se.get("items")[0].item_code = item_code
+ se.get("items")[0].qty = 3
+ se.get("items")[0].serial_no = " _TS1, _TS2 , _TS3 "
+ se.get("items")[0].transfer_qty = 3
+ se.set_stock_entry_type()
+ se.insert()
+ se.submit()
+
+ self.assertEqual(se.get("items")[0].serial_no, "_TS1\n_TS2\n_TS3")
+
+ frappe.db.rollback()
+
def tearDown(self):
frappe.db.rollback()
\ No newline at end of file
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 90b81dd..3ff42bf 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -76,6 +76,7 @@
self.validate_difference_account()
self.set_job_card_data()
self.set_purpose_for_stock_entry()
+ self.clean_serial_nos()
self.validate_duplicate_serial_no()
if not self.from_bom:
@@ -719,6 +720,10 @@
frappe.throw(_("Multiple items cannot be marked as finished item"))
if self.purpose == "Manufacture":
+ if not finished_items:
+ frappe.throw(_('Finished Good has not set in the stock entry {0}')
+ .format(self.name))
+
allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings",
"overproduction_percentage_for_work_order"))
@@ -1090,13 +1095,13 @@
"is_finished_item": 1
}
- if self.work_order and self.pro_doc.has_batch_no:
+ if self.work_order and self.pro_doc.has_batch_no and cint(frappe.db.get_single_value('Manufacturing Settings',
+ 'make_serial_no_batch_from_work_order', cache=True)):
self.set_batchwise_finished_goods(args, item)
else:
- self.add_finisged_goods(args, item)
+ self.add_finished_goods(args, item)
def set_batchwise_finished_goods(self, args, item):
- qty = flt(self.fg_completed_qty)
filters = {
"reference_name": self.pro_doc.name,
"reference_doctype": self.pro_doc.doctype,
@@ -1105,7 +1110,17 @@
fields = ["qty_to_produce as qty", "produced_qty", "name"]
- for row in frappe.get_all("Batch", filters = filters, fields = fields, order_by="creation asc"):
+ data = frappe.get_all("Batch", filters = filters, fields = fields, order_by="creation asc")
+
+ if not data:
+ self.add_finished_goods(args, item)
+ else:
+ self.add_batchwise_finished_good(data, args, item)
+
+ def add_batchwise_finished_good(self, data, args, item):
+ qty = flt(self.fg_completed_qty)
+
+ for row in data:
batch_qty = flt(row.qty) - flt(row.produced_qty)
if not batch_qty:
continue
@@ -1121,9 +1136,9 @@
args["qty"] = fg_qty
args["batch_no"] = row.name
- self.add_finisged_goods(args, item)
+ self.add_finished_goods(args, item)
- def add_finisged_goods(self, args, item):
+ def add_finished_goods(self, args, item):
self.add_to_stock_entry_detail({
item.name: args
}, bom_no = self.bom_no)
@@ -1775,7 +1790,7 @@
from `tabBatch` b, `tabStock Ledger Entry` sle
where b.expiry_date <= %s
and b.expiry_date is not NULL
- and b.batch_id = sle.batch_no
+ and b.batch_id = sle.batch_no and sle.is_cancelled = 0
group by sle.warehouse, sle.item_code, sle.batch_no""",(nowdate()), as_dict=1)
@frappe.whitelist()
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
index cb939e6..b4f4583 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -60,7 +60,7 @@
if self.batch_no and not self.get("allow_negative_stock"):
batch_bal_after_transaction = flt(frappe.db.sql("""select sum(actual_qty)
from `tabStock Ledger Entry`
- where warehouse=%s and item_code=%s and batch_no=%s""",
+ where is_cancelled =0 and warehouse=%s and item_code=%s and batch_no=%s""",
(self.warehouse, self.item_code, self.batch_no))[0][0])
if batch_bal_after_transaction < 0:
@@ -89,17 +89,16 @@
if item_det.is_stock_item != 1:
frappe.throw(_("Item {0} must be a stock Item").format(self.item_code))
- # check if batch number is required
- if self.voucher_type != 'Stock Reconciliation':
- if item_det.has_batch_no == 1:
- batch_item = self.item_code if self.item_code == item_det.item_name else self.item_code + ":" + item_det.item_name
- if not self.batch_no:
- frappe.throw(_("Batch number is mandatory for Item {0}").format(batch_item))
- elif not frappe.db.get_value("Batch",{"item": self.item_code, "name": self.batch_no}):
- frappe.throw(_("{0} is not a valid Batch Number for Item {1}").format(self.batch_no, batch_item))
+ # check if batch number is valid
+ if item_det.has_batch_no == 1:
+ batch_item = self.item_code if self.item_code == item_det.item_name else self.item_code + ":" + item_det.item_name
+ if not self.batch_no:
+ frappe.throw(_("Batch number is mandatory for Item {0}").format(batch_item))
+ elif not frappe.db.get_value("Batch",{"item": self.item_code, "name": self.batch_no}):
+ frappe.throw(_("{0} is not a valid Batch Number for Item {1}").format(self.batch_no, batch_item))
- elif item_det.has_batch_no == 0 and self.batch_no and self.is_cancelled == 0:
- frappe.throw(_("The Item {0} cannot have Batch").format(self.item_code))
+ elif item_det.has_batch_no == 0 and self.batch_no and self.is_cancelled == 0:
+ frappe.throw(_("The Item {0} cannot have Batch").format(self.item_code))
if item_det.has_variants:
frappe.throw(_("Stock cannot exist for Item {0} since has variants").format(self.item_code),
@@ -153,7 +152,7 @@
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
+ where docstatus = 1 and is_cancelled = 0 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")
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
index a01db80..349e59f 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
@@ -17,6 +17,14 @@
}
}
});
+ frm.set_query("batch_no", "items", function(doc, cdt, cdn) {
+ var item = locals[cdt][cdn];
+ return {
+ filters: {
+ 'item': item.item_code
+ }
+ };
+ });
if (frm.doc.company) {
erpnext.queries.setup_queries(frm, "Warehouse", function() {
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 2e09286..324bb7a 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -31,6 +31,7 @@
self.validate_expense_account()
self.validate_customer_provided_item()
self.set_zero_value_for_customer_provided_items()
+ self.clean_serial_nos()
self.set_total_qty_and_amount()
self.validate_putaway_capacity()
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index 84cdc49..c192582 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -16,6 +16,7 @@
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
+
class TestStockReconciliation(unittest.TestCase):
@classmethod
def setUpClass(self):
@@ -352,6 +353,26 @@
dn2.cancel()
pr1.cancel()
+ def test_valid_batch(self):
+ create_batch_item_with_batch("Testing Batch Item 1", "001")
+ create_batch_item_with_batch("Testing Batch Item 2", "002")
+ sr = create_stock_reconciliation(item_code="Testing Batch Item 1", qty=1, rate=100, batch_no="002"
+ , do_not_submit=True)
+ self.assertRaises(frappe.ValidationError, sr.submit)
+
+def create_batch_item_with_batch(item_name, batch_id):
+ batch_item_doc = create_item(item_name, is_stock_item=1)
+ if not batch_item_doc.has_batch_no:
+ batch_item_doc.has_batch_no = 1
+ batch_item_doc.create_new_batch = 1
+ batch_item_doc.save(ignore_permissions=True)
+
+ if not frappe.db.exists('Batch', batch_id):
+ b = frappe.new_doc('Batch')
+ b.item = item_name
+ b.batch_id = batch_id
+ b.save()
+
def insert_existing_sle(warehouse):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json
index 2a9dcfb..f75cb56 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.json
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.json
@@ -18,6 +18,7 @@
"section_break_9",
"over_delivery_receipt_allowance",
"role_allowed_to_over_deliver_receive",
+ "mr_qty_allowance",
"column_break_12",
"auto_insert_price_list_rate_if_missing",
"allow_negative_stock",
@@ -283,6 +284,12 @@
"fieldtype": "Select",
"label": "Action If Quality Inspection Is Rejected",
"options": "Stop\nWarn"
+ },
+ {
+ "description": "The percentage you are allowed to transfer more against the quantity ordered. For example, if you have ordered 100 units, and your Allowance is 10%, then you are allowed transfer 110 units.",
+ "fieldname": "mr_qty_allowance",
+ "fieldtype": "Float",
+ "label": "Over Transfer Allowance"
}
],
"icon": "icon-cog",
@@ -290,7 +297,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2021-07-10 16:17:42.159829",
+ "modified": "2021-06-28 17:02:26.683002",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Settings",
@@ -310,4 +317,4 @@
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index ca174a3..a0fbcec 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -74,9 +74,7 @@
update_party_blanket_order(args, out)
- if not doc or cint(doc.get('is_return')) == 0:
- # get price list rate only if the invoice is not a credit or debit note
- get_price_list_rate(args, item, out)
+ out.update(get_price_list_rate(args, item))
if args.customer and cint(args.is_pos):
out.update(get_pos_profile_item_details(args.company, args, update_data=True))
@@ -288,6 +286,7 @@
"warehouse": warehouse,
"income_account": get_default_income_account(args, item_defaults, item_group_defaults, brand_defaults),
"expense_account": expense_account or get_default_expense_account(args, item_defaults, item_group_defaults, brand_defaults) ,
+ "discount_account": None or get_default_discount_account(args, item_defaults),
"cost_center": get_default_cost_center(args, item_defaults, item_group_defaults, brand_defaults),
'has_serial_no': item.has_serial_no,
'has_batch_no': item.has_batch_no,
@@ -313,8 +312,8 @@
"transaction_date": args.get("transaction_date"),
"against_blanket_order": args.get("against_blanket_order"),
"bom_no": item.get("default_bom"),
- "weight_per_unit": args.get("weight_per_unit") or item.get("weight_per_unit"),
- "weight_uom": args.get("weight_uom") or item.get("weight_uom")
+ "weight_per_unit": item.get("weight_per_unit"),
+ "weight_uom": item.get("weight_uom")
})
if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
@@ -441,7 +440,7 @@
if item_tax_templates is None:
item_tax_templates = {}
-
+
if item_rates is None:
item_rates = {}
@@ -590,6 +589,10 @@
or brand.get("expense_account")
or args.expense_account)
+def get_default_discount_account(args, item):
+ return (item.get("default_discount_account")
+ or args.discount_account)
+
def get_default_deferred_account(args, item, fieldname=None):
if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
return (item.get(fieldname)
@@ -639,7 +642,10 @@
or item_group.get("default_supplier")
or brand.get("default_supplier"))
-def get_price_list_rate(args, item_doc, out):
+def get_price_list_rate(args, item_doc, out=None):
+ if out is None:
+ out = frappe._dict()
+
meta = frappe.get_meta(args.parenttype or args.doctype)
if meta.get_field("currency") or args.get('currency'):
@@ -652,17 +658,17 @@
if meta.get_field("currency"):
validate_conversion_rate(args, meta)
- price_list_rate = get_price_list_rate_for(args, item_doc.name) or 0
+ price_list_rate = get_price_list_rate_for(args, item_doc.name)
# variant
- if not price_list_rate and item_doc.variant_of:
+ if price_list_rate is None and item_doc.variant_of:
price_list_rate = get_price_list_rate_for(args, item_doc.variant_of)
# insert in database
- if not price_list_rate:
+ if price_list_rate is None:
if args.price_list and args.rate:
insert_item_price(args)
- return {}
+ return out
out.price_list_rate = flt(price_list_rate) * flt(args.plc_conversion_rate) \
/ flt(args.conversion_rate)
@@ -672,6 +678,8 @@
out.update(get_last_purchase_details(item_doc.name,
args.name, args.conversion_rate))
+ return out
+
def insert_item_price(args):
"""Insert Item Price if Price List and Price List Rate are specified and currency is the same"""
if frappe.db.get_value("Price List", args.price_list, "currency", cache=True) == args.currency \
@@ -807,10 +815,14 @@
def validate_conversion_rate(args, meta):
from erpnext.controllers.accounts_controller import validate_conversion_rate
- if (not args.conversion_rate
- and args.currency==frappe.get_cached_value('Company', args.company, "default_currency")):
+ company_currency = frappe.get_cached_value('Company', args.company, "default_currency")
+ if (not args.conversion_rate and args.currency==company_currency):
args.conversion_rate = 1.0
+ if (not args.ignore_conversion_rate and args.conversion_rate == 1 and args.currency!=company_currency):
+ args.conversion_rate = get_exchange_rate(args.currency,
+ company_currency, args.transaction_date, "for_buying") or 1.0
+
# validate currency conversion rate
validate_conversion_rate(args.currency, args.conversion_rate,
meta.get_label("conversion_rate"), args.company)
@@ -1070,9 +1082,8 @@
}
def apply_price_list_on_item(args):
- item_details = frappe._dict()
item_doc = frappe.get_doc("Item", args.item_code)
- get_price_list_rate(args, item_doc, item_details)
+ item_details = get_price_list_rate(args, item_doc)
item_details.update(get_pricing_rule_for_item(args, item_details.price_list_rate))
diff --git a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py
index 14d543b..bfc4471 100644
--- a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py
+++ b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py
@@ -22,6 +22,7 @@
data = []
filters = {
+ "is_cancelled": 0,
"company": report_filters.company,
"posting_date": ("<=", report_filters.as_on_date)
}
@@ -34,7 +35,7 @@
key = (d.voucher_type, d.voucher_no)
gl_data = voucher_wise_gl_data.get(key) or {}
d.account_value = gl_data.get("account_value", 0)
- d.difference_value = (d.stock_value - d.account_value)
+ d.difference_value = abs(d.stock_value - d.account_value)
if abs(d.difference_value) > 0.1:
data.append(d)
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index b6a8063..9e56ad4 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -16,8 +16,6 @@
is_reposting_item_valuation_in_progress()
if not filters: filters = {}
- validate_filters(filters)
-
from_date = filters.get('from_date')
to_date = filters.get('to_date')
@@ -295,12 +293,6 @@
return dict((d.parent + d.warehouse, d) for d in item_reorder_details)
-def validate_filters(filters):
- if not (filters.get("item_code") or filters.get("warehouse")):
- sle_count = flt(frappe.db.sql("""select count(name) from `tabStock Ledger Entry`""")[0][0])
- if sle_count > 500000:
- frappe.throw(_("Please set filter based on Item or Warehouse due to a large amount of entries."))
-
def get_variants_attributes():
'''Return all item variant attributes.'''
return [i.name for i in frappe.get_all('Item Attribute')]
diff --git a/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py b/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py
index 5873a7a..4108a57 100644
--- a/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py
+++ b/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py
@@ -69,7 +69,7 @@
i.stock_uom, sle.actual_qty, sle.stock_value_difference,
sle.voucher_no, sle.voucher_type
from `tabStock Ledger Entry` sle, `tabItem` i
- where sle.item_code=i.name and sle.actual_qty < 0 %s""" % conditions, values, as_dict=1):
+ where sle.is_cancelled = 0 and sle.item_code=i.name and sle.actual_qty < 0 %s""" % conditions, values, as_dict=1):
consumed_details.setdefault(d.item_code, []).append(d)
return consumed_details
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index c15d1ed..8f9ec46 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -127,30 +127,24 @@
sle.submit()
return sle
-def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negative_stock=None, via_landed_cost_voucher=False):
+def repost_future_sle(args=None, doc=None, voucher_type=None, voucher_no=None, allow_negative_stock=None, via_landed_cost_voucher=False):
if not args and voucher_type and voucher_no:
- args = get_args_for_voucher(voucher_type, voucher_no)
+ args = get_items_to_be_repost(voucher_type, voucher_no, doc)
- distinct_item_warehouses = {}
- for i, d in enumerate(args):
- distinct_item_warehouses.setdefault((d.item_code, d.warehouse), frappe._dict({
- "reposting_status": False,
- "sle": d,
- "args_idx": i
- }))
+ distinct_item_warehouses = get_distinct_item_warehouse(args, doc)
- i = 0
+ i = get_current_index(doc) or 0
while i < len(args):
obj = update_entries_after({
- "item_code": args[i].item_code,
- "warehouse": args[i].warehouse,
- "posting_date": args[i].posting_date,
- "posting_time": args[i].posting_time,
+ "item_code": args[i].get('item_code'),
+ "warehouse": args[i].get('warehouse'),
+ "posting_date": args[i].get('posting_date'),
+ "posting_time": args[i].get('posting_time'),
"creation": args[i].get("creation"),
"distinct_item_warehouses": distinct_item_warehouses
}, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher)
- distinct_item_warehouses[(args[i].item_code, args[i].warehouse)].reposting_status = True
+ distinct_item_warehouses[(args[i].get('item_code'), args[i].get('warehouse'))].reposting_status = True
if obj.new_items_found:
for item_wh, data in iteritems(distinct_item_warehouses):
@@ -159,11 +153,35 @@
args.append(data.sle)
elif data.sle_changed and not data.reposting_status:
args[data.args_idx] = data.sle
-
+
data.sle_changed = False
i += 1
-def get_args_for_voucher(voucher_type, voucher_no):
+ if doc and i % 2 == 0:
+ update_args_in_repost_item_valuation(doc, i, args, distinct_item_warehouses)
+
+ if doc and args:
+ update_args_in_repost_item_valuation(doc, i, args, distinct_item_warehouses)
+
+def update_args_in_repost_item_valuation(doc, index, args, distinct_item_warehouses):
+ frappe.db.set_value(doc.doctype, doc.name, {
+ 'items_to_be_repost': json.dumps(args, default=str),
+ 'distinct_item_and_warehouse': json.dumps({str(k): v for k,v in distinct_item_warehouses.items()}, default=str),
+ 'current_index': index
+ })
+
+ frappe.db.commit()
+
+ frappe.publish_realtime('item_reposting_progress', {
+ 'name': doc.name,
+ 'items_to_be_repost': json.dumps(args, default=str),
+ 'current_index': index
+ })
+
+def get_items_to_be_repost(voucher_type, voucher_no, doc=None):
+ if doc and doc.items_to_be_repost:
+ return json.loads(doc.items_to_be_repost) or []
+
return frappe.db.get_all("Stock Ledger Entry",
filters={"voucher_type": voucher_type, "voucher_no": voucher_no},
fields=["item_code", "warehouse", "posting_date", "posting_time", "creation"],
@@ -171,6 +189,25 @@
group_by="item_code, warehouse"
)
+def get_distinct_item_warehouse(args=None, doc=None):
+ distinct_item_warehouses = {}
+ if doc and doc.distinct_item_and_warehouse:
+ distinct_item_warehouses = json.loads(doc.distinct_item_and_warehouse)
+ distinct_item_warehouses = {frappe.safe_eval(k): frappe._dict(v) for k, v in distinct_item_warehouses.items()}
+ else:
+ for i, d in enumerate(args):
+ distinct_item_warehouses.setdefault((d.item_code, d.warehouse), frappe._dict({
+ "reposting_status": False,
+ "sle": d,
+ "args_idx": i
+ }))
+
+ return distinct_item_warehouses
+
+def get_current_index(doc=None):
+ if doc and doc.current_index:
+ return doc.current_index
+
class update_entries_after(object):
"""
update valution rate and qty after transaction
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index 8a6a3a3..b57b2aa 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -314,13 +314,16 @@
for row_idx, row in enumerate(result):
data = row.items() if is_dict_obj else enumerate(row)
for key, value in data:
- if key not in convertible_columns or not conversion_factors[row_idx-1]:
+ if key not in convertible_columns:
continue
+ # If no conversion factor for the UOM, defaults to 1
+ if not conversion_factors[row_idx]:
+ conversion_factors[row_idx] = 1
if convertible_columns.get(key) == 'rate':
- new_value = flt(value) * conversion_factors[row_idx-1]
+ new_value = flt(value) * conversion_factors[row_idx]
else:
- new_value = flt(value) / conversion_factors[row_idx-1]
+ new_value = flt(value) / conversion_factors[row_idx]
if not is_dict_obj:
row.insert(key+1, new_value)
@@ -386,4 +389,4 @@
reposting_in_progress = frappe.db.exists("Repost Item Valuation",
{'docstatus': 1, 'status': ['in', ['Queued','In Progress']]})
if reposting_in_progress:
- frappe.msgprint(_("Item valuation reposting in progress. Report might show incorrect item valuation."), alert=1)
\ No newline at end of file
+ frappe.msgprint(_("Item valuation reposting in progress. Report might show incorrect item valuation."), alert=1)
diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py
index 9c69deb..b48925d 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -222,6 +222,10 @@
}).insert(ignore_permissions=True)
return replicated_issue.name
+
+ def reset_issue_metrics(self):
+ self.db_set("resolution_time", None)
+ self.db_set("user_resolution_time", None)
def before_insert(self):
if frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):
@@ -231,8 +235,7 @@
self.set_response_and_resolution_time()
def set_response_and_resolution_time(self, priority=None, service_level_agreement=None):
- service_level_agreement = get_active_service_level_agreement_for(priority=priority,
- customer=self.customer, service_level_agreement=service_level_agreement)
+ service_level_agreement = get_active_service_level_agreement_for(self)
if not service_level_agreement:
if frappe.db.get_value("Issue", self.name, "service_level_agreement"):
@@ -243,7 +246,8 @@
frappe.throw(_("This Service Level Agreement is specific to Customer {0}").format(service_level_agreement.customer))
self.service_level_agreement = service_level_agreement.name
- self.priority = service_level_agreement.default_priority if not priority else priority
+ if not self.priority:
+ self.priority = service_level_agreement.default_priority
priority = get_priority(self)
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.json b/erpnext/support/doctype/service_level_agreement/service_level_agreement.json
index 939c199..1678f04 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.json
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.json
@@ -18,6 +18,10 @@
"entity_type",
"column_break_10",
"entity",
+ "filters_section",
+ "condition",
+ "column_break_15",
+ "condition_description",
"agreement_details_section",
"start_date",
"active",
@@ -171,10 +175,30 @@
"fieldtype": "Table",
"label": "Pause SLA On",
"options": "Pause SLA On Status"
+ },
+ {
+ "fieldname": "filters_section",
+ "fieldtype": "Section Break",
+ "label": "Assignment Condition"
+ },
+ {
+ "fieldname": "column_break_15",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "condition",
+ "fieldtype": "Code",
+ "label": "Condition",
+ "options": "Python"
+ },
+ {
+ "fieldname": "condition_description",
+ "fieldtype": "HTML",
+ "options": "<p><strong>Condition Examples:</strong></p>\n<pre>doc.status==\"Open\"<br>doc.due_date==nowdate()<br>doc.total > 40000\n</pre>"
}
],
"links": [],
- "modified": "2020-06-10 12:30:15.050785",
+ "modified": "2021-07-27 11:16:45.596579",
"modified_by": "Administrator",
"module": "Support",
"name": "Service Level Agreement",
@@ -208,4 +232,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
index 70c4696..ec0237e 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
@@ -3,10 +3,12 @@
# For license information, please see license.txt
from __future__ import unicode_literals
+
import frappe
from frappe.model.document import Document
from frappe import _
-from frappe.utils import getdate, get_weekdays, get_link_to_form
+from frappe.utils import getdate, get_weekdays, get_link_to_form, nowdate
+from frappe.utils.safe_exec import get_safe_globals
class ServiceLevelAgreement(Document):
@@ -14,6 +16,7 @@
self.validate_doc()
self.check_priorities()
self.check_support_and_resolution()
+ self.validate_condition()
def check_priorities(self):
default_priority = []
@@ -92,6 +95,14 @@
if frappe.db.exists("Service Level Agreement", {"entity_type": self.entity_type, "entity": self.entity, "name": ["!=", self.name]}):
frappe.throw(_("Service Level Agreement with Entity Type {0} and Entity {1} already exists.").format(self.entity_type, self.entity))
+ def validate_condition(self):
+ temp_doc = frappe.new_doc('Issue')
+ if self.condition:
+ try:
+ frappe.safe_eval(self.condition, None, get_context(temp_doc))
+ except Exception:
+ frappe.throw(_("The Condition '{0}' is invalid").format(self.condition))
+
def get_service_level_agreement_priority(self, priority):
priority = frappe.get_doc("Service Level Priority", {"priority": priority, "parent": self.name})
@@ -112,7 +123,7 @@
if doc.end_date and getdate(doc.end_date) < getdate(frappe.utils.getdate()):
frappe.db.set_value("Service Level Agreement", service_level_agreement.name, "active", 0)
-def get_active_service_level_agreement_for(priority, customer=None, service_level_agreement=None):
+def get_active_service_level_agreement_for(doc):
if not frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):
return
@@ -121,23 +132,42 @@
["Service Level Agreement", "enable", "=", 1]
]
- if priority:
- filters.append(["Service Level Priority", "priority", "=", priority])
+ if doc.get('priority'):
+ filters.append(["Service Level Priority", "priority", "=", doc.get('priority')])
+ customer = doc.get('customer')
or_filters = [
["Service Level Agreement", "entity", "in", [customer, get_customer_group(customer), get_customer_territory(customer)]]
]
+
+ service_level_agreement = doc.get('service_level_agreement')
if service_level_agreement:
or_filters = [
- ["Service Level Agreement", "name", "=", service_level_agreement],
+ ["Service Level Agreement", "name", "=", doc.get('service_level_agreement')],
]
- or_filters.append(["Service Level Agreement", "default_service_level_agreement", "=", 1])
+ default_sla_filter = filters + [["Service Level Agreement", "default_service_level_agreement", "=", 1]]
+ default_sla = frappe.get_all("Service Level Agreement", filters=default_sla_filter,
+ fields=["name", "default_priority", "condition"])
- agreement = frappe.get_list("Service Level Agreement", filters=filters, or_filters=or_filters,
- fields=["name", "default_priority"])
+ filters += [["Service Level Agreement", "default_service_level_agreement", "=", 0]]
+ agreements = frappe.get_all("Service Level Agreement", filters=filters, or_filters=or_filters,
+ fields=["name", "default_priority", "condition"])
+
+ # check if the current document on which SLA is to be applied fulfills all the conditions
+ filtered_agreements = []
+ for agreement in agreements:
+ condition = agreement.get('condition')
+ if not condition or (condition and frappe.safe_eval(condition, None, get_context(doc))):
+ filtered_agreements.append(agreement)
- return agreement[0] if agreement else None
+ # if any default sla
+ filtered_agreements += default_sla
+
+ return filtered_agreements[0] if filtered_agreements else None
+
+def get_context(doc):
+ return {"doc": doc.as_dict(), "nowdate": nowdate, "frappe": frappe._dict(utils=get_safe_globals().get("frappe").get("utils"))}
def get_customer_group(customer):
if customer:
diff --git a/erpnext/telephony/doctype/call_log/call_log.py b/erpnext/telephony/doctype/call_log/call_log.py
index 4d553df..c00dfa9 100644
--- a/erpnext/telephony/doctype/call_log/call_log.py
+++ b/erpnext/telephony/doctype/call_log/call_log.py
@@ -142,7 +142,7 @@
for log in logs:
call_log = frappe.get_doc('Call Log', log)
call_log.add_link(link_type=doc.doctype, link_name=doc.name)
- call_log.save()
+ call_log.save(ignore_permissions=True)
frappe.db.commit()
except Exception:
frappe.log_error(title=_('Error during caller information update'))
diff --git a/erpnext/templates/generators/item_group.html b/erpnext/templates/generators/item_group.html
index 393c3a4..9050cc3 100644
--- a/erpnext/templates/generators/item_group.html
+++ b/erpnext/templates/generators/item_group.html
@@ -9,7 +9,7 @@
{% endblock %}
{% block page_content %}
-<div class="item-group-content" itemscope itemtype="http://schema.org/Product">
+<div class="item-group-content" itemscope itemtype="http://schema.org/Product" data-item-group="{{ name }}">
<div class="item-group-slideshow">
{% if slideshow %}<!-- slideshow -->
{{ web_block(
@@ -127,15 +127,36 @@
</script>
</div>
</div>
- <div class="row">
- <div class="col-12">
+ <div class="row mt-6">
+ <div class="col-3">
+ </div>
+ <div class="col-9">
{% if frappe.form_dict.start|int > 0 %}
- <button class="btn btn-outline-secondary btn-prev" data-start="{{ frappe.form_dict.start|int - page_length }}">{{ _("Prev") }}</button>
+ <button class="btn btn-outline-secondary btn-prev" data-start="{{ frappe.form_dict.start|int - page_length }}">
+ {{ _("Prev") }}
+ </button>
{% endif %}
{% if items|length >= page_length %}
- <button class="btn btn-outline-secondary btn-next" data-start="{{ frappe.form_dict.start|int + page_length }}">{{ _("Next") }}</button>
+ <button class="btn btn-outline-secondary btn-next" data-start="{{ frappe.form_dict.start|int + page_length }}"
+ style="float: right;">
+ {{ _("Next") }}
+ </button>
{% endif %}
</div>
</div>
</div>
+
+<script>
+ frappe.ready(() => {
+ $('.btn-prev, .btn-next').click((e) => {
+ const $btn = $(e.target);
+ $btn.prop('disabled', true);
+ const start = $btn.data('start');
+ let query_params = frappe.utils.get_query_params();
+ query_params.start = start;
+ let path = window.location.pathname + '?' + frappe.utils.get_url_from_dict(query_params);
+ window.location.href = path;
+ });
+ });
+</script>
{% endblock %}
\ No newline at end of file
diff --git a/erpnext/www/all-products/index.js b/erpnext/www/all-products/index.js
index 0721056..1c641b5 100644
--- a/erpnext/www/all-products/index.js
+++ b/erpnext/www/all-products/index.js
@@ -124,6 +124,10 @@
attribute_filters: if_key_exists(attribute_filters)
};
+ const item_group = $(".item-group-content").data('item-group');
+ if (item_group) {
+ Object.assign(field_filters, { item_group });
+ }
return new Promise((resolve, reject) => {
frappe.call('erpnext.portal.product_configurator.utils.get_products_html_for_website', args)
.then(r => {