Merge remote-tracking branch 'upstream/develop' into remove-india
This commit is contained in:
commit
05351bee8b
2
.github/workflows/docs-checker.yml
vendored
2
.github/workflows/docs-checker.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
|||||||
- name: 'Setup Environment'
|
- name: 'Setup Environment'
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
python-version: '3.10'
|
||||||
|
|
||||||
- name: 'Clone repo'
|
- name: 'Clone repo'
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
16
.github/workflows/linters.yml
vendored
16
.github/workflows/linters.yml
vendored
@ -11,10 +11,10 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Set up Python 3.8
|
- name: Set up Python 3.10
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
python-version: '3.10'
|
||||||
|
|
||||||
- name: Install and Run Pre-commit
|
- name: Install and Run Pre-commit
|
||||||
uses: pre-commit/action@v2.0.3
|
uses: pre-commit/action@v2.0.3
|
||||||
@ -22,10 +22,8 @@ jobs:
|
|||||||
- name: Download Semgrep rules
|
- name: Download Semgrep rules
|
||||||
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
|
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
|
||||||
|
|
||||||
- uses: returntocorp/semgrep-action@v1
|
- name: Download semgrep
|
||||||
env:
|
run: pip install semgrep==0.97.0
|
||||||
SEMGREP_TIMEOUT: 120
|
|
||||||
with:
|
- name: Run Semgrep rules
|
||||||
config: >-
|
run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness
|
||||||
r/python.lang.correctness
|
|
||||||
./frappe-semgrep-rules/rules
|
|
||||||
|
25
.github/workflows/patch.yml
vendored
25
.github/workflows/patch.yml
vendored
@ -35,9 +35,9 @@ jobs:
|
|||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v2
|
uses: "gabrielfalcao/pyenv-action@v9"
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
versions: 3.10:latest, 3.7:latest
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
@ -52,7 +52,7 @@ jobs:
|
|||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/pip
|
path: ~/.cache/pip
|
||||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pip-
|
${{ runner.os }}-pip-
|
||||||
${{ runner.os }}-
|
${{ runner.os }}-
|
||||||
@ -82,7 +82,10 @@ jobs:
|
|||||||
${{ runner.os }}-yarn-
|
${{ runner.os }}-yarn-
|
||||||
|
|
||||||
- name: Install
|
- name: Install
|
||||||
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
run: |
|
||||||
|
pip install frappe-bench
|
||||||
|
pyenv global $(pyenv versions | grep '3.10')
|
||||||
|
bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
||||||
env:
|
env:
|
||||||
DB: mariadb
|
DB: mariadb
|
||||||
TYPE: server
|
TYPE: server
|
||||||
@ -96,18 +99,23 @@ jobs:
|
|||||||
git -C "apps/frappe" remote set-url upstream https://github.com/frappe/frappe.git
|
git -C "apps/frappe" remote set-url upstream https://github.com/frappe/frappe.git
|
||||||
git -C "apps/erpnext" remote set-url upstream https://github.com/frappe/erpnext.git
|
git -C "apps/erpnext" remote set-url upstream https://github.com/frappe/erpnext.git
|
||||||
|
|
||||||
|
pyenv global $(pyenv versions | grep '3.7')
|
||||||
for version in $(seq 12 13)
|
for version in $(seq 12 13)
|
||||||
do
|
do
|
||||||
echo "Updating to v$version"
|
echo "Updating to v$version"
|
||||||
branch_name="version-$version-hotfix"
|
branch_name="version-$version-hotfix"
|
||||||
|
|
||||||
|
|
||||||
git -C "apps/frappe" fetch --depth 1 upstream $branch_name:$branch_name
|
git -C "apps/frappe" fetch --depth 1 upstream $branch_name:$branch_name
|
||||||
git -C "apps/erpnext" fetch --depth 1 upstream $branch_name:$branch_name
|
git -C "apps/erpnext" fetch --depth 1 upstream $branch_name:$branch_name
|
||||||
|
|
||||||
git -C "apps/frappe" checkout -q -f $branch_name
|
git -C "apps/frappe" checkout -q -f $branch_name
|
||||||
git -C "apps/erpnext" checkout -q -f $branch_name
|
git -C "apps/erpnext" checkout -q -f $branch_name
|
||||||
|
|
||||||
bench setup requirements --python
|
rm -rf ~/frappe-bench/env
|
||||||
|
bench setup env
|
||||||
|
bench pip install -e ./apps/erpnext
|
||||||
|
|
||||||
bench --site test_site migrate
|
bench --site test_site migrate
|
||||||
done
|
done
|
||||||
|
|
||||||
@ -115,5 +123,10 @@ jobs:
|
|||||||
echo "Updating to latest version"
|
echo "Updating to latest version"
|
||||||
git -C "apps/frappe" checkout -q -f "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}"
|
git -C "apps/frappe" checkout -q -f "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}"
|
||||||
git -C "apps/erpnext" checkout -q -f "$GITHUB_SHA"
|
git -C "apps/erpnext" checkout -q -f "$GITHUB_SHA"
|
||||||
bench setup requirements --python
|
|
||||||
|
pyenv global $(pyenv versions | grep '3.10')
|
||||||
|
rm -rf ~/frappe-bench/env
|
||||||
|
bench -v setup env
|
||||||
|
bench pip install -e ./apps/erpnext
|
||||||
|
|
||||||
bench --site test_site migrate
|
bench --site test_site migrate
|
||||||
|
6
.github/workflows/server-tests-mariadb.yml
vendored
6
.github/workflows/server-tests-mariadb.yml
vendored
@ -39,7 +39,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
container: [1, 2, 3]
|
container: [1, 2, 3, 4]
|
||||||
|
|
||||||
name: Python Unit Tests
|
name: Python Unit Tests
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ jobs:
|
|||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
python-version: '3.10'
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
@ -74,7 +74,7 @@ jobs:
|
|||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/pip
|
path: ~/.cache/pip
|
||||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pip-
|
${{ runner.os }}-pip-
|
||||||
${{ runner.os }}-
|
${{ runner.os }}-
|
||||||
|
6
.github/workflows/server-tests-postgres.yml
vendored
6
.github/workflows/server-tests-postgres.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
container: [1, 2, 3]
|
container: [1]
|
||||||
|
|
||||||
name: Python Unit Tests
|
name: Python Unit Tests
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ jobs:
|
|||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
python-version: '3.10'
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
@ -61,7 +61,7 @@ jobs:
|
|||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/pip
|
path: ~/.cache/pip
|
||||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pip-
|
${{ runner.os }}-pip-
|
||||||
${{ runner.os }}-
|
${{ runner.os }}-
|
||||||
|
33
CODEOWNERS
33
CODEOWNERS
@ -3,33 +3,30 @@
|
|||||||
# These owners will be the default owners for everything in
|
# These owners will be the default owners for everything in
|
||||||
# the repo. Unless a later match takes precedence,
|
# the repo. Unless a later match takes precedence,
|
||||||
|
|
||||||
erpnext/accounts/ @nextchamp-saqib @deepeshgarg007
|
erpnext/accounts/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
|
||||||
erpnext/assets/ @nextchamp-saqib @deepeshgarg007
|
erpnext/assets/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
|
||||||
erpnext/erpnext_integrations/ @nextchamp-saqib
|
|
||||||
erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007
|
erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007
|
||||||
erpnext/regional @nextchamp-saqib @deepeshgarg007
|
erpnext/regional @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
|
||||||
erpnext/selling @nextchamp-saqib @deepeshgarg007
|
erpnext/selling @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
|
||||||
erpnext/support/ @nextchamp-saqib @deepeshgarg007
|
erpnext/support/ @nextchamp-saqib @deepeshgarg007
|
||||||
pos* @nextchamp-saqib
|
pos* @nextchamp-saqib
|
||||||
|
|
||||||
erpnext/buying/ @marination @rohitwaghchaure @ankush
|
erpnext/buying/ @marination @rohitwaghchaure @s-aga-r
|
||||||
erpnext/e_commerce/ @marination
|
erpnext/e_commerce/ @marination
|
||||||
erpnext/maintenance/ @marination @rohitwaghchaure
|
erpnext/maintenance/ @marination @rohitwaghchaure @s-aga-r
|
||||||
erpnext/manufacturing/ @marination @rohitwaghchaure @ankush
|
erpnext/manufacturing/ @marination @rohitwaghchaure @s-aga-r
|
||||||
erpnext/portal/ @marination
|
erpnext/portal/ @marination
|
||||||
erpnext/quality_management/ @marination @rohitwaghchaure
|
erpnext/quality_management/ @marination @rohitwaghchaure @s-aga-r
|
||||||
erpnext/shopping_cart/ @marination
|
erpnext/shopping_cart/ @marination
|
||||||
erpnext/stock/ @marination @rohitwaghchaure @ankush
|
erpnext/stock/ @marination @rohitwaghchaure @s-aga-r
|
||||||
|
|
||||||
erpnext/crm/ @ruchamahabal @pateljannat
|
erpnext/crm/ @NagariaHussain
|
||||||
erpnext/education/ @ruchamahabal @pateljannat
|
erpnext/education/ @rutwikhdev
|
||||||
erpnext/hr/ @ruchamahabal @pateljannat
|
erpnext/projects/ @ruchamahabal
|
||||||
erpnext/payroll @ruchamahabal @pateljannat
|
|
||||||
erpnext/projects/ @ruchamahabal @pateljannat
|
|
||||||
|
|
||||||
erpnext/controllers/ @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination @ankush
|
erpnext/controllers/ @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination
|
||||||
erpnext/patches/ @deepeshgarg007 @nextchamp-saqib @marination @ankush
|
erpnext/patches/ @deepeshgarg007 @nextchamp-saqib @marination
|
||||||
erpnext/public/ @nextchamp-saqib @marination
|
erpnext/public/ @nextchamp-saqib @marination
|
||||||
|
|
||||||
.github/ @ankush
|
.github/ @ankush
|
||||||
requirements.txt @gavindsouza
|
pyproject.toml @gavindsouza @ankush
|
||||||
|
@ -1 +0,0 @@
|
|||||||
hypothesis~=6.31.0
|
|
@ -476,6 +476,13 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
|||||||
this.frm.trigger("calculate_timesheet_totals");
|
this.frm.trigger("calculate_timesheet_totals");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is_cash_or_non_trade_discount() {
|
||||||
|
this.frm.set_df_property("additional_discount_account", "hidden", 1 - this.frm.doc.is_cash_or_non_trade_discount);
|
||||||
|
if (!this.frm.doc.is_cash_or_non_trade_discount) {
|
||||||
|
this.frm.set_value("additional_discount_account", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// for backward compatibility: combine new and previous states
|
// for backward compatibility: combine new and previous states
|
||||||
|
@ -106,6 +106,7 @@
|
|||||||
"loyalty_redemption_cost_center",
|
"loyalty_redemption_cost_center",
|
||||||
"section_break_49",
|
"section_break_49",
|
||||||
"apply_discount_on",
|
"apply_discount_on",
|
||||||
|
"is_cash_or_non_trade_discount",
|
||||||
"base_discount_amount",
|
"base_discount_amount",
|
||||||
"additional_discount_account",
|
"additional_discount_account",
|
||||||
"column_break_51",
|
"column_break_51",
|
||||||
@ -1766,8 +1767,6 @@
|
|||||||
"width": "50%"
|
"width": "50%"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "sales_partner.commission_rate",
|
|
||||||
"fetch_if_empty": 1,
|
|
||||||
"fieldname": "commission_rate",
|
"fieldname": "commission_rate",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
@ -1966,7 +1965,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "additional_discount_account",
|
"fieldname": "additional_discount_account",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Additional Discount Account",
|
"label": "Discount Account",
|
||||||
"options": "Account"
|
"options": "Account"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -2004,6 +2003,13 @@
|
|||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Amount Eligible for Commission",
|
"label": "Amount Eligible for Commission",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval: doc.apply_discount_on == \"Grand Total\"",
|
||||||
|
"fieldname": "is_cash_or_non_trade_discount",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Is Cash or Non Trade Discount"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
@ -2016,7 +2022,7 @@
|
|||||||
"link_fieldname": "consolidated_invoice"
|
"link_fieldname": "consolidated_invoice"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2022-06-10 03:52:51.409913",
|
"modified": "2022-06-16 16:22:44.870575",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice",
|
"name": "Sales Invoice",
|
||||||
|
@ -1008,7 +1008,7 @@ class SalesInvoice(SellingController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if grand_total and not self.is_internal_transfer():
|
if grand_total and not self.is_internal_transfer():
|
||||||
# Didnot use base_grand_total to book rounding loss gle
|
# Did not use base_grand_total to book rounding loss gle
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
self.get_gl_dict(
|
self.get_gl_dict(
|
||||||
{
|
{
|
||||||
@ -1033,6 +1033,22 @@ class SalesInvoice(SellingController):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.apply_discount_on == "Grand Total" and self.get("is_cash_or_discount_account"):
|
||||||
|
gl_entries.append(
|
||||||
|
self.get_gl_dict(
|
||||||
|
{
|
||||||
|
"account": self.additional_discount_account,
|
||||||
|
"against": self.debit_to,
|
||||||
|
"debit": self.base_discount_amount,
|
||||||
|
"debit_in_account_currency": self.discount_amount,
|
||||||
|
"cost_center": self.cost_center,
|
||||||
|
"project": self.project,
|
||||||
|
},
|
||||||
|
self.currency,
|
||||||
|
item=self,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def make_tax_gl_entries(self, gl_entries):
|
def make_tax_gl_entries(self, gl_entries):
|
||||||
enable_discount_accounting = cint(
|
enable_discount_accounting = cint(
|
||||||
frappe.db.get_single_value("Selling Settings", "enable_discount_accounting")
|
frappe.db.get_single_value("Selling Settings", "enable_discount_accounting")
|
||||||
@ -2093,6 +2109,8 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
|||||||
source_document_warehouse_field = "from_warehouse"
|
source_document_warehouse_field = "from_warehouse"
|
||||||
target_document_warehouse_field = "target_warehouse"
|
target_document_warehouse_field = "target_warehouse"
|
||||||
|
|
||||||
|
received_items = get_received_items(source_name, target_doctype, target_detail_field)
|
||||||
|
|
||||||
validate_inter_company_transaction(source_doc, doctype)
|
validate_inter_company_transaction(source_doc, doctype)
|
||||||
details = get_inter_company_details(source_doc, doctype)
|
details = get_inter_company_details(source_doc, doctype)
|
||||||
|
|
||||||
@ -2157,12 +2175,17 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
|||||||
shipping_address_name=target_doc.shipping_address_name,
|
shipping_address_name=target_doc.shipping_address_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def update_item(source, target, source_parent):
|
||||||
|
target.qty = flt(source.qty) - received_items.get(source.name, 0.0)
|
||||||
|
|
||||||
item_field_map = {
|
item_field_map = {
|
||||||
"doctype": target_doctype + " Item",
|
"doctype": target_doctype + " Item",
|
||||||
"field_no_map": ["income_account", "expense_account", "cost_center", "warehouse"],
|
"field_no_map": ["income_account", "expense_account", "cost_center", "warehouse"],
|
||||||
"field_map": {
|
"field_map": {
|
||||||
"rate": "rate",
|
"rate": "rate",
|
||||||
},
|
},
|
||||||
|
"postprocess": update_item,
|
||||||
|
"condition": lambda doc: doc.qty > 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
if doctype in ["Sales Invoice", "Sales Order"]:
|
if doctype in ["Sales Invoice", "Sales Order"]:
|
||||||
@ -2200,6 +2223,28 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
|||||||
return doclist
|
return doclist
|
||||||
|
|
||||||
|
|
||||||
|
def get_received_items(reference_name, doctype, reference_fieldname):
|
||||||
|
target_doctypes = frappe.get_all(
|
||||||
|
doctype,
|
||||||
|
filters={"inter_company_invoice_reference": reference_name, "docstatus": 1},
|
||||||
|
as_list=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
if target_doctypes:
|
||||||
|
target_doctypes = list(target_doctypes[0])
|
||||||
|
|
||||||
|
received_items_map = frappe._dict(
|
||||||
|
frappe.get_all(
|
||||||
|
doctype + " Item",
|
||||||
|
filters={"parent": ("in", target_doctypes)},
|
||||||
|
fields=[reference_fieldname, "qty"],
|
||||||
|
as_list=1,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return received_items_map
|
||||||
|
|
||||||
|
|
||||||
def set_purchase_references(doc):
|
def set_purchase_references(doc):
|
||||||
# add internal PO or PR links if any
|
# add internal PO or PR links if any
|
||||||
if doc.is_internal_transfer():
|
if doc.is_internal_transfer():
|
||||||
|
@ -11,6 +11,7 @@ def get_data():
|
|||||||
"Payment Request": "reference_name",
|
"Payment Request": "reference_name",
|
||||||
"Sales Invoice": "return_against",
|
"Sales Invoice": "return_against",
|
||||||
"Auto Repeat": "reference_document",
|
"Auto Repeat": "reference_document",
|
||||||
|
"Purchase Invoice": "inter_company_invoice_reference",
|
||||||
},
|
},
|
||||||
"internal_links": {
|
"internal_links": {
|
||||||
"Sales Order": ["items", "sales_order"],
|
"Sales Order": ["items", "sales_order"],
|
||||||
@ -30,5 +31,6 @@ def get_data():
|
|||||||
{"label": _("Reference"), "items": ["Timesheet", "Delivery Note", "Sales Order"]},
|
{"label": _("Reference"), "items": ["Timesheet", "Delivery Note", "Sales Order"]},
|
||||||
{"label": _("Returns"), "items": ["Sales Invoice"]},
|
{"label": _("Returns"), "items": ["Sales Invoice"]},
|
||||||
{"label": _("Subscription"), "items": ["Auto Repeat"]},
|
{"label": _("Subscription"), "items": ["Auto Repeat"]},
|
||||||
|
{"label": _("Internal Transfers"), "items": ["Purchase Invoice"]},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
@ -128,6 +128,7 @@ class ReceivablePayableReport(object):
|
|||||||
credit_note_in_account_currency=0.0,
|
credit_note_in_account_currency=0.0,
|
||||||
outstanding_in_account_currency=0.0,
|
outstanding_in_account_currency=0.0,
|
||||||
)
|
)
|
||||||
|
self.get_invoices(ple)
|
||||||
|
|
||||||
if self.filters.get("group_by_party"):
|
if self.filters.get("group_by_party"):
|
||||||
self.init_subtotal_row(ple.party)
|
self.init_subtotal_row(ple.party)
|
||||||
|
@ -425,7 +425,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
|
|||||||
company: me.frm.doc.company
|
company: me.frm.doc.company
|
||||||
},
|
},
|
||||||
allow_child_item_selection: true,
|
allow_child_item_selection: true,
|
||||||
child_fielname: "items",
|
child_fieldname: "items",
|
||||||
child_columns: ["item_code", "qty"]
|
child_columns: ["item_code", "qty"]
|
||||||
})
|
})
|
||||||
}, __("Get Items From"));
|
}, __("Get Items From"));
|
||||||
|
@ -140,6 +140,43 @@ class TestPurchaseOrder(FrappeTestCase):
|
|||||||
# ordered qty decreases as ordered qty is 0 (deleted row)
|
# ordered qty decreases as ordered qty is 0 (deleted row)
|
||||||
self.assertEqual(get_ordered_qty(), existing_ordered_qty - 10) # 0
|
self.assertEqual(get_ordered_qty(), existing_ordered_qty - 10) # 0
|
||||||
|
|
||||||
|
def test_supplied_items_validations_on_po_update_after_submit(self):
|
||||||
|
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted=1, qty=5, rate=100)
|
||||||
|
item = po.items[0]
|
||||||
|
|
||||||
|
original_supplied_items = {po.name: po.required_qty for po in po.supplied_items}
|
||||||
|
|
||||||
|
# Just update rate
|
||||||
|
trans_item = [
|
||||||
|
{
|
||||||
|
"item_code": "_Test FG Item",
|
||||||
|
"rate": 20,
|
||||||
|
"qty": 5,
|
||||||
|
"conversion_factor": 1.0,
|
||||||
|
"docname": item.name,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
update_child_qty_rate("Purchase Order", json.dumps(trans_item), po.name)
|
||||||
|
po.reload()
|
||||||
|
|
||||||
|
new_supplied_items = {po.name: po.required_qty for po in po.supplied_items}
|
||||||
|
self.assertEqual(set(original_supplied_items.keys()), set(new_supplied_items.keys()))
|
||||||
|
|
||||||
|
# Update qty to 2x
|
||||||
|
trans_item[0]["qty"] *= 2
|
||||||
|
update_child_qty_rate("Purchase Order", json.dumps(trans_item), po.name)
|
||||||
|
po.reload()
|
||||||
|
|
||||||
|
new_supplied_items = {po.name: po.required_qty for po in po.supplied_items}
|
||||||
|
self.assertEqual(2 * sum(original_supplied_items.values()), sum(new_supplied_items.values()))
|
||||||
|
|
||||||
|
# Set transfer qty and attempt to update qty, shouldn't be allowed
|
||||||
|
po.supplied_items[0].supplied_qty = 2
|
||||||
|
po.supplied_items[0].db_update()
|
||||||
|
trans_item[0]["qty"] *= 2
|
||||||
|
with self.assertRaises(frappe.ValidationError):
|
||||||
|
update_child_qty_rate("Purchase Order", json.dumps(trans_item), po.name)
|
||||||
|
|
||||||
def test_update_child(self):
|
def test_update_child(self):
|
||||||
mr = make_material_request(qty=10)
|
mr = make_material_request(qty=10)
|
||||||
po = make_purchase_order(mr.name)
|
po = make_purchase_order(mr.name)
|
||||||
|
@ -2440,7 +2440,7 @@ def update_bin_on_delete(row, doctype):
|
|||||||
update_bin_qty(row.item_code, row.warehouse, qty_dict)
|
update_bin_qty(row.item_code, row.warehouse, qty_dict)
|
||||||
|
|
||||||
|
|
||||||
def validate_and_delete_children(parent, data):
|
def validate_and_delete_children(parent, data) -> bool:
|
||||||
deleted_children = []
|
deleted_children = []
|
||||||
updated_item_names = [d.get("docname") for d in data]
|
updated_item_names = [d.get("docname") for d in data]
|
||||||
for item in parent.items:
|
for item in parent.items:
|
||||||
@ -2459,6 +2459,8 @@ def validate_and_delete_children(parent, data):
|
|||||||
for d in deleted_children:
|
for d in deleted_children:
|
||||||
update_bin_on_delete(d, parent.doctype)
|
update_bin_on_delete(d, parent.doctype)
|
||||||
|
|
||||||
|
return bool(deleted_children)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
|
def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
|
||||||
@ -2522,13 +2524,38 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
|||||||
):
|
):
|
||||||
frappe.throw(_("Cannot set quantity less than received quantity"))
|
frappe.throw(_("Cannot set quantity less than received quantity"))
|
||||||
|
|
||||||
|
def should_update_supplied_items(doc) -> bool:
|
||||||
|
"""Subcontracted PO can allow following changes *after submit*:
|
||||||
|
|
||||||
|
1. Change rate of subcontracting - regardless of other changes.
|
||||||
|
2. Change qty and/or add new items and/or remove items
|
||||||
|
Exception: Transfer/Consumption is already made, qty change not allowed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
supplied_items_processed = any(
|
||||||
|
item.supplied_qty or item.consumed_qty or item.returned_qty for item in doc.supplied_items
|
||||||
|
)
|
||||||
|
|
||||||
|
update_supplied_items = (
|
||||||
|
any_qty_changed or items_added_or_removed or any_conversion_factor_changed
|
||||||
|
)
|
||||||
|
if update_supplied_items and supplied_items_processed:
|
||||||
|
frappe.throw(_("Item qty can not be updated as raw materials are already processed."))
|
||||||
|
|
||||||
|
return update_supplied_items
|
||||||
|
|
||||||
data = json.loads(trans_items)
|
data = json.loads(trans_items)
|
||||||
|
|
||||||
|
any_qty_changed = False # updated to true if any item's qty changes
|
||||||
|
items_added_or_removed = False # updated to true if any new item is added or removed
|
||||||
|
any_conversion_factor_changed = False
|
||||||
|
|
||||||
sales_doctypes = ["Sales Order", "Sales Invoice", "Delivery Note", "Quotation"]
|
sales_doctypes = ["Sales Order", "Sales Invoice", "Delivery Note", "Quotation"]
|
||||||
parent = frappe.get_doc(parent_doctype, parent_doctype_name)
|
parent = frappe.get_doc(parent_doctype, parent_doctype_name)
|
||||||
|
|
||||||
check_doc_permissions(parent, "write")
|
check_doc_permissions(parent, "write")
|
||||||
validate_and_delete_children(parent, data)
|
_removed_items = validate_and_delete_children(parent, data)
|
||||||
|
items_added_or_removed |= _removed_items
|
||||||
|
|
||||||
for d in data:
|
for d in data:
|
||||||
new_child_flag = False
|
new_child_flag = False
|
||||||
@ -2539,6 +2566,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
|||||||
|
|
||||||
if not d.get("docname"):
|
if not d.get("docname"):
|
||||||
new_child_flag = True
|
new_child_flag = True
|
||||||
|
items_added_or_removed = True
|
||||||
check_doc_permissions(parent, "create")
|
check_doc_permissions(parent, "create")
|
||||||
child_item = get_new_child_item(d)
|
child_item = get_new_child_item(d)
|
||||||
else:
|
else:
|
||||||
@ -2561,6 +2589,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
|||||||
qty_unchanged = prev_qty == new_qty
|
qty_unchanged = prev_qty == new_qty
|
||||||
uom_unchanged = prev_uom == new_uom
|
uom_unchanged = prev_uom == new_uom
|
||||||
conversion_factor_unchanged = prev_con_fac == new_con_fac
|
conversion_factor_unchanged = prev_con_fac == new_con_fac
|
||||||
|
any_conversion_factor_changed |= not conversion_factor_unchanged
|
||||||
date_unchanged = (
|
date_unchanged = (
|
||||||
prev_date == getdate(new_date) if prev_date and new_date else False
|
prev_date == getdate(new_date) if prev_date and new_date else False
|
||||||
) # in case of delivery note etc
|
) # in case of delivery note etc
|
||||||
@ -2574,6 +2603,8 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
validate_quantity(child_item, d)
|
validate_quantity(child_item, d)
|
||||||
|
if flt(child_item.get("qty")) != flt(d.get("qty")):
|
||||||
|
any_qty_changed = True
|
||||||
|
|
||||||
child_item.qty = flt(d.get("qty"))
|
child_item.qty = flt(d.get("qty"))
|
||||||
rate_precision = child_item.precision("rate") or 2
|
rate_precision = child_item.precision("rate") or 2
|
||||||
@ -2679,8 +2710,9 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
|||||||
parent.update_ordered_and_reserved_qty()
|
parent.update_ordered_and_reserved_qty()
|
||||||
parent.update_receiving_percentage()
|
parent.update_receiving_percentage()
|
||||||
if parent.is_subcontracted:
|
if parent.is_subcontracted:
|
||||||
parent.update_reserved_qty_for_subcontract()
|
if should_update_supplied_items(parent):
|
||||||
parent.create_raw_materials_supplied("supplied_items")
|
parent.update_reserved_qty_for_subcontract()
|
||||||
|
parent.create_raw_materials_supplied("supplied_items")
|
||||||
parent.save()
|
parent.save()
|
||||||
else: # Sales Order
|
else: # Sales Order
|
||||||
parent.validate_warehouse()
|
parent.validate_warehouse()
|
||||||
|
@ -500,6 +500,9 @@ class calculate_taxes_and_totals(object):
|
|||||||
else:
|
else:
|
||||||
self.doc.grand_total = flt(self.doc.net_total)
|
self.doc.grand_total = flt(self.doc.net_total)
|
||||||
|
|
||||||
|
if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"):
|
||||||
|
self.doc.grand_total -= self.doc.discount_amount
|
||||||
|
|
||||||
if self.doc.get("taxes"):
|
if self.doc.get("taxes"):
|
||||||
self.doc.total_taxes_and_charges = flt(
|
self.doc.total_taxes_and_charges = flt(
|
||||||
self.doc.grand_total - self.doc.net_total - flt(self.doc.rounding_adjustment),
|
self.doc.grand_total - self.doc.net_total - flt(self.doc.rounding_adjustment),
|
||||||
@ -594,6 +597,12 @@ class calculate_taxes_and_totals(object):
|
|||||||
if not self.doc.apply_discount_on:
|
if not self.doc.apply_discount_on:
|
||||||
frappe.throw(_("Please select Apply Discount On"))
|
frappe.throw(_("Please select Apply Discount On"))
|
||||||
|
|
||||||
|
if self.doc.apply_discount_on == "Grand Total" and self.doc.get(
|
||||||
|
"is_cash_or_non_trade_discount"
|
||||||
|
):
|
||||||
|
self.discount_amount_applied = True
|
||||||
|
return
|
||||||
|
|
||||||
self.doc.base_discount_amount = flt(
|
self.doc.base_discount_amount = flt(
|
||||||
self.doc.discount_amount * self.doc.conversion_rate, self.doc.precision("base_discount_amount")
|
self.doc.discount_amount * self.doc.conversion_rate, self.doc.precision("base_discount_amount")
|
||||||
)
|
)
|
||||||
|
@ -46,6 +46,10 @@
|
|||||||
"fax",
|
"fax",
|
||||||
"address_section",
|
"address_section",
|
||||||
"address_html",
|
"address_html",
|
||||||
|
"column_break_38",
|
||||||
|
"city",
|
||||||
|
"state",
|
||||||
|
"country",
|
||||||
"column_break2",
|
"column_break2",
|
||||||
"contact_html",
|
"contact_html",
|
||||||
"qualification_tab",
|
"qualification_tab",
|
||||||
@ -333,9 +337,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "no_of_employees",
|
"fieldname": "no_of_employees",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Int",
|
||||||
"label": "No. of Employees",
|
"label": "No. of Employees"
|
||||||
"options": "1-10\n11-20\n21-30\n31-100\n11-50\n51-200\n201-500\n101-500\n500-1000\n501-1000\n>1000\n1000+"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_22",
|
"fieldname": "column_break_22",
|
||||||
@ -477,13 +480,33 @@
|
|||||||
"fieldname": "disabled",
|
"fieldname": "disabled",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Disabled"
|
"label": "Disabled"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_38",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "city",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "City"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "state",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "State"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "country",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Country",
|
||||||
|
"options": "Country"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-user",
|
"icon": "fa fa-user",
|
||||||
"idx": 5,
|
"idx": 5,
|
||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-06-21 15:10:06.613519",
|
"modified": "2022-06-27 21:56:17.392756",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "CRM",
|
"module": "CRM",
|
||||||
"name": "Lead",
|
"name": "Lead",
|
||||||
|
@ -32,9 +32,12 @@
|
|||||||
"column_break_23",
|
"column_break_23",
|
||||||
"industry",
|
"industry",
|
||||||
"market_segment",
|
"market_segment",
|
||||||
"column_break_31",
|
|
||||||
"territory",
|
|
||||||
"website",
|
"website",
|
||||||
|
"column_break_31",
|
||||||
|
"city",
|
||||||
|
"state",
|
||||||
|
"country",
|
||||||
|
"territory",
|
||||||
"section_break_14",
|
"section_break_14",
|
||||||
"currency",
|
"currency",
|
||||||
"column_break_36",
|
"column_break_36",
|
||||||
@ -463,9 +466,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "no_of_employees",
|
"fieldname": "no_of_employees",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Int",
|
||||||
"label": "No of Employees",
|
"label": "No of Employees"
|
||||||
"options": "1-10\n11-20\n21-30\n31-100\n11-50\n51-200\n201-500\n101-500\n500-1000\n501-1000\n>1000\n1000+"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "annual_revenue",
|
"fieldname": "annual_revenue",
|
||||||
@ -603,12 +605,28 @@
|
|||||||
"label": "Notes",
|
"label": "Notes",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "CRM Note"
|
"options": "CRM Note"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "city",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "City"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "state",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "State"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "country",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Country",
|
||||||
|
"options": "Country"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-info-sign",
|
"icon": "fa fa-info-sign",
|
||||||
"idx": 195,
|
"idx": 195,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-06-21 15:04:34.363959",
|
"modified": "2022-06-27 18:44:32.858696",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "CRM",
|
"module": "CRM",
|
||||||
"name": "Opportunity",
|
"name": "Opportunity",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import cstr, now
|
from frappe.utils import cstr, now, today
|
||||||
|
from pypika import functions
|
||||||
|
|
||||||
|
|
||||||
def update_lead_phone_numbers(contact, method):
|
def update_lead_phone_numbers(contact, method):
|
||||||
@ -177,6 +178,27 @@ def get_open_events(ref_doctype, ref_docname):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def open_leads_opportunities_based_on_todays_event():
|
||||||
|
event = frappe.qb.DocType("Event")
|
||||||
|
event_link = frappe.qb.DocType("Event Participants")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(event)
|
||||||
|
.join(event_link)
|
||||||
|
.on(event_link.parent == event.name)
|
||||||
|
.select(event_link.reference_doctype, event_link.reference_docname)
|
||||||
|
.where(
|
||||||
|
(event_link.reference_doctype.isin(["Lead", "Opportunity"]))
|
||||||
|
& (event.status == "Open")
|
||||||
|
& (functions.Date(event.starts_on) == today())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
data = query.run(as_dict=True)
|
||||||
|
|
||||||
|
for d in data:
|
||||||
|
frappe.db.set_value(d.reference_doctype, d.reference_docname, "status", "Open")
|
||||||
|
|
||||||
|
|
||||||
class CRMNote(Document):
|
class CRMNote(Document):
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def add_note(self, note):
|
def add_note(self, note):
|
||||||
|
@ -376,6 +376,14 @@ scheduler_events = {
|
|||||||
"0/30 * * * *": [
|
"0/30 * * * *": [
|
||||||
"erpnext.utilities.doctype.video.video.update_youtube_data",
|
"erpnext.utilities.doctype.video.video.update_youtube_data",
|
||||||
],
|
],
|
||||||
|
# Hourly but offset by 30 minutes
|
||||||
|
"30 * * * *": [
|
||||||
|
"erpnext.accounts.doctype.gl_entry.gl_entry.rename_gle_sle_docs",
|
||||||
|
],
|
||||||
|
# Daily but offset by 45 minutes
|
||||||
|
"45 0 * * *": [
|
||||||
|
"erpnext.stock.reorder_item.reorder_item",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"all": [
|
"all": [
|
||||||
"erpnext.projects.doctype.project.project.project_status_update_reminder",
|
"erpnext.projects.doctype.project.project.project_status_update_reminder",
|
||||||
@ -385,7 +393,6 @@ scheduler_events = {
|
|||||||
"hourly": [
|
"hourly": [
|
||||||
"erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group.trigger_emails",
|
"erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group.trigger_emails",
|
||||||
"erpnext.accounts.doctype.subscription.subscription.process_all",
|
"erpnext.accounts.doctype.subscription.subscription.process_all",
|
||||||
"erpnext.accounts.doctype.gl_entry.gl_entry.rename_gle_sle_docs",
|
|
||||||
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.automatic_synchronization",
|
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.automatic_synchronization",
|
||||||
"erpnext.projects.doctype.project.project.hourly_reminder",
|
"erpnext.projects.doctype.project.project.hourly_reminder",
|
||||||
"erpnext.projects.doctype.project.project.collect_project_status",
|
"erpnext.projects.doctype.project.project.collect_project_status",
|
||||||
@ -396,7 +403,6 @@ scheduler_events = {
|
|||||||
"erpnext.bulk_transaction.doctype.bulk_transaction_log.bulk_transaction_log.retry_failing_transaction",
|
"erpnext.bulk_transaction.doctype.bulk_transaction_log.bulk_transaction_log.retry_failing_transaction",
|
||||||
],
|
],
|
||||||
"daily": [
|
"daily": [
|
||||||
"erpnext.stock.reorder_item.reorder_item",
|
|
||||||
"erpnext.support.doctype.issue.issue.auto_close_tickets",
|
"erpnext.support.doctype.issue.issue.auto_close_tickets",
|
||||||
"erpnext.crm.doctype.opportunity.opportunity.auto_close_opportunity",
|
"erpnext.crm.doctype.opportunity.opportunity.auto_close_opportunity",
|
||||||
"erpnext.controllers.accounts_controller.update_invoice_status",
|
"erpnext.controllers.accounts_controller.update_invoice_status",
|
||||||
@ -431,6 +437,7 @@ scheduler_events = {
|
|||||||
"erpnext.hr.utils.allocate_earned_leaves",
|
"erpnext.hr.utils.allocate_earned_leaves",
|
||||||
"erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall.create_process_loan_security_shortfall",
|
"erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall.create_process_loan_security_shortfall",
|
||||||
"erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual.process_loan_interest_accrual_for_term_loans",
|
"erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual.process_loan_interest_accrual_for_term_loans",
|
||||||
|
"erpnext.crm.utils.open_leads_opportunities_based_on_todays_event",
|
||||||
],
|
],
|
||||||
"weekly": ["erpnext.hr.doctype.employee.employee_reminders.send_reminders_in_advance_weekly"],
|
"weekly": ["erpnext.hr.doctype.employee.employee_reminders.send_reminders_in_advance_weekly"],
|
||||||
"monthly": ["erpnext.hr.doctype.employee.employee_reminders.send_reminders_in_advance_monthly"],
|
"monthly": ["erpnext.hr.doctype.employee.employee_reminders.send_reminders_in_advance_monthly"],
|
||||||
|
@ -624,7 +624,7 @@ class SalarySlip(TransactionBase):
|
|||||||
data = self.get_data_for_eval()
|
data = self.get_data_for_eval()
|
||||||
for struct_row in self._salary_structure_doc.get(component_type):
|
for struct_row in self._salary_structure_doc.get(component_type):
|
||||||
amount = self.eval_condition_and_formula(struct_row, data)
|
amount = self.eval_condition_and_formula(struct_row, data)
|
||||||
if amount and struct_row.statistical_component == 0:
|
if amount is not None and struct_row.statistical_component == 0:
|
||||||
self.update_component_row(struct_row, amount, component_type)
|
self.update_component_row(struct_row, amount, component_type)
|
||||||
|
|
||||||
def get_data_for_eval(self):
|
def get_data_for_eval(self):
|
||||||
@ -854,6 +854,10 @@ class SalarySlip(TransactionBase):
|
|||||||
component_row, joining_date, relieving_date
|
component_row, joining_date, relieving_date
|
||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
|
# remove 0 valued components that have been updated later
|
||||||
|
if component_row.amount == 0:
|
||||||
|
self.remove(component_row)
|
||||||
|
|
||||||
def set_precision_for_component_amounts(self):
|
def set_precision_for_component_amounts(self):
|
||||||
for component_type in ("earnings", "deductions"):
|
for component_type in ("earnings", "deductions"):
|
||||||
for component_row in self.get(component_type):
|
for component_row in self.get(component_type):
|
||||||
|
@ -1107,9 +1107,25 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is_a_mapped_document(item) {
|
||||||
|
const mapped_item_field_map = {
|
||||||
|
"Delivery Note Item": ["si_detail", "so_detail", "dn_detail"],
|
||||||
|
"Sales Invoice Item": ["dn_detail", "so_detail", "sales_invoice_item"],
|
||||||
|
"Purchase Receipt Item": ["purchase_order_item", "purchase_invoice_item", "purchase_receipt_item"],
|
||||||
|
"Purchase Invoice Item": ["purchase_order_item", "pr_detail", "po_detail"],
|
||||||
|
};
|
||||||
|
const mappped_fields = mapped_item_field_map[item.doctype] || [];
|
||||||
|
|
||||||
|
return mappped_fields
|
||||||
|
.map((field) => item[field])
|
||||||
|
.filter(Boolean).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
batch_no(doc, cdt, cdn) {
|
batch_no(doc, cdt, cdn) {
|
||||||
let item = frappe.get_doc(cdt, cdn);
|
let item = frappe.get_doc(cdt, cdn);
|
||||||
this.apply_price_list(item, true);
|
if (!this.is_a_mapped_document(item)) {
|
||||||
|
this.apply_price_list(item, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle_conversion_factor(item) {
|
toggle_conversion_factor(item) {
|
||||||
@ -1483,48 +1499,46 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
}
|
}
|
||||||
|
|
||||||
_set_values_for_item_list(children) {
|
_set_values_for_item_list(children) {
|
||||||
var me = this;
|
const items_rule_dict = {};
|
||||||
var items_rule_dict = {};
|
|
||||||
|
|
||||||
for(var i=0, l=children.length; i<l; i++) {
|
for (const child of children) {
|
||||||
var d = children[i] ;
|
const existing_pricing_rule = frappe.model.get_value(child.doctype, child.name, "pricing_rules");
|
||||||
let item_row = frappe.get_doc(d.doctype, d.name);
|
|
||||||
var existing_pricing_rule = frappe.model.get_value(d.doctype, d.name, "pricing_rules");
|
for (const [key, value] of Object.entries(child)) {
|
||||||
for(var k in d) {
|
if (!["doctype", "name"].includes(key)) {
|
||||||
var v = d[k];
|
if (key === "price_list_rate") {
|
||||||
if (["doctype", "name"].indexOf(k)===-1) {
|
frappe.model.set_value(child.doctype, child.name, "rate", value);
|
||||||
if(k=="price_list_rate") {
|
|
||||||
item_row['rate'] = v;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (k !== 'free_item_data') {
|
if (key !== "free_item_data") {
|
||||||
item_row[k] = v;
|
frappe.model.set_value(child.doctype, child.name, key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
frappe.model.round_floats_in(item_row, ["price_list_rate", "discount_percentage"]);
|
frappe.model.round_floats_in(
|
||||||
|
frappe.get_doc(child.doctype, child.name),
|
||||||
|
["price_list_rate", "discount_percentage"],
|
||||||
|
);
|
||||||
|
|
||||||
// if pricing rule set as blank from an existing value, apply price_list
|
// if pricing rule set as blank from an existing value, apply price_list
|
||||||
if(!me.frm.doc.ignore_pricing_rule && existing_pricing_rule && !d.pricing_rules) {
|
if (!this.frm.doc.ignore_pricing_rule && existing_pricing_rule && !child.pricing_rules) {
|
||||||
me.apply_price_list(frappe.get_doc(d.doctype, d.name));
|
this.apply_price_list(frappe.get_doc(child.doctype, child.name));
|
||||||
} else if(!d.pricing_rules) {
|
} else if (!child.pricing_rules) {
|
||||||
me.remove_pricing_rule(frappe.get_doc(d.doctype, d.name));
|
this.remove_pricing_rule(frappe.get_doc(child.doctype, child.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (d.free_item_data.length > 0) {
|
if (child.free_item_data.length > 0) {
|
||||||
me.apply_product_discount(d);
|
this.apply_product_discount(child);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (d.apply_rule_on_other_items) {
|
if (child.apply_rule_on_other_items) {
|
||||||
items_rule_dict[d.name] = d;
|
items_rule_dict[child.name] = child;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
me.frm.refresh_field('items');
|
this.apply_rule_on_other_items(items_rule_dict);
|
||||||
me.apply_rule_on_other_items(items_rule_dict);
|
this.calculate_taxes_and_totals();
|
||||||
|
|
||||||
me.calculate_taxes_and_totals();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply_rule_on_other_items(args) {
|
apply_rule_on_other_items(args) {
|
||||||
|
@ -713,7 +713,7 @@ erpnext.utils.map_current_doc = function(opts) {
|
|||||||
get_query: opts.get_query,
|
get_query: opts.get_query,
|
||||||
add_filters_group: 1,
|
add_filters_group: 1,
|
||||||
allow_child_item_selection: opts.allow_child_item_selection,
|
allow_child_item_selection: opts.allow_child_item_selection,
|
||||||
child_fieldname: opts.child_fielname,
|
child_fieldname: opts.child_fieldname,
|
||||||
child_columns: opts.child_columns,
|
child_columns: opts.child_columns,
|
||||||
size: opts.size,
|
size: opts.size,
|
||||||
action: function(selections, args) {
|
action: function(selections, args) {
|
||||||
|
@ -1,19 +1,13 @@
|
|||||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
cur_frm.cscript.refresh = function(doc, cdt, cdn) {
|
frappe.ui.form.on("Product Bundle", {
|
||||||
cur_frm.toggle_enable('new_item_code', doc.__islocal);
|
refresh: function (frm) {
|
||||||
}
|
frm.toggle_enable("new_item_code", frm.is_new());
|
||||||
|
frm.set_query("new_item_code", () => {
|
||||||
cur_frm.fields_dict.new_item_code.get_query = function() {
|
return {
|
||||||
return{
|
query: "erpnext.selling.doctype.product_bundle.product_bundle.get_new_item_code",
|
||||||
query: "erpnext.selling.doctype.product_bundle.product_bundle.get_new_item_code"
|
};
|
||||||
}
|
});
|
||||||
}
|
},
|
||||||
cur_frm.fields_dict.new_item_code.query_description = __('Please select Item where "Is Stock Item" is "No" and "Is Sales Item" is "Yes" and there is no other Product Bundle');
|
});
|
||||||
|
|
||||||
cur_frm.cscript.onload = function() {
|
|
||||||
// set add fetch for item_code's item_name and description
|
|
||||||
cur_frm.add_fetch('item_code', 'stock_uom', 'uom');
|
|
||||||
cur_frm.add_fetch('item_code', 'description', 'description');
|
|
||||||
}
|
|
||||||
|
@ -33,6 +33,8 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"fetch_from": "item_code.description",
|
||||||
|
"fetch_if_empty": 1,
|
||||||
"fieldname": "description",
|
"fieldname": "description",
|
||||||
"fieldtype": "Text Editor",
|
"fieldtype": "Text Editor",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@ -51,6 +53,8 @@
|
|||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"fetch_from": "item_code.stock_uom",
|
||||||
|
"fetch_if_empty": 1,
|
||||||
"fieldname": "uom",
|
"fieldname": "uom",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@ -64,7 +68,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-02-28 14:06:05.725655",
|
"modified": "2022-06-27 05:30:18.475150",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Product Bundle Item",
|
"name": "Product Bundle Item",
|
||||||
@ -72,5 +76,6 @@
|
|||||||
"permissions": [],
|
"permissions": [],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
@ -207,6 +207,15 @@ def make_sales_order(source_name, target_doc=None):
|
|||||||
|
|
||||||
def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
|
def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
|
||||||
customer = _make_customer(source_name, ignore_permissions)
|
customer = _make_customer(source_name, ignore_permissions)
|
||||||
|
ordered_items = frappe._dict(
|
||||||
|
frappe.db.get_all(
|
||||||
|
"Sales Order Item",
|
||||||
|
{"prevdoc_docname": source_name, "docstatus": 1},
|
||||||
|
["item_code", "sum(qty)"],
|
||||||
|
group_by="item_code",
|
||||||
|
as_list=1,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def set_missing_values(source, target):
|
def set_missing_values(source, target):
|
||||||
if customer:
|
if customer:
|
||||||
@ -222,7 +231,9 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
|
|||||||
target.run_method("calculate_taxes_and_totals")
|
target.run_method("calculate_taxes_and_totals")
|
||||||
|
|
||||||
def update_item(obj, target, source_parent):
|
def update_item(obj, target, source_parent):
|
||||||
target.stock_qty = flt(obj.qty) * flt(obj.conversion_factor)
|
balance_qty = obj.qty - ordered_items.get(obj.item_code, 0.0)
|
||||||
|
target.qty = balance_qty if balance_qty > 0 else 0
|
||||||
|
target.stock_qty = flt(target.qty) * flt(obj.conversion_factor)
|
||||||
|
|
||||||
if obj.against_blanket_order:
|
if obj.against_blanket_order:
|
||||||
target.against_blanket_order = obj.against_blanket_order
|
target.against_blanket_order = obj.against_blanket_order
|
||||||
@ -238,6 +249,7 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
|
|||||||
"doctype": "Sales Order Item",
|
"doctype": "Sales Order Item",
|
||||||
"field_map": {"parent": "prevdoc_docname"},
|
"field_map": {"parent": "prevdoc_docname"},
|
||||||
"postprocess": update_item,
|
"postprocess": update_item,
|
||||||
|
"condition": lambda doc: doc.qty > 0,
|
||||||
},
|
},
|
||||||
"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "add_if_empty": True},
|
"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "add_if_empty": True},
|
||||||
"Sales Team": {"doctype": "Sales Team", "add_if_empty": True},
|
"Sales Team": {"doctype": "Sales Team", "add_if_empty": True},
|
||||||
|
@ -219,8 +219,8 @@ erpnext.company.setup_queries = function(frm) {
|
|||||||
["default_discount_account", {}],
|
["default_discount_account", {}],
|
||||||
["discount_allowed_account", {"root_type": "Expense"}],
|
["discount_allowed_account", {"root_type": "Expense"}],
|
||||||
["discount_received_account", {"root_type": "Income"}],
|
["discount_received_account", {"root_type": "Income"}],
|
||||||
["exchange_gain_loss_account", {"root_type": "Expense"}],
|
["exchange_gain_loss_account", {"root_type": ["in", ["Expense", "Income"]]}],
|
||||||
["unrealized_exchange_gain_loss_account", {"root_type": "Expense"}],
|
["unrealized_exchange_gain_loss_account", {"root_type": ["in", ["Expense", "Income"]]}],
|
||||||
["accumulated_depreciation_account",
|
["accumulated_depreciation_account",
|
||||||
{"root_type": "Asset", "account_type": "Accumulated Depreciation"}],
|
{"root_type": "Asset", "account_type": "Accumulated Depreciation"}],
|
||||||
["depreciation_expense_account", {"root_type": "Expense", "account_type": "Depreciation"}],
|
["depreciation_expense_account", {"root_type": "Expense", "account_type": "Depreciation"}],
|
||||||
|
@ -260,6 +260,16 @@ erpnext.stock.move_item = function (item, source, target, actual_qty, rate, call
|
|||||||
}
|
}
|
||||||
|
|
||||||
dialog.set_primary_action(__('Create Stock Entry'), function () {
|
dialog.set_primary_action(__('Create Stock Entry'), function () {
|
||||||
|
if (source && (dialog.get_value("qty") == 0 || dialog.get_value("qty") > actual_qty)) {
|
||||||
|
frappe.msgprint(__("Quantity must be greater than zero, and less or equal to {0}", [actual_qty]));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dialog.get_value("source") === dialog.get_value("target")) {
|
||||||
|
frappe.msgprint(__("Source and target warehouse must be different"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
frappe.model.with_doctype('Stock Entry', function () {
|
frappe.model.with_doctype('Stock Entry', function () {
|
||||||
let doc = frappe.model.get_new_doc('Stock Entry');
|
let doc = frappe.model.get_new_doc('Stock Entry');
|
||||||
doc.from_warehouse = dialog.get_value('source');
|
doc.from_warehouse = dialog.get_value('source');
|
||||||
|
@ -9,6 +9,7 @@ import frappe
|
|||||||
import pytz
|
import pytz
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from frappe.utils import cint
|
||||||
from pyyoutube import Api
|
from pyyoutube import Api
|
||||||
|
|
||||||
|
|
||||||
@ -46,7 +47,7 @@ def is_tracking_enabled():
|
|||||||
def get_frequency(value):
|
def get_frequency(value):
|
||||||
# Return numeric value from frequency field, return 1 as fallback default value: 1 hour
|
# Return numeric value from frequency field, return 1 as fallback default value: 1 hour
|
||||||
if value != "Daily":
|
if value != "Daily":
|
||||||
return frappe.utils.cint(value[:2].strip())
|
return cint(value[:2].strip())
|
||||||
elif value:
|
elif value:
|
||||||
return 24
|
return 24
|
||||||
return 1
|
return 1
|
||||||
@ -120,24 +121,12 @@ def batch_update_youtube_data():
|
|||||||
video_stats = entry.to_dict().get("statistics")
|
video_stats = entry.to_dict().get("statistics")
|
||||||
video_id = entry.to_dict().get("id")
|
video_id = entry.to_dict().get("id")
|
||||||
stats = {
|
stats = {
|
||||||
"like_count": video_stats.get("likeCount"),
|
"like_count": cint(video_stats.get("likeCount")),
|
||||||
"view_count": video_stats.get("viewCount"),
|
"view_count": cint(video_stats.get("viewCount")),
|
||||||
"dislike_count": video_stats.get("dislikeCount"),
|
"dislike_count": cint(video_stats.get("dislikeCount")),
|
||||||
"comment_count": video_stats.get("commentCount"),
|
"comment_count": cint(video_stats.get("commentCount")),
|
||||||
"video_id": video_id,
|
|
||||||
}
|
}
|
||||||
|
frappe.db.set_value("Video", video_id, stats)
|
||||||
frappe.db.sql(
|
|
||||||
"""
|
|
||||||
UPDATE `tabVideo`
|
|
||||||
SET
|
|
||||||
like_count = %(like_count)s,
|
|
||||||
view_count = %(view_count)s,
|
|
||||||
dislike_count = %(dislike_count)s,
|
|
||||||
comment_count = %(comment_count)s
|
|
||||||
WHERE youtube_video_id = %(video_id)s""",
|
|
||||||
stats,
|
|
||||||
)
|
|
||||||
|
|
||||||
video_list = frappe.get_all("Video", fields=["youtube_video_id"])
|
video_list = frappe.get_all("Video", fields=["youtube_video_id"])
|
||||||
if len(video_list) > 50:
|
if len(video_list) > 50:
|
||||||
|
@ -1,3 +1,35 @@
|
|||||||
|
[project]
|
||||||
|
name = "erpnext"
|
||||||
|
authors = [
|
||||||
|
{ name = "Frappe Technologies Pvt Ltd", email = "developers@frappe.io"}
|
||||||
|
]
|
||||||
|
description = "Open Source ERP"
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
readme = "README.md"
|
||||||
|
dynamic = ["version"]
|
||||||
|
dependencies = [
|
||||||
|
# Core dependencies
|
||||||
|
"pycountry~=20.7.3",
|
||||||
|
"python-stdnum~=1.16",
|
||||||
|
"Unidecode~=1.2.0",
|
||||||
|
"redisearch~=2.1.0",
|
||||||
|
|
||||||
|
# integration dependencies
|
||||||
|
"gocardless-pro~=1.22.0",
|
||||||
|
"googlemaps",
|
||||||
|
"plaid-python~=7.2.1",
|
||||||
|
"python-youtube~=0.8.0",
|
||||||
|
"taxjar~=1.9.2",
|
||||||
|
"tweepy~=3.10.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["flit_core >=3.4,<4"]
|
||||||
|
build-backend = "flit_core.buildapi"
|
||||||
|
|
||||||
|
[tool.bench.dev-dependencies]
|
||||||
|
hypothesis = "~=6.31.0"
|
||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
line-length = 99
|
line-length = 99
|
||||||
|
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
# frappe # https://github.com/frappe/frappe is installed during bench-init
|
|
||||||
gocardless-pro~=1.22.0
|
|
||||||
googlemaps
|
|
||||||
plaid-python~=7.2.1
|
|
||||||
pycountry~=20.7.3
|
|
||||||
python-stdnum~=1.16
|
|
||||||
python-youtube~=0.8.0
|
|
||||||
taxjar~=1.9.2
|
|
||||||
tweepy~=3.10.0
|
|
||||||
Unidecode~=1.2.0
|
|
||||||
redisearch~=2.1.0
|
|
25
setup.py
25
setup.py
@ -1,23 +1,6 @@
|
|||||||
from setuptools import setup, find_packages
|
# TODO: Remove this file when v15.0.0 is released
|
||||||
import re, ast
|
from setuptools import setup
|
||||||
|
|
||||||
# get version from __version__ variable in erpnext/__init__.py
|
name = "frappe"
|
||||||
_version_re = re.compile(r"__version__\s+=\s+(.*)")
|
|
||||||
|
|
||||||
with open("requirements.txt") as f:
|
setup()
|
||||||
install_requires = f.read().strip().split("\n")
|
|
||||||
|
|
||||||
with open("erpnext/__init__.py", "rb") as f:
|
|
||||||
version = str(ast.literal_eval(_version_re.search(f.read().decode("utf-8")).group(1)))
|
|
||||||
|
|
||||||
setup(
|
|
||||||
name="erpnext",
|
|
||||||
version=version,
|
|
||||||
description="Open Source ERP",
|
|
||||||
author="Frappe Technologies",
|
|
||||||
author_email="info@erpnext.com",
|
|
||||||
packages=find_packages(),
|
|
||||||
zip_safe=False,
|
|
||||||
include_package_data=True,
|
|
||||||
install_requires=install_requires,
|
|
||||||
)
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user