Merge branch 'develop' into lead-notes-patch
This commit is contained in:
commit
51c37aeee3
32
.github/workflows/initiate_release.yml
vendored
Normal file
32
.github/workflows/initiate_release.yml
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# This workflow is agnostic to branches. Only maintain on develop branch.
|
||||||
|
# To add/remove versions just modify the matrix.
|
||||||
|
|
||||||
|
name: Create weekly release pull requests
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# 9:30 UTC => 3 PM IST Tuesday
|
||||||
|
- cron: "30 9 * * 2"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: Release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
version: ["13", "14"]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: octokit/request-action@v2.x
|
||||||
|
with:
|
||||||
|
route: POST /repos/{owner}/{repo}/pulls
|
||||||
|
owner: frappe
|
||||||
|
repo: erpnext
|
||||||
|
title: |-
|
||||||
|
"chore: release v${{ matrix.version }}"
|
||||||
|
body: "Automated weekly release."
|
||||||
|
base: version-${{ matrix.version }}
|
||||||
|
head: version-${{ matrix.version }}-hotfix
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
@ -82,6 +82,8 @@ GNU/General Public License (see [license.txt](license.txt))
|
|||||||
|
|
||||||
The ERPNext code is licensed as GNU General Public License (v3) and the Documentation is licensed as Creative Commons (CC-BY-SA-3.0) and the copyright is owned by Frappe Technologies Pvt Ltd (Frappe) and Contributors.
|
The ERPNext code is licensed as GNU General Public License (v3) and the Documentation is licensed as Creative Commons (CC-BY-SA-3.0) and the copyright is owned by Frappe Technologies Pvt Ltd (Frappe) and Contributors.
|
||||||
|
|
||||||
|
By contributing to ERPNext, you agree that your contributions will be licensed under its GNU General Public License (v3).
|
||||||
|
|
||||||
## Logo and Trademark Policy
|
## Logo and Trademark Policy
|
||||||
|
|
||||||
Please read our [Logo and Trademark Policy](TRADEMARK_POLICY.md).
|
Please read our [Logo and Trademark Policy](TRADEMARK_POLICY.md).
|
||||||
|
@ -141,7 +141,7 @@ frappe.ui.form.on("Bank Statement Import", {
|
|||||||
},
|
},
|
||||||
|
|
||||||
show_import_status(frm) {
|
show_import_status(frm) {
|
||||||
let import_log = JSON.parse(frm.doc.import_log || "[]");
|
let import_log = JSON.parse(frm.doc.statement_import_log || "[]");
|
||||||
let successful_records = import_log.filter((log) => log.success);
|
let successful_records = import_log.filter((log) => log.success);
|
||||||
let failed_records = import_log.filter((log) => !log.success);
|
let failed_records = import_log.filter((log) => !log.success);
|
||||||
if (successful_records.length === 0) return;
|
if (successful_records.length === 0) return;
|
||||||
@ -309,7 +309,7 @@ frappe.ui.form.on("Bank Statement Import", {
|
|||||||
// method: 'frappe.core.doctype.data_import.data_import.get_preview_from_template',
|
// method: 'frappe.core.doctype.data_import.data_import.get_preview_from_template',
|
||||||
|
|
||||||
show_import_preview(frm, preview_data) {
|
show_import_preview(frm, preview_data) {
|
||||||
let import_log = JSON.parse(frm.doc.import_log || "[]");
|
let import_log = JSON.parse(frm.doc.statement_import_log || "[]");
|
||||||
|
|
||||||
if (
|
if (
|
||||||
frm.import_preview &&
|
frm.import_preview &&
|
||||||
@ -439,7 +439,7 @@ frappe.ui.form.on("Bank Statement Import", {
|
|||||||
},
|
},
|
||||||
|
|
||||||
show_import_log(frm) {
|
show_import_log(frm) {
|
||||||
let import_log = JSON.parse(frm.doc.import_log || "[]");
|
let import_log = JSON.parse(frm.doc.statement_import_log || "[]");
|
||||||
let logs = import_log;
|
let logs = import_log;
|
||||||
frm.toggle_display("import_log", false);
|
frm.toggle_display("import_log", false);
|
||||||
frm.toggle_display("import_log_section", logs.length > 0);
|
frm.toggle_display("import_log_section", logs.length > 0);
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
"section_import_preview",
|
"section_import_preview",
|
||||||
"import_preview",
|
"import_preview",
|
||||||
"import_log_section",
|
"import_log_section",
|
||||||
"import_log",
|
"statement_import_log",
|
||||||
"show_failed_logs",
|
"show_failed_logs",
|
||||||
"import_log_preview",
|
"import_log_preview",
|
||||||
"reference_doctype",
|
"reference_doctype",
|
||||||
@ -90,12 +90,6 @@
|
|||||||
"options": "JSON",
|
"options": "JSON",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "import_log",
|
|
||||||
"fieldtype": "Code",
|
|
||||||
"label": "Import Log",
|
|
||||||
"options": "JSON"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "import_log_section",
|
"fieldname": "import_log_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
@ -198,11 +192,17 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_4",
|
"fieldname": "column_break_4",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "statement_import_log",
|
||||||
|
"fieldtype": "Code",
|
||||||
|
"label": "Statement Import Log",
|
||||||
|
"options": "JSON"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"hide_toolbar": 1,
|
"hide_toolbar": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-05-12 14:17:37.777246",
|
"modified": "2022-09-07 11:11:40.293317",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Bank Statement Import",
|
"name": "Bank Statement Import",
|
||||||
|
@ -53,15 +53,13 @@ class BankStatementImport(DataImport):
|
|||||||
if "Bank Account" not in json.dumps(preview["columns"]):
|
if "Bank Account" not in json.dumps(preview["columns"]):
|
||||||
frappe.throw(_("Please add the Bank Account column"))
|
frappe.throw(_("Please add the Bank Account column"))
|
||||||
|
|
||||||
from frappe.core.page.background_jobs.background_jobs import get_info
|
from frappe.utils.background_jobs import is_job_queued
|
||||||
from frappe.utils.scheduler import is_scheduler_inactive
|
from frappe.utils.scheduler import is_scheduler_inactive
|
||||||
|
|
||||||
if is_scheduler_inactive() and not frappe.flags.in_test:
|
if is_scheduler_inactive() and not frappe.flags.in_test:
|
||||||
frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
|
frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
|
||||||
|
|
||||||
enqueued_jobs = [d.get("job_name") for d in get_info()]
|
if not is_job_queued(self.name):
|
||||||
|
|
||||||
if self.name not in enqueued_jobs:
|
|
||||||
enqueue(
|
enqueue(
|
||||||
start_import,
|
start_import,
|
||||||
queue="default",
|
queue="default",
|
||||||
|
@ -4,22 +4,20 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from frappe.utils.background_jobs import is_job_queued
|
||||||
|
|
||||||
from erpnext.accounts.doctype.account.account import merge_account
|
from erpnext.accounts.doctype.account.account import merge_account
|
||||||
|
|
||||||
|
|
||||||
class LedgerMerge(Document):
|
class LedgerMerge(Document):
|
||||||
def start_merge(self):
|
def start_merge(self):
|
||||||
from frappe.core.page.background_jobs.background_jobs import get_info
|
|
||||||
from frappe.utils.background_jobs import enqueue
|
from frappe.utils.background_jobs import enqueue
|
||||||
from frappe.utils.scheduler import is_scheduler_inactive
|
from frappe.utils.scheduler import is_scheduler_inactive
|
||||||
|
|
||||||
if is_scheduler_inactive() and not frappe.flags.in_test:
|
if is_scheduler_inactive() and not frappe.flags.in_test:
|
||||||
frappe.throw(_("Scheduler is inactive. Cannot merge accounts."), title=_("Scheduler Inactive"))
|
frappe.throw(_("Scheduler is inactive. Cannot merge accounts."), title=_("Scheduler Inactive"))
|
||||||
|
|
||||||
enqueued_jobs = [d.get("job_name") for d in get_info()]
|
if not is_job_queued(self.name):
|
||||||
|
|
||||||
if self.name not in enqueued_jobs:
|
|
||||||
enqueue(
|
enqueue(
|
||||||
start_merge,
|
start_merge,
|
||||||
queue="default",
|
queue="default",
|
||||||
|
@ -6,7 +6,7 @@ import frappe
|
|||||||
from frappe import _, scrub
|
from frappe import _, scrub
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import flt, nowdate
|
from frappe.utils import flt, nowdate
|
||||||
from frappe.utils.background_jobs import enqueue
|
from frappe.utils.background_jobs import enqueue, is_job_queued
|
||||||
|
|
||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||||
get_accounting_dimensions,
|
get_accounting_dimensions,
|
||||||
@ -207,14 +207,12 @@ class OpeningInvoiceCreationTool(Document):
|
|||||||
if len(invoices) < 50:
|
if len(invoices) < 50:
|
||||||
return start_import(invoices)
|
return start_import(invoices)
|
||||||
else:
|
else:
|
||||||
from frappe.core.page.background_jobs.background_jobs import get_info
|
|
||||||
from frappe.utils.scheduler import is_scheduler_inactive
|
from frappe.utils.scheduler import is_scheduler_inactive
|
||||||
|
|
||||||
if is_scheduler_inactive() and not frappe.flags.in_test:
|
if is_scheduler_inactive() and not frappe.flags.in_test:
|
||||||
frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
|
frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
|
||||||
|
|
||||||
enqueued_jobs = [d.get("job_name") for d in get_info()]
|
if not is_job_queued(self.name):
|
||||||
if self.name not in enqueued_jobs:
|
|
||||||
enqueue(
|
enqueue(
|
||||||
start_import,
|
start_import,
|
||||||
queue="default",
|
queue="default",
|
||||||
|
@ -6,11 +6,10 @@ import json
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.core.page.background_jobs.background_jobs import get_info
|
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.model.mapper import map_child_doc, map_doc
|
from frappe.model.mapper import map_child_doc, map_doc
|
||||||
from frappe.utils import cint, flt, get_time, getdate, nowdate, nowtime
|
from frappe.utils import cint, flt, get_time, getdate, nowdate, nowtime
|
||||||
from frappe.utils.background_jobs import enqueue
|
from frappe.utils.background_jobs import enqueue, is_job_queued
|
||||||
from frappe.utils.scheduler import is_scheduler_inactive
|
from frappe.utils.scheduler import is_scheduler_inactive
|
||||||
|
|
||||||
|
|
||||||
@ -467,7 +466,7 @@ def enqueue_job(job, **kwargs):
|
|||||||
closing_entry = kwargs.get("closing_entry") or {}
|
closing_entry = kwargs.get("closing_entry") or {}
|
||||||
|
|
||||||
job_name = closing_entry.get("name")
|
job_name = closing_entry.get("name")
|
||||||
if not job_already_enqueued(job_name):
|
if not is_job_queued(job_name):
|
||||||
enqueue(
|
enqueue(
|
||||||
job,
|
job,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
@ -491,12 +490,6 @@ def check_scheduler_status():
|
|||||||
frappe.throw(_("Scheduler is inactive. Cannot enqueue job."), title=_("Scheduler Inactive"))
|
frappe.throw(_("Scheduler is inactive. Cannot enqueue job."), title=_("Scheduler Inactive"))
|
||||||
|
|
||||||
|
|
||||||
def job_already_enqueued(job_name):
|
|
||||||
enqueued_jobs = [d.get("job_name") for d in get_info()]
|
|
||||||
if job_name in enqueued_jobs:
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def safe_load_json(message):
|
def safe_load_json(message):
|
||||||
try:
|
try:
|
||||||
json_message = json.loads(message).get("message")
|
json_message = json.loads(message).get("message")
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<table class="table table-bordered">
|
<table class="table table-bordered" style="font-size: 10px">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width: 12%">{{ _("Date") }}</th>
|
<th style="width: 12%">{{ _("Date") }}</th>
|
||||||
|
@ -34,8 +34,8 @@ pricing_rule_fields = [
|
|||||||
other_fields = [
|
other_fields = [
|
||||||
"min_qty",
|
"min_qty",
|
||||||
"max_qty",
|
"max_qty",
|
||||||
"min_amt",
|
"min_amount",
|
||||||
"max_amt",
|
"max_amount",
|
||||||
"priority",
|
"priority",
|
||||||
"warehouse",
|
"warehouse",
|
||||||
"threshold_percentage",
|
"threshold_percentage",
|
||||||
@ -246,7 +246,11 @@ def prepare_pricing_rule(
|
|||||||
def set_args(args, pr, doc, child_doc, discount_fields, child_doc_fields):
|
def set_args(args, pr, doc, child_doc, discount_fields, child_doc_fields):
|
||||||
pr.update(args)
|
pr.update(args)
|
||||||
for field in other_fields + discount_fields:
|
for field in other_fields + discount_fields:
|
||||||
pr.set(field, child_doc_fields.get(field))
|
target_field = field
|
||||||
|
if target_field in ["min_amount", "max_amount"]:
|
||||||
|
target_field = "min_amt" if field == "min_amount" else "max_amt"
|
||||||
|
|
||||||
|
pr.set(target_field, child_doc_fields.get(field))
|
||||||
|
|
||||||
pr.promotional_scheme_id = child_doc_fields.name
|
pr.promotional_scheme_id = child_doc_fields.name
|
||||||
pr.promotional_scheme = doc.name
|
pr.promotional_scheme = doc.name
|
||||||
|
@ -90,6 +90,23 @@ class TestPromotionalScheme(unittest.TestCase):
|
|||||||
price_rules = frappe.get_all("Pricing Rule", filters={"promotional_scheme": ps.name})
|
price_rules = frappe.get_all("Pricing Rule", filters={"promotional_scheme": ps.name})
|
||||||
self.assertEqual(price_rules, [])
|
self.assertEqual(price_rules, [])
|
||||||
|
|
||||||
|
def test_min_max_amount_configuration(self):
|
||||||
|
ps = make_promotional_scheme()
|
||||||
|
ps.price_discount_slabs[0].min_amount = 10
|
||||||
|
ps.price_discount_slabs[0].max_amount = 1000
|
||||||
|
ps.save()
|
||||||
|
|
||||||
|
price_rules_data = frappe.db.get_value(
|
||||||
|
"Pricing Rule", {"promotional_scheme": ps.name}, ["min_amt", "max_amt"], as_dict=1
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(price_rules_data.min_amt, 10)
|
||||||
|
self.assertEqual(price_rules_data.max_amt, 1000)
|
||||||
|
|
||||||
|
frappe.delete_doc("Promotional Scheme", ps.name)
|
||||||
|
price_rules = frappe.get_all("Pricing Rule", filters={"promotional_scheme": ps.name})
|
||||||
|
self.assertEqual(price_rules, [])
|
||||||
|
|
||||||
|
|
||||||
def make_promotional_scheme(**args):
|
def make_promotional_scheme(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
@ -710,6 +710,7 @@ class SalesInvoice(SellingController):
|
|||||||
if (
|
if (
|
||||||
cint(frappe.db.get_single_value("Selling Settings", "maintain_same_sales_rate"))
|
cint(frappe.db.get_single_value("Selling Settings", "maintain_same_sales_rate"))
|
||||||
and not self.is_return
|
and not self.is_return
|
||||||
|
and not self.is_internal_customer
|
||||||
):
|
):
|
||||||
self.validate_rate_with_reference_doc(
|
self.validate_rate_with_reference_doc(
|
||||||
[["Sales Order", "sales_order", "so_detail"], ["Delivery Note", "delivery_note", "dn_detail"]]
|
[["Sales Order", "sales_order", "so_detail"], ["Delivery Note", "delivery_note", "dn_detail"]]
|
||||||
@ -2161,6 +2162,17 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
|||||||
|
|
||||||
def update_item(source, target, source_parent):
|
def update_item(source, target, source_parent):
|
||||||
target.qty = flt(source.qty) - received_items.get(source.name, 0.0)
|
target.qty = flt(source.qty) - received_items.get(source.name, 0.0)
|
||||||
|
if source.doctype == "Purchase Order Item" and target.doctype == "Sales Order Item":
|
||||||
|
target.purchase_order = source.parent
|
||||||
|
target.purchase_order_item = source.name
|
||||||
|
|
||||||
|
if (
|
||||||
|
source.get("purchase_order")
|
||||||
|
and source.get("purchase_order_item")
|
||||||
|
and target.doctype == "Purchase Invoice Item"
|
||||||
|
):
|
||||||
|
target.purchase_order = source.purchase_order
|
||||||
|
target.po_detail = source.purchase_order_item
|
||||||
|
|
||||||
item_field_map = {
|
item_field_map = {
|
||||||
"doctype": target_doctype + " Item",
|
"doctype": target_doctype + " Item",
|
||||||
@ -2187,6 +2199,12 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
|||||||
"serial_no": "serial_no",
|
"serial_no": "serial_no",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
elif target_doctype == "Sales Order":
|
||||||
|
item_field_map["field_map"].update(
|
||||||
|
{
|
||||||
|
source_document_warehouse_field: "warehouse",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
doclist = get_mapped_doc(
|
doclist = get_mapped_doc(
|
||||||
doctype,
|
doctype,
|
||||||
@ -2231,6 +2249,7 @@ def get_received_items(reference_name, doctype, reference_fieldname):
|
|||||||
|
|
||||||
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():
|
||||||
if doc.doctype == "Purchase Receipt":
|
if doc.doctype == "Purchase Receipt":
|
||||||
so_item_map = get_delivery_note_details(doc.inter_company_invoice_reference)
|
so_item_map = get_delivery_note_details(doc.inter_company_invoice_reference)
|
||||||
@ -2260,15 +2279,6 @@ def set_purchase_references(doc):
|
|||||||
warehouse_map,
|
warehouse_map,
|
||||||
)
|
)
|
||||||
|
|
||||||
if list(so_item_map.values()):
|
|
||||||
pd_item_map, parent_child_map, warehouse_map = get_pd_details(
|
|
||||||
"Purchase Order Item", so_item_map, "sales_order_item"
|
|
||||||
)
|
|
||||||
|
|
||||||
update_pi_items(
|
|
||||||
doc, "po_detail", "purchase_order", so_item_map, pd_item_map, parent_child_map, warehouse_map
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def update_pi_items(
|
def update_pi_items(
|
||||||
doc,
|
doc,
|
||||||
@ -2284,13 +2294,19 @@ def update_pi_items(
|
|||||||
item.set(parent_field, parent_child_map.get(sales_item_map.get(item.sales_invoice_item)))
|
item.set(parent_field, parent_child_map.get(sales_item_map.get(item.sales_invoice_item)))
|
||||||
if doc.update_stock:
|
if doc.update_stock:
|
||||||
item.warehouse = warehouse_map.get(sales_item_map.get(item.sales_invoice_item))
|
item.warehouse = warehouse_map.get(sales_item_map.get(item.sales_invoice_item))
|
||||||
|
if not item.warehouse and item.get("purchase_order") and item.get("purchase_order_item"):
|
||||||
|
item.warehouse = frappe.db.get_value(
|
||||||
|
"Purchase Order Item", item.purchase_order_item, "warehouse"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def update_pr_items(doc, sales_item_map, purchase_item_map, parent_child_map, warehouse_map):
|
def update_pr_items(doc, sales_item_map, purchase_item_map, parent_child_map, warehouse_map):
|
||||||
for item in doc.get("items"):
|
for item in doc.get("items"):
|
||||||
item.purchase_order_item = purchase_item_map.get(sales_item_map.get(item.delivery_note_item))
|
|
||||||
item.warehouse = warehouse_map.get(sales_item_map.get(item.delivery_note_item))
|
item.warehouse = warehouse_map.get(sales_item_map.get(item.delivery_note_item))
|
||||||
item.purchase_order = parent_child_map.get(sales_item_map.get(item.delivery_note_item))
|
if not item.warehouse and item.get("purchase_order") and item.get("purchase_order_item"):
|
||||||
|
item.warehouse = frappe.db.get_value(
|
||||||
|
"Purchase Order Item", item.purchase_order_item, "warehouse"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_delivery_note_details(internal_reference):
|
def get_delivery_note_details(internal_reference):
|
||||||
|
@ -96,6 +96,10 @@
|
|||||||
"delivery_note",
|
"delivery_note",
|
||||||
"dn_detail",
|
"dn_detail",
|
||||||
"delivered_qty",
|
"delivered_qty",
|
||||||
|
"internal_transfer_section",
|
||||||
|
"purchase_order",
|
||||||
|
"column_break_92",
|
||||||
|
"purchase_order_item",
|
||||||
"accounting_dimensions_section",
|
"accounting_dimensions_section",
|
||||||
"cost_center",
|
"cost_center",
|
||||||
"dimension_col_break",
|
"dimension_col_break",
|
||||||
@ -840,12 +844,38 @@
|
|||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Grant Commission",
|
"label": "Grant Commission",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"depends_on": "eval:parent.is_internal_customer == 1",
|
||||||
|
"fieldname": "internal_transfer_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Internal Transfer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "purchase_order",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Purchase Order",
|
||||||
|
"options": "Purchase Order",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_92",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "purchase_order_item",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Purchase Order Item",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-08-26 12:06:31.205417",
|
"modified": "2022-09-06 14:17:43.394309",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice Item",
|
"name": "Sales Invoice Item",
|
||||||
|
@ -489,7 +489,6 @@ def make_reverse_gl_entries(
|
|||||||
).run(as_dict=1)
|
).run(as_dict=1)
|
||||||
|
|
||||||
if gl_entries:
|
if gl_entries:
|
||||||
create_payment_ledger_entry(gl_entries, cancel=1)
|
|
||||||
create_payment_ledger_entry(
|
create_payment_ledger_entry(
|
||||||
gl_entries, cancel=1, adv_adj=adv_adj, update_outstanding=update_outstanding
|
gl_entries, cancel=1, adv_adj=adv_adj, update_outstanding=update_outstanding
|
||||||
)
|
)
|
||||||
|
@ -784,7 +784,7 @@ class ReceivablePayableReport(object):
|
|||||||
def add_customer_filters(
|
def add_customer_filters(
|
||||||
self,
|
self,
|
||||||
):
|
):
|
||||||
self.customter = qb.DocType("Customer")
|
self.customer = qb.DocType("Customer")
|
||||||
|
|
||||||
if self.filters.get("customer_group"):
|
if self.filters.get("customer_group"):
|
||||||
self.get_hierarchical_filters("Customer Group", "customer_group")
|
self.get_hierarchical_filters("Customer Group", "customer_group")
|
||||||
@ -838,7 +838,7 @@ class ReceivablePayableReport(object):
|
|||||||
customer = self.customer
|
customer = self.customer
|
||||||
groups = qb.from_(doc).select(doc.name).where((doc.lft >= lft) & (doc.rgt <= rgt))
|
groups = qb.from_(doc).select(doc.name).where((doc.lft >= lft) & (doc.rgt <= rgt))
|
||||||
customers = qb.from_(customer).select(customer.name).where(customer[key].isin(groups))
|
customers = qb.from_(customer).select(customer.name).where(customer[key].isin(groups))
|
||||||
self.qb_selection_filter.append(ple.isin(ple.party.isin(customers)))
|
self.qb_selection_filter.append(ple.party.isin(customers))
|
||||||
|
|
||||||
def add_accounting_dimensions_filters(self):
|
def add_accounting_dimensions_filters(self):
|
||||||
accounting_dimensions = get_accounting_dimensions(as_list=False)
|
accounting_dimensions = get_accounting_dimensions(as_list=False)
|
||||||
|
@ -60,6 +60,7 @@
|
|||||||
"section_break_45",
|
"section_break_45",
|
||||||
"before_items_section",
|
"before_items_section",
|
||||||
"scan_barcode",
|
"scan_barcode",
|
||||||
|
"set_from_warehouse",
|
||||||
"items_col_break",
|
"items_col_break",
|
||||||
"set_warehouse",
|
"set_warehouse",
|
||||||
"items_section",
|
"items_section",
|
||||||
@ -1166,13 +1167,20 @@
|
|||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Is Old Subcontracting Flow",
|
"label": "Is Old Subcontracting Flow",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "is_internal_supplier",
|
||||||
|
"fieldname": "set_from_warehouse",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Set From Warehouse",
|
||||||
|
"options": "Warehouse"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"idx": 105,
|
"idx": 105,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-06-15 15:40:58.527065",
|
"modified": "2022-09-07 11:06:46.035093",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Order",
|
"name": "Purchase Order",
|
||||||
|
@ -23,5 +23,6 @@ def get_data():
|
|||||||
"items": ["Material Request", "Supplier Quotation", "Project", "Auto Repeat"],
|
"items": ["Material Request", "Supplier Quotation", "Project", "Auto Repeat"],
|
||||||
},
|
},
|
||||||
{"label": _("Sub-contracting"), "items": ["Subcontracting Order", "Stock Entry"]},
|
{"label": _("Sub-contracting"), "items": ["Subcontracting Order", "Stock Entry"]},
|
||||||
|
{"label": _("Internal"), "items": ["Sales Order"]},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,10 @@ import json
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests.utils import FrappeTestCase
|
||||||
from frappe.utils import add_days, flt, getdate, nowdate
|
from frappe.utils import add_days, flt, getdate, nowdate
|
||||||
|
from frappe.utils.data import today
|
||||||
|
|
||||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||||
|
from erpnext.buying.doctype.purchase_order.purchase_order import make_inter_company_sales_order
|
||||||
from erpnext.buying.doctype.purchase_order.purchase_order import (
|
from erpnext.buying.doctype.purchase_order.purchase_order import (
|
||||||
make_purchase_invoice as make_pi_from_po,
|
make_purchase_invoice as make_pi_from_po,
|
||||||
)
|
)
|
||||||
@ -796,6 +798,111 @@ class TestPurchaseOrder(FrappeTestCase):
|
|||||||
|
|
||||||
automatically_fetch_payment_terms(enable=0)
|
automatically_fetch_payment_terms(enable=0)
|
||||||
|
|
||||||
|
def test_internal_transfer_flow(self):
|
||||||
|
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
|
||||||
|
make_inter_company_purchase_invoice,
|
||||||
|
)
|
||||||
|
from erpnext.selling.doctype.sales_order.sales_order import (
|
||||||
|
make_delivery_note,
|
||||||
|
make_sales_invoice,
|
||||||
|
)
|
||||||
|
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
|
||||||
|
|
||||||
|
frappe.db.set_value("Selling Settings", None, "maintain_same_sales_rate", 1)
|
||||||
|
frappe.db.set_value("Buying Settings", None, "maintain_same_rate", 1)
|
||||||
|
|
||||||
|
prepare_data_for_internal_transfer()
|
||||||
|
supplier = "_Test Internal Supplier 2"
|
||||||
|
|
||||||
|
po = create_purchase_order(
|
||||||
|
company="_Test Company with perpetual inventory",
|
||||||
|
supplier=supplier,
|
||||||
|
warehouse="Stores - TCP1",
|
||||||
|
from_warehouse="_Test Internal Warehouse New 1 - TCP1",
|
||||||
|
qty=2,
|
||||||
|
rate=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
so = make_inter_company_sales_order(po.name)
|
||||||
|
so.items[0].delivery_date = today()
|
||||||
|
self.assertEqual(so.items[0].warehouse, "_Test Internal Warehouse New 1 - TCP1")
|
||||||
|
self.assertTrue(so.items[0].purchase_order)
|
||||||
|
self.assertTrue(so.items[0].purchase_order_item)
|
||||||
|
so.submit()
|
||||||
|
|
||||||
|
dn = make_delivery_note(so.name)
|
||||||
|
dn.items[0].target_warehouse = "_Test Internal Warehouse GIT - TCP1"
|
||||||
|
self.assertEqual(dn.items[0].warehouse, "_Test Internal Warehouse New 1 - TCP1")
|
||||||
|
self.assertTrue(dn.items[0].purchase_order)
|
||||||
|
self.assertTrue(dn.items[0].purchase_order_item)
|
||||||
|
|
||||||
|
self.assertEqual(po.items[0].name, dn.items[0].purchase_order_item)
|
||||||
|
dn.submit()
|
||||||
|
|
||||||
|
pr = make_inter_company_purchase_receipt(dn.name)
|
||||||
|
self.assertEqual(pr.items[0].warehouse, "Stores - TCP1")
|
||||||
|
self.assertTrue(pr.items[0].purchase_order)
|
||||||
|
self.assertTrue(pr.items[0].purchase_order_item)
|
||||||
|
self.assertEqual(po.items[0].name, pr.items[0].purchase_order_item)
|
||||||
|
pr.submit()
|
||||||
|
|
||||||
|
si = make_sales_invoice(so.name)
|
||||||
|
self.assertEqual(si.items[0].warehouse, "_Test Internal Warehouse New 1 - TCP1")
|
||||||
|
self.assertTrue(si.items[0].purchase_order)
|
||||||
|
self.assertTrue(si.items[0].purchase_order_item)
|
||||||
|
si.submit()
|
||||||
|
|
||||||
|
pi = make_inter_company_purchase_invoice(si.name)
|
||||||
|
self.assertTrue(pi.items[0].purchase_order)
|
||||||
|
self.assertTrue(pi.items[0].po_detail)
|
||||||
|
pi.submit()
|
||||||
|
|
||||||
|
po.load_from_db()
|
||||||
|
self.assertEqual(po.status, "Completed")
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_data_for_internal_transfer():
|
||||||
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||||
|
from erpnext.selling.doctype.customer.test_customer import create_internal_customer
|
||||||
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||||
|
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||||
|
|
||||||
|
company = "_Test Company with perpetual inventory"
|
||||||
|
|
||||||
|
create_internal_customer(
|
||||||
|
"_Test Internal Customer 2",
|
||||||
|
company,
|
||||||
|
company,
|
||||||
|
)
|
||||||
|
|
||||||
|
create_internal_supplier(
|
||||||
|
"_Test Internal Supplier 2",
|
||||||
|
company,
|
||||||
|
company,
|
||||||
|
)
|
||||||
|
|
||||||
|
warehouse = create_warehouse("_Test Internal Warehouse New 1", company=company)
|
||||||
|
|
||||||
|
create_warehouse("_Test Internal Warehouse GIT", company=company)
|
||||||
|
|
||||||
|
make_purchase_receipt(company=company, warehouse=warehouse, qty=2, rate=100)
|
||||||
|
|
||||||
|
if not frappe.db.get_value("Company", company, "unrealized_profit_loss_account"):
|
||||||
|
account = "Unrealized Profit and Loss - TCP1"
|
||||||
|
if not frappe.db.exists("Account", account):
|
||||||
|
frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Account",
|
||||||
|
"account_name": "Unrealized Profit and Loss",
|
||||||
|
"parent_account": "Direct Income - TCP1",
|
||||||
|
"company": company,
|
||||||
|
"is_group": 0,
|
||||||
|
"account_type": "Income Account",
|
||||||
|
}
|
||||||
|
).insert()
|
||||||
|
|
||||||
|
frappe.db.set_value("Company", company, "unrealized_profit_loss_account", account)
|
||||||
|
|
||||||
|
|
||||||
def make_pr_against_po(po, received_qty=0):
|
def make_pr_against_po(po, received_qty=0):
|
||||||
pr = make_purchase_receipt(po)
|
pr = make_purchase_receipt(po)
|
||||||
@ -847,6 +954,7 @@ def create_purchase_order(**args):
|
|||||||
{
|
{
|
||||||
"item_code": args.item or args.item_code or "_Test Item",
|
"item_code": args.item or args.item_code or "_Test Item",
|
||||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||||
|
"from_warehouse": args.from_warehouse,
|
||||||
"qty": args.qty or 10,
|
"qty": args.qty or 10,
|
||||||
"rate": args.rate or 500,
|
"rate": args.rate or 500,
|
||||||
"schedule_date": add_days(nowdate(), 1),
|
"schedule_date": add_days(nowdate(), 1),
|
||||||
|
@ -10,12 +10,14 @@
|
|||||||
"item_code",
|
"item_code",
|
||||||
"supplier_part_no",
|
"supplier_part_no",
|
||||||
"item_name",
|
"item_name",
|
||||||
|
"brand",
|
||||||
"product_bundle",
|
"product_bundle",
|
||||||
"fg_item",
|
"fg_item",
|
||||||
"fg_item_qty",
|
"fg_item_qty",
|
||||||
"column_break_4",
|
"column_break_4",
|
||||||
"schedule_date",
|
"schedule_date",
|
||||||
"expected_delivery_date",
|
"expected_delivery_date",
|
||||||
|
"item_group",
|
||||||
"section_break_5",
|
"section_break_5",
|
||||||
"description",
|
"description",
|
||||||
"col_break1",
|
"col_break1",
|
||||||
@ -58,9 +60,12 @@
|
|||||||
"base_net_rate",
|
"base_net_rate",
|
||||||
"base_net_amount",
|
"base_net_amount",
|
||||||
"warehouse_and_reference",
|
"warehouse_and_reference",
|
||||||
|
"from_warehouse",
|
||||||
"warehouse",
|
"warehouse",
|
||||||
|
"column_break_54",
|
||||||
"actual_qty",
|
"actual_qty",
|
||||||
"company_total_stock",
|
"company_total_stock",
|
||||||
|
"references_section",
|
||||||
"material_request",
|
"material_request",
|
||||||
"material_request_item",
|
"material_request_item",
|
||||||
"sales_order",
|
"sales_order",
|
||||||
@ -73,8 +78,6 @@
|
|||||||
"against_blanket_order",
|
"against_blanket_order",
|
||||||
"blanket_order",
|
"blanket_order",
|
||||||
"blanket_order_rate",
|
"blanket_order_rate",
|
||||||
"item_group",
|
|
||||||
"brand",
|
|
||||||
"section_break_56",
|
"section_break_56",
|
||||||
"received_qty",
|
"received_qty",
|
||||||
"returned_qty",
|
"returned_qty",
|
||||||
@ -442,13 +445,13 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "warehouse_and_reference",
|
"fieldname": "warehouse_and_reference",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Warehouse and Reference"
|
"label": "Warehouse Settings"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "warehouse",
|
"fieldname": "warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Warehouse",
|
"label": "Target Warehouse",
|
||||||
"oldfieldname": "warehouse",
|
"oldfieldname": "warehouse",
|
||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"options": "Warehouse",
|
"options": "Warehouse",
|
||||||
@ -760,7 +763,7 @@
|
|||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
"fieldname": "actual_qty",
|
"fieldname": "actual_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Available Qty at Warehouse",
|
"label": "Available Qty at Target Warehouse",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@ -868,13 +871,30 @@
|
|||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Finished Good Item Qty",
|
"label": "Finished Good Item Qty",
|
||||||
"mandatory_depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow"
|
"mandatory_depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:parent.is_internal_supplier",
|
||||||
|
"fieldname": "from_warehouse",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "From Warehouse",
|
||||||
|
"options": "Warehouse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"fieldname": "references_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "References"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_54",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-06-17 05:29:40.602349",
|
"modified": "2022-09-07 11:12:38.634976",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Order Item",
|
"name": "Purchase Order Item",
|
||||||
|
@ -205,6 +205,10 @@ class AccountsController(TransactionBase):
|
|||||||
def on_trash(self):
|
def on_trash(self):
|
||||||
# delete sl and gl entries on deletion of transaction
|
# delete sl and gl entries on deletion of transaction
|
||||||
if frappe.db.get_single_value("Accounts Settings", "delete_linked_ledger_entries"):
|
if frappe.db.get_single_value("Accounts Settings", "delete_linked_ledger_entries"):
|
||||||
|
ple = frappe.qb.DocType("Payment Ledger Entry")
|
||||||
|
frappe.qb.from_(ple).delete().where(
|
||||||
|
(ple.voucher_type == self.doctype) & (ple.voucher_no == self.name)
|
||||||
|
).run()
|
||||||
frappe.db.sql(
|
frappe.db.sql(
|
||||||
"delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name)
|
"delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name)
|
||||||
)
|
)
|
||||||
@ -373,7 +377,7 @@ class AccountsController(TransactionBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def validate_inter_company_reference(self):
|
def validate_inter_company_reference(self):
|
||||||
if self.doctype not in ("Purchase Invoice", "Purchase Receipt", "Purchase Order"):
|
if self.doctype not in ("Purchase Invoice", "Purchase Receipt"):
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.is_internal_transfer():
|
if self.is_internal_transfer():
|
||||||
|
@ -309,7 +309,11 @@ class BuyingController(SubcontractingController):
|
|||||||
rate = flt(outgoing_rate * (d.conversion_factor or 1), d.precision("rate"))
|
rate = flt(outgoing_rate * (d.conversion_factor or 1), d.precision("rate"))
|
||||||
else:
|
else:
|
||||||
field = "incoming_rate" if self.get("is_internal_supplier") else "rate"
|
field = "incoming_rate" if self.get("is_internal_supplier") else "rate"
|
||||||
rate = frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), field)
|
rate = flt(
|
||||||
|
frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), field)
|
||||||
|
* (d.conversion_factor or 1),
|
||||||
|
d.precision("rate"),
|
||||||
|
)
|
||||||
|
|
||||||
if self.is_internal_transfer():
|
if self.is_internal_transfer():
|
||||||
if rate != d.rate:
|
if rate != d.rate:
|
||||||
|
@ -1,193 +0,0 @@
|
|||||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
|
||||||
# License: GNU General Public License v3. See license.txt
|
|
||||||
|
|
||||||
import frappe
|
|
||||||
from frappe import _
|
|
||||||
from frappe.desk.form import assign_to
|
|
||||||
from frappe.model.document import Document
|
|
||||||
from frappe.utils import add_days, flt, unique
|
|
||||||
|
|
||||||
from erpnext.setup.doctype.employee.employee import get_holiday_list_for_employee
|
|
||||||
from erpnext.setup.doctype.holiday_list.holiday_list import is_holiday
|
|
||||||
|
|
||||||
|
|
||||||
class EmployeeBoardingController(Document):
|
|
||||||
"""
|
|
||||||
Create the project and the task for the boarding process
|
|
||||||
Assign to the concerned person and roles as per the onboarding/separation template
|
|
||||||
"""
|
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
# remove the task if linked before submitting the form
|
|
||||||
if self.amended_from:
|
|
||||||
for activity in self.activities:
|
|
||||||
activity.task = ""
|
|
||||||
|
|
||||||
def on_submit(self):
|
|
||||||
# create the project for the given employee onboarding
|
|
||||||
project_name = _(self.doctype) + " : "
|
|
||||||
if self.doctype == "Employee Onboarding":
|
|
||||||
project_name += self.job_applicant
|
|
||||||
else:
|
|
||||||
project_name += self.employee
|
|
||||||
|
|
||||||
project = frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "Project",
|
|
||||||
"project_name": project_name,
|
|
||||||
"expected_start_date": self.date_of_joining
|
|
||||||
if self.doctype == "Employee Onboarding"
|
|
||||||
else self.resignation_letter_date,
|
|
||||||
"department": self.department,
|
|
||||||
"company": self.company,
|
|
||||||
}
|
|
||||||
).insert(ignore_permissions=True, ignore_mandatory=True)
|
|
||||||
|
|
||||||
self.db_set("project", project.name)
|
|
||||||
self.db_set("boarding_status", "Pending")
|
|
||||||
self.reload()
|
|
||||||
self.create_task_and_notify_user()
|
|
||||||
|
|
||||||
def create_task_and_notify_user(self):
|
|
||||||
# create the task for the given project and assign to the concerned person
|
|
||||||
holiday_list = self.get_holiday_list()
|
|
||||||
|
|
||||||
for activity in self.activities:
|
|
||||||
if activity.task:
|
|
||||||
continue
|
|
||||||
|
|
||||||
dates = self.get_task_dates(activity, holiday_list)
|
|
||||||
|
|
||||||
task = frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "Task",
|
|
||||||
"project": self.project,
|
|
||||||
"subject": activity.activity_name + " : " + self.employee_name,
|
|
||||||
"description": activity.description,
|
|
||||||
"department": self.department,
|
|
||||||
"company": self.company,
|
|
||||||
"task_weight": activity.task_weight,
|
|
||||||
"exp_start_date": dates[0],
|
|
||||||
"exp_end_date": dates[1],
|
|
||||||
}
|
|
||||||
).insert(ignore_permissions=True)
|
|
||||||
activity.db_set("task", task.name)
|
|
||||||
|
|
||||||
users = [activity.user] if activity.user else []
|
|
||||||
if activity.role:
|
|
||||||
user_list = frappe.db.sql_list(
|
|
||||||
"""
|
|
||||||
SELECT
|
|
||||||
DISTINCT(has_role.parent)
|
|
||||||
FROM
|
|
||||||
`tabHas Role` has_role
|
|
||||||
LEFT JOIN `tabUser` user
|
|
||||||
ON has_role.parent = user.name
|
|
||||||
WHERE
|
|
||||||
has_role.parenttype = 'User'
|
|
||||||
AND user.enabled = 1
|
|
||||||
AND has_role.role = %s
|
|
||||||
""",
|
|
||||||
activity.role,
|
|
||||||
)
|
|
||||||
users = unique(users + user_list)
|
|
||||||
|
|
||||||
if "Administrator" in users:
|
|
||||||
users.remove("Administrator")
|
|
||||||
|
|
||||||
# assign the task the users
|
|
||||||
if users:
|
|
||||||
self.assign_task_to_users(task, users)
|
|
||||||
|
|
||||||
def get_holiday_list(self):
|
|
||||||
if self.doctype == "Employee Separation":
|
|
||||||
return get_holiday_list_for_employee(self.employee)
|
|
||||||
else:
|
|
||||||
if self.employee:
|
|
||||||
return get_holiday_list_for_employee(self.employee)
|
|
||||||
else:
|
|
||||||
if not self.holiday_list:
|
|
||||||
frappe.throw(_("Please set the Holiday List."), frappe.MandatoryError)
|
|
||||||
else:
|
|
||||||
return self.holiday_list
|
|
||||||
|
|
||||||
def get_task_dates(self, activity, holiday_list):
|
|
||||||
start_date = end_date = None
|
|
||||||
|
|
||||||
if activity.begin_on is not None:
|
|
||||||
start_date = add_days(self.boarding_begins_on, activity.begin_on)
|
|
||||||
start_date = self.update_if_holiday(start_date, holiday_list)
|
|
||||||
|
|
||||||
if activity.duration is not None:
|
|
||||||
end_date = add_days(self.boarding_begins_on, activity.begin_on + activity.duration)
|
|
||||||
end_date = self.update_if_holiday(end_date, holiday_list)
|
|
||||||
|
|
||||||
return [start_date, end_date]
|
|
||||||
|
|
||||||
def update_if_holiday(self, date, holiday_list):
|
|
||||||
while is_holiday(holiday_list, date):
|
|
||||||
date = add_days(date, 1)
|
|
||||||
return date
|
|
||||||
|
|
||||||
def assign_task_to_users(self, task, users):
|
|
||||||
for user in users:
|
|
||||||
args = {
|
|
||||||
"assign_to": [user],
|
|
||||||
"doctype": task.doctype,
|
|
||||||
"name": task.name,
|
|
||||||
"description": task.description or task.subject,
|
|
||||||
"notify": self.notify_users_by_email,
|
|
||||||
}
|
|
||||||
assign_to.add(args)
|
|
||||||
|
|
||||||
def on_cancel(self):
|
|
||||||
# delete task project
|
|
||||||
project = self.project
|
|
||||||
for task in frappe.get_all("Task", filters={"project": project}):
|
|
||||||
frappe.delete_doc("Task", task.name, force=1)
|
|
||||||
frappe.delete_doc("Project", project, force=1)
|
|
||||||
self.db_set("project", "")
|
|
||||||
for activity in self.activities:
|
|
||||||
activity.db_set("task", "")
|
|
||||||
|
|
||||||
frappe.msgprint(
|
|
||||||
_("Linked Project {} and Tasks deleted.").format(project), alert=True, indicator="blue"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_onboarding_details(parent, parenttype):
|
|
||||||
return frappe.get_all(
|
|
||||||
"Employee Boarding Activity",
|
|
||||||
fields=[
|
|
||||||
"activity_name",
|
|
||||||
"role",
|
|
||||||
"user",
|
|
||||||
"required_for_employee_creation",
|
|
||||||
"description",
|
|
||||||
"task_weight",
|
|
||||||
"begin_on",
|
|
||||||
"duration",
|
|
||||||
],
|
|
||||||
filters={"parent": parent, "parenttype": parenttype},
|
|
||||||
order_by="idx",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def update_employee_boarding_status(project):
|
|
||||||
employee_onboarding = frappe.db.exists("Employee Onboarding", {"project": project.name})
|
|
||||||
employee_separation = frappe.db.exists("Employee Separation", {"project": project.name})
|
|
||||||
|
|
||||||
if not (employee_onboarding or employee_separation):
|
|
||||||
return
|
|
||||||
|
|
||||||
status = "Pending"
|
|
||||||
if flt(project.percent_complete) > 0.0 and flt(project.percent_complete) < 100.0:
|
|
||||||
status = "In Process"
|
|
||||||
elif flt(project.percent_complete) == 100.0:
|
|
||||||
status = "Completed"
|
|
||||||
|
|
||||||
if employee_onboarding:
|
|
||||||
frappe.db.set_value("Employee Onboarding", employee_onboarding, "boarding_status", status)
|
|
||||||
elif employee_separation:
|
|
||||||
frappe.db.set_value("Employee Separation", employee_separation, "boarding_status", status)
|
|
@ -311,6 +311,7 @@ class SellingController(StockController):
|
|||||||
"sales_invoice_item": d.get("sales_invoice_item"),
|
"sales_invoice_item": d.get("sales_invoice_item"),
|
||||||
"dn_detail": d.get("dn_detail"),
|
"dn_detail": d.get("dn_detail"),
|
||||||
"incoming_rate": p.get("incoming_rate"),
|
"incoming_rate": p.get("incoming_rate"),
|
||||||
|
"item_row": p,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -334,6 +335,7 @@ class SellingController(StockController):
|
|||||||
"sales_invoice_item": d.get("sales_invoice_item"),
|
"sales_invoice_item": d.get("sales_invoice_item"),
|
||||||
"dn_detail": d.get("dn_detail"),
|
"dn_detail": d.get("dn_detail"),
|
||||||
"incoming_rate": d.get("incoming_rate"),
|
"incoming_rate": d.get("incoming_rate"),
|
||||||
|
"item_row": d,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -307,6 +307,20 @@ class StatusUpdater(Document):
|
|||||||
|
|
||||||
def limits_crossed_error(self, args, item, qty_or_amount):
|
def limits_crossed_error(self, args, item, qty_or_amount):
|
||||||
"""Raise exception for limits crossed"""
|
"""Raise exception for limits crossed"""
|
||||||
|
if (
|
||||||
|
self.doctype in ["Sales Invoice", "Delivery Note"]
|
||||||
|
and qty_or_amount == "amount"
|
||||||
|
and self.is_internal_customer
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
elif (
|
||||||
|
self.doctype in ["Purchase Invoice", "Purchase Receipt"]
|
||||||
|
and qty_or_amount == "amount"
|
||||||
|
and self.is_internal_supplier
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
if qty_or_amount == "qty":
|
if qty_or_amount == "qty":
|
||||||
action_msg = _(
|
action_msg = _(
|
||||||
'To allow over receipt / delivery, update "Over Receipt/Delivery Allowance" in Stock Settings or the Item.'
|
'To allow over receipt / delivery, update "Over Receipt/Delivery Allowance" in Stock Settings or the Item.'
|
||||||
|
@ -390,6 +390,10 @@ class StockController(AccountsController):
|
|||||||
return sl_dict
|
return sl_dict
|
||||||
|
|
||||||
def update_inventory_dimensions(self, row, sl_dict) -> None:
|
def update_inventory_dimensions(self, row, sl_dict) -> None:
|
||||||
|
# To handle delivery note and sales invoice
|
||||||
|
if row.get("item_row"):
|
||||||
|
row = row.get("item_row")
|
||||||
|
|
||||||
dimensions = get_evaluated_inventory_dimension(row, sl_dict, parent_doc=self)
|
dimensions = get_evaluated_inventory_dimension(row, sl_dict, parent_doc=self)
|
||||||
for dimension in dimensions:
|
for dimension in dimensions:
|
||||||
if not dimension:
|
if not dimension:
|
||||||
@ -407,9 +411,17 @@ class StockController(AccountsController):
|
|||||||
"DocField", {"parent": self.doctype, "options": dimension.fetch_from_parent}, "fieldname"
|
"DocField", {"parent": self.doctype, "options": dimension.fetch_from_parent}, "fieldname"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not fieldname:
|
||||||
|
fieldname = frappe.get_cached_value(
|
||||||
|
"Custom Field", {"dt": self.doctype, "options": dimension.fetch_from_parent}, "fieldname"
|
||||||
|
)
|
||||||
|
|
||||||
if fieldname and self.get(fieldname):
|
if fieldname and self.get(fieldname):
|
||||||
sl_dict[dimension.target_fieldname] = self.get(fieldname)
|
sl_dict[dimension.target_fieldname] = self.get(fieldname)
|
||||||
|
|
||||||
|
if sl_dict[dimension.target_fieldname] and self.docstatus == 1:
|
||||||
|
row.db_set(dimension.source_fieldname, sl_dict[dimension.target_fieldname])
|
||||||
|
|
||||||
def make_sl_entries(self, sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False):
|
def make_sl_entries(self, sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False):
|
||||||
from erpnext.stock.stock_ledger import make_sl_entries
|
from erpnext.stock.stock_ledger import make_sl_entries
|
||||||
|
|
||||||
@ -688,6 +700,47 @@ class StockController(AccountsController):
|
|||||||
else:
|
else:
|
||||||
create_repost_item_valuation_entry(args)
|
create_repost_item_valuation_entry(args)
|
||||||
|
|
||||||
|
def add_gl_entry(
|
||||||
|
self,
|
||||||
|
gl_entries,
|
||||||
|
account,
|
||||||
|
cost_center,
|
||||||
|
debit,
|
||||||
|
credit,
|
||||||
|
remarks,
|
||||||
|
against_account,
|
||||||
|
debit_in_account_currency=None,
|
||||||
|
credit_in_account_currency=None,
|
||||||
|
account_currency=None,
|
||||||
|
project=None,
|
||||||
|
voucher_detail_no=None,
|
||||||
|
item=None,
|
||||||
|
posting_date=None,
|
||||||
|
):
|
||||||
|
|
||||||
|
gl_entry = {
|
||||||
|
"account": account,
|
||||||
|
"cost_center": cost_center,
|
||||||
|
"debit": debit,
|
||||||
|
"credit": credit,
|
||||||
|
"against": against_account,
|
||||||
|
"remarks": remarks,
|
||||||
|
}
|
||||||
|
|
||||||
|
if voucher_detail_no:
|
||||||
|
gl_entry.update({"voucher_detail_no": voucher_detail_no})
|
||||||
|
|
||||||
|
if debit_in_account_currency:
|
||||||
|
gl_entry.update({"debit_in_account_currency": debit_in_account_currency})
|
||||||
|
|
||||||
|
if credit_in_account_currency:
|
||||||
|
gl_entry.update({"credit_in_account_currency": credit_in_account_currency})
|
||||||
|
|
||||||
|
if posting_date:
|
||||||
|
gl_entry.update({"posting_date": posting_date})
|
||||||
|
|
||||||
|
gl_entries.append(self.get_gl_dict(gl_entry, item=item))
|
||||||
|
|
||||||
|
|
||||||
def repost_required_for_queue(doc: StockController) -> bool:
|
def repost_required_for_queue(doc: StockController) -> bool:
|
||||||
"""check if stock document contains repeated item-warehouse with queue based valuation.
|
"""check if stock document contains repeated item-warehouse with queue based valuation.
|
||||||
|
@ -7,6 +7,7 @@ from collections import defaultdict
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.model.mapper import get_mapped_doc
|
||||||
from frappe.utils import cint, cstr, flt, get_link_to_form
|
from frappe.utils import cint, cstr, flt, get_link_to_form
|
||||||
|
|
||||||
from erpnext.controllers.stock_controller import StockController
|
from erpnext.controllers.stock_controller import StockController
|
||||||
@ -870,7 +871,18 @@ def add_items_in_ste(
|
|||||||
def make_return_stock_entry_for_subcontract(
|
def make_return_stock_entry_for_subcontract(
|
||||||
available_materials, order_doc, rm_details, order_doctype="Subcontracting Order"
|
available_materials, order_doc, rm_details, order_doctype="Subcontracting Order"
|
||||||
):
|
):
|
||||||
ste_doc = frappe.new_doc("Stock Entry")
|
ste_doc = get_mapped_doc(
|
||||||
|
order_doctype,
|
||||||
|
order_doc.name,
|
||||||
|
{
|
||||||
|
order_doctype: {
|
||||||
|
"doctype": "Stock Entry",
|
||||||
|
"field_no_map": ["purchase_order", "subcontracting_order"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ignore_child_tables=True,
|
||||||
|
)
|
||||||
|
|
||||||
ste_doc.purpose = "Material Transfer"
|
ste_doc.purpose = "Material Transfer"
|
||||||
|
|
||||||
if order_doctype == "Purchase Order":
|
if order_doctype == "Purchase Order":
|
||||||
|
@ -897,7 +897,7 @@ def make_stock_transfer_entry(**args):
|
|||||||
"item_name": row.item_code,
|
"item_name": row.item_code,
|
||||||
"rate": row.rate or 100,
|
"rate": row.rate or 100,
|
||||||
"stock_uom": row.stock_uom or "Nos",
|
"stock_uom": row.stock_uom or "Nos",
|
||||||
"warehouse": row.warehuose or "_Test Warehouse - _TC",
|
"warehouse": row.warehouse or "_Test Warehouse - _TC",
|
||||||
}
|
}
|
||||||
|
|
||||||
item_details = args.itemwise_details.get(row.item_code)
|
item_details = args.itemwise_details.get(row.item_code)
|
||||||
@ -1031,9 +1031,9 @@ def get_subcontracting_order(**args):
|
|||||||
if not args.service_items:
|
if not args.service_items:
|
||||||
service_items = [
|
service_items = [
|
||||||
{
|
{
|
||||||
"warehouse": "_Test Warehouse - _TC",
|
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||||
"item_code": "Subcontracted Service Item 7",
|
"item_code": "Subcontracted Service Item 7",
|
||||||
"qty": 5,
|
"qty": 10,
|
||||||
"rate": 100,
|
"rate": 100,
|
||||||
"fg_item": "Subcontracted Item SA7",
|
"fg_item": "Subcontracted Item SA7",
|
||||||
"fg_item_qty": 10,
|
"fg_item_qty": 10,
|
||||||
@ -1046,6 +1046,7 @@ def get_subcontracting_order(**args):
|
|||||||
rm_items=service_items,
|
rm_items=service_items,
|
||||||
is_subcontracted=1,
|
is_subcontracted=1,
|
||||||
supplier_warehouse=args.supplier_warehouse or "_Test Warehouse 1 - _TC",
|
supplier_warehouse=args.supplier_warehouse or "_Test Warehouse 1 - _TC",
|
||||||
|
company=args.company,
|
||||||
)
|
)
|
||||||
|
|
||||||
return create_subcontracting_order(po_name=po.name, **args)
|
return create_subcontracting_order(po_name=po.name, **args)
|
||||||
|
@ -11,17 +11,24 @@ frappe.query_reports["BOM Stock Calculated"] = {
|
|||||||
"options": "BOM",
|
"options": "BOM",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "qty_to_make",
|
"fieldname": "warehouse",
|
||||||
"label": __("Quantity to Make"),
|
"label": __("Warehouse"),
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Link",
|
||||||
"default": "1"
|
"options": "Warehouse",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
{
|
"fieldname": "qty_to_make",
|
||||||
|
"label": __("Quantity to Make"),
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"default": "1.0",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
"fieldname": "show_exploded_view",
|
"fieldname": "show_exploded_view",
|
||||||
"label": __("Show exploded view"),
|
"label": __("Show exploded view"),
|
||||||
"fieldtype": "Check"
|
"fieldtype": "Check",
|
||||||
|
"default": false,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -4,29 +4,31 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.query_builder.functions import IfNull, Sum
|
||||||
from frappe.utils.data import comma_and
|
from frappe.utils.data import comma_and
|
||||||
|
from pypika.terms import ExistsCriterion
|
||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
# if not filters: filters = {}
|
|
||||||
columns = get_columns()
|
columns = get_columns()
|
||||||
summ_data = []
|
data = []
|
||||||
|
|
||||||
data = get_bom_stock(filters)
|
bom_data = get_bom_data(filters)
|
||||||
qty_to_make = filters.get("qty_to_make")
|
qty_to_make = filters.get("qty_to_make")
|
||||||
|
|
||||||
manufacture_details = get_manufacturer_records()
|
manufacture_details = get_manufacturer_records()
|
||||||
for row in data:
|
|
||||||
reqd_qty = qty_to_make * row.actual_qty
|
|
||||||
last_pur_price = frappe.db.get_value("Item", row.item_code, "last_purchase_rate")
|
|
||||||
|
|
||||||
summ_data.append(get_report_data(last_pur_price, reqd_qty, row, manufacture_details))
|
for row in bom_data:
|
||||||
return columns, summ_data
|
required_qty = qty_to_make * row.qty_per_unit
|
||||||
|
last_purchase_rate = frappe.db.get_value("Item", row.item_code, "last_purchase_rate")
|
||||||
|
|
||||||
|
data.append(get_report_data(last_purchase_rate, required_qty, row, manufacture_details))
|
||||||
|
|
||||||
|
return columns, data
|
||||||
|
|
||||||
|
|
||||||
def get_report_data(last_pur_price, reqd_qty, row, manufacture_details):
|
def get_report_data(last_purchase_rate, required_qty, row, manufacture_details):
|
||||||
to_build = row.to_build if row.to_build > 0 else 0
|
qty_per_unit = row.qty_per_unit if row.qty_per_unit > 0 else 0
|
||||||
diff_qty = to_build - reqd_qty
|
difference_qty = row.actual_qty - required_qty
|
||||||
return [
|
return [
|
||||||
row.item_code,
|
row.item_code,
|
||||||
row.description,
|
row.description,
|
||||||
@ -34,85 +36,126 @@ def get_report_data(last_pur_price, reqd_qty, row, manufacture_details):
|
|||||||
comma_and(
|
comma_and(
|
||||||
manufacture_details.get(row.item_code, {}).get("manufacturer_part", []), add_quotes=False
|
manufacture_details.get(row.item_code, {}).get("manufacturer_part", []), add_quotes=False
|
||||||
),
|
),
|
||||||
|
qty_per_unit,
|
||||||
row.actual_qty,
|
row.actual_qty,
|
||||||
str(to_build),
|
required_qty,
|
||||||
reqd_qty,
|
difference_qty,
|
||||||
diff_qty,
|
last_purchase_rate,
|
||||||
last_pur_price,
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_columns():
|
def get_columns():
|
||||||
"""return columns"""
|
return [
|
||||||
columns = [
|
{
|
||||||
_("Item") + ":Link/Item:100",
|
"fieldname": "item",
|
||||||
_("Description") + "::150",
|
"label": _("Item"),
|
||||||
_("Manufacturer") + "::250",
|
"fieldtype": "Link",
|
||||||
_("Manufacturer Part Number") + "::250",
|
"options": "Item",
|
||||||
_("Qty") + ":Float:50",
|
"width": 120,
|
||||||
_("Stock Qty") + ":Float:100",
|
},
|
||||||
_("Reqd Qty") + ":Float:100",
|
{
|
||||||
_("Diff Qty") + ":Float:100",
|
"fieldname": "description",
|
||||||
_("Last Purchase Price") + ":Float:100",
|
"label": _("Description"),
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "manufacturer",
|
||||||
|
"label": _("Manufacturer"),
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "manufacturer_part_number",
|
||||||
|
"label": _("Manufacturer Part Number"),
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "qty_per_unit",
|
||||||
|
"label": _("Qty Per Unit"),
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"width": 110,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "available_qty",
|
||||||
|
"label": _("Available Qty"),
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "required_qty",
|
||||||
|
"label": _("Required Qty"),
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "difference_qty",
|
||||||
|
"label": _("Difference Qty"),
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"width": 130,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "last_purchase_rate",
|
||||||
|
"label": _("Last Purchase Rate"),
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"width": 160,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
return columns
|
|
||||||
|
|
||||||
|
|
||||||
def get_bom_stock(filters):
|
def get_bom_data(filters):
|
||||||
conditions = ""
|
|
||||||
bom = filters.get("bom")
|
|
||||||
|
|
||||||
table = "`tabBOM Item`"
|
|
||||||
qty_field = "qty"
|
|
||||||
|
|
||||||
if filters.get("show_exploded_view"):
|
if filters.get("show_exploded_view"):
|
||||||
table = "`tabBOM Explosion Item`"
|
bom_item_table = "BOM Explosion Item"
|
||||||
qty_field = "stock_qty"
|
else:
|
||||||
|
bom_item_table = "BOM Item"
|
||||||
|
|
||||||
|
bom_item = frappe.qb.DocType(bom_item_table)
|
||||||
|
bin = frappe.qb.DocType("Bin")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(bom_item)
|
||||||
|
.left_join(bin)
|
||||||
|
.on(bom_item.item_code == bin.item_code)
|
||||||
|
.select(
|
||||||
|
bom_item.item_code,
|
||||||
|
bom_item.description,
|
||||||
|
bom_item.qty_consumed_per_unit.as_("qty_per_unit"),
|
||||||
|
IfNull(Sum(bin.actual_qty), 0).as_("actual_qty"),
|
||||||
|
)
|
||||||
|
.where((bom_item.parent == filters.get("bom")) & (bom_item.parenttype == "BOM"))
|
||||||
|
.groupby(bom_item.item_code)
|
||||||
|
)
|
||||||
|
|
||||||
if filters.get("warehouse"):
|
if filters.get("warehouse"):
|
||||||
warehouse_details = frappe.db.get_value(
|
warehouse_details = frappe.db.get_value(
|
||||||
"Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1
|
"Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1
|
||||||
)
|
)
|
||||||
|
|
||||||
if warehouse_details:
|
if warehouse_details:
|
||||||
conditions += (
|
wh = frappe.qb.DocType("Warehouse")
|
||||||
" and exists (select name from `tabWarehouse` wh \
|
query = query.where(
|
||||||
where wh.lft >= %s and wh.rgt <= %s and ledger.warehouse = wh.name)"
|
ExistsCriterion(
|
||||||
% (warehouse_details.lft, warehouse_details.rgt)
|
frappe.qb.from_(wh)
|
||||||
|
.select(wh.name)
|
||||||
|
.where(
|
||||||
|
(wh.lft >= warehouse_details.lft)
|
||||||
|
& (wh.rgt <= warehouse_details.rgt)
|
||||||
|
& (bin.warehouse == wh.name)
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
conditions += " and ledger.warehouse = %s" % frappe.db.escape(filters.get("warehouse"))
|
query = query.where(bin.warehouse == frappe.db.escape(filters.get("warehouse")))
|
||||||
|
|
||||||
else:
|
return query.run(as_dict=True)
|
||||||
conditions += ""
|
|
||||||
|
|
||||||
return frappe.db.sql(
|
|
||||||
"""
|
|
||||||
SELECT
|
|
||||||
bom_item.item_code,
|
|
||||||
bom_item.description,
|
|
||||||
bom_item.{qty_field},
|
|
||||||
ifnull(sum(ledger.actual_qty), 0) as actual_qty,
|
|
||||||
ifnull(sum(FLOOR(ledger.actual_qty / bom_item.{qty_field})), 0) as to_build
|
|
||||||
FROM
|
|
||||||
{table} AS bom_item
|
|
||||||
LEFT JOIN `tabBin` AS ledger
|
|
||||||
ON bom_item.item_code = ledger.item_code
|
|
||||||
{conditions}
|
|
||||||
|
|
||||||
WHERE
|
|
||||||
bom_item.parent = '{bom}' and bom_item.parenttype='BOM'
|
|
||||||
|
|
||||||
GROUP BY bom_item.item_code""".format(
|
|
||||||
qty_field=qty_field, table=table, conditions=conditions, bom=bom
|
|
||||||
),
|
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_manufacturer_records():
|
def get_manufacturer_records():
|
||||||
details = frappe.get_all(
|
details = frappe.get_all(
|
||||||
"Item Manufacturer", fields=["manufacturer", "manufacturer_part_no", "item_code"]
|
"Item Manufacturer", fields=["manufacturer", "manufacturer_part_no", "item_code"]
|
||||||
)
|
)
|
||||||
|
|
||||||
manufacture_details = frappe._dict()
|
manufacture_details = frappe._dict()
|
||||||
for detail in details:
|
for detail in details:
|
||||||
dic = manufacture_details.setdefault(detail.get("item_code"), {})
|
dic = manufacture_details.setdefault(detail.get("item_code"), {})
|
||||||
|
@ -0,0 +1,115 @@
|
|||||||
|
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
||||||
|
from erpnext.manufacturing.report.bom_stock_calculated.bom_stock_calculated import (
|
||||||
|
execute as bom_stock_calculated_report,
|
||||||
|
)
|
||||||
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
|
|
||||||
|
|
||||||
|
class TestBOMStockCalculated(FrappeTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.fg_item, self.rm_items = create_items()
|
||||||
|
self.boms = create_boms(self.fg_item, self.rm_items)
|
||||||
|
|
||||||
|
def test_bom_stock_calculated(self):
|
||||||
|
qty_to_make = 10
|
||||||
|
|
||||||
|
# Case 1: When Item(s) Qty and Stock Qty are equal.
|
||||||
|
data = bom_stock_calculated_report(
|
||||||
|
filters={
|
||||||
|
"qty_to_make": qty_to_make,
|
||||||
|
"bom": self.boms[0].name,
|
||||||
|
}
|
||||||
|
)[1]
|
||||||
|
expected_data = get_expected_data(self.boms[0], qty_to_make)
|
||||||
|
self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data))
|
||||||
|
|
||||||
|
# Case 2: When Item(s) Qty and Stock Qty are different and BOM Qty is 1.
|
||||||
|
data = bom_stock_calculated_report(
|
||||||
|
filters={
|
||||||
|
"qty_to_make": qty_to_make,
|
||||||
|
"bom": self.boms[1].name,
|
||||||
|
}
|
||||||
|
)[1]
|
||||||
|
expected_data = get_expected_data(self.boms[1], qty_to_make)
|
||||||
|
self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data))
|
||||||
|
|
||||||
|
# Case 3: When Item(s) Qty and Stock Qty are different and BOM Qty is greater than 1.
|
||||||
|
data = bom_stock_calculated_report(
|
||||||
|
filters={
|
||||||
|
"qty_to_make": qty_to_make,
|
||||||
|
"bom": self.boms[2].name,
|
||||||
|
}
|
||||||
|
)[1]
|
||||||
|
expected_data = get_expected_data(self.boms[2], qty_to_make)
|
||||||
|
self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data))
|
||||||
|
|
||||||
|
|
||||||
|
def create_items():
|
||||||
|
fg_item = make_item(properties={"is_stock_item": 1}).name
|
||||||
|
rm_item1 = make_item(
|
||||||
|
properties={
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"standard_rate": 100,
|
||||||
|
"opening_stock": 100,
|
||||||
|
"last_purchase_rate": 100,
|
||||||
|
}
|
||||||
|
).name
|
||||||
|
rm_item2 = make_item(
|
||||||
|
properties={
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"standard_rate": 200,
|
||||||
|
"opening_stock": 200,
|
||||||
|
"last_purchase_rate": 200,
|
||||||
|
}
|
||||||
|
).name
|
||||||
|
|
||||||
|
return fg_item, [rm_item1, rm_item2]
|
||||||
|
|
||||||
|
|
||||||
|
def create_boms(fg_item, rm_items):
|
||||||
|
def update_bom_items(bom, uom, conversion_factor):
|
||||||
|
for item in bom.items:
|
||||||
|
item.uom = uom
|
||||||
|
item.conversion_factor = conversion_factor
|
||||||
|
|
||||||
|
return bom
|
||||||
|
|
||||||
|
bom1 = make_bom(item=fg_item, quantity=1, raw_materials=rm_items, rm_qty=10)
|
||||||
|
|
||||||
|
bom2 = make_bom(item=fg_item, quantity=1, raw_materials=rm_items, rm_qty=10, do_not_submit=True)
|
||||||
|
bom2 = update_bom_items(bom2, "Box", 10)
|
||||||
|
bom2.save()
|
||||||
|
bom2.submit()
|
||||||
|
|
||||||
|
bom3 = make_bom(item=fg_item, quantity=2, raw_materials=rm_items, rm_qty=10, do_not_submit=True)
|
||||||
|
bom3 = update_bom_items(bom3, "Box", 10)
|
||||||
|
bom3.save()
|
||||||
|
bom3.submit()
|
||||||
|
|
||||||
|
return [bom1, bom2, bom3]
|
||||||
|
|
||||||
|
|
||||||
|
def get_expected_data(bom, qty_to_make):
|
||||||
|
expected_data = []
|
||||||
|
|
||||||
|
for idx in range(len(bom.items)):
|
||||||
|
expected_data.append(
|
||||||
|
[
|
||||||
|
bom.items[idx].item_code,
|
||||||
|
bom.items[idx].item_code,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
float(bom.items[idx].stock_qty / bom.quantity),
|
||||||
|
float(100 * (idx + 1)),
|
||||||
|
float(qty_to_make * (bom.items[idx].stock_qty / bom.quantity)),
|
||||||
|
float((100 * (idx + 1)) - (qty_to_make * (bom.items[idx].stock_qty / bom.quantity))),
|
||||||
|
float(100 * (idx + 1)),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
return expected_data
|
@ -5,6 +5,7 @@ from typing import Dict, List, Tuple
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.query_builder.functions import Sum
|
||||||
|
|
||||||
Filters = frappe._dict
|
Filters = frappe._dict
|
||||||
Row = frappe._dict
|
Row = frappe._dict
|
||||||
@ -14,15 +15,50 @@ QueryArgs = Dict[str, str]
|
|||||||
|
|
||||||
|
|
||||||
def execute(filters: Filters) -> Tuple[Columns, Data]:
|
def execute(filters: Filters) -> Tuple[Columns, Data]:
|
||||||
|
filters = frappe._dict(filters or {})
|
||||||
columns = get_columns()
|
columns = get_columns()
|
||||||
data = get_data(filters)
|
data = get_data(filters)
|
||||||
return columns, data
|
return columns, data
|
||||||
|
|
||||||
|
|
||||||
def get_data(filters: Filters) -> Data:
|
def get_data(filters: Filters) -> Data:
|
||||||
query_args = get_query_args(filters)
|
wo = frappe.qb.DocType("Work Order")
|
||||||
data = run_query(query_args)
|
se = frappe.qb.DocType("Stock Entry")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(wo)
|
||||||
|
.inner_join(se)
|
||||||
|
.on(wo.name == se.work_order)
|
||||||
|
.select(
|
||||||
|
wo.name,
|
||||||
|
wo.status,
|
||||||
|
wo.production_item,
|
||||||
|
wo.qty,
|
||||||
|
wo.produced_qty,
|
||||||
|
wo.process_loss_qty,
|
||||||
|
(wo.produced_qty - wo.process_loss_qty).as_("actual_produced_qty"),
|
||||||
|
Sum(se.total_incoming_value).as_("total_fg_value"),
|
||||||
|
Sum(se.total_outgoing_value).as_("total_rm_value"),
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
(wo.process_loss_qty > 0)
|
||||||
|
& (wo.company == filters.company)
|
||||||
|
& (se.docstatus == 1)
|
||||||
|
& (se.posting_date.between(filters.from_date, filters.to_date))
|
||||||
|
)
|
||||||
|
.groupby(se.work_order)
|
||||||
|
)
|
||||||
|
|
||||||
|
if "item" in filters:
|
||||||
|
query.where(wo.production_item == filters.item)
|
||||||
|
|
||||||
|
if "work_order" in filters:
|
||||||
|
query.where(wo.name == filters.work_order)
|
||||||
|
|
||||||
|
data = query.run(as_dict=True)
|
||||||
|
|
||||||
update_data_with_total_pl_value(data)
|
update_data_with_total_pl_value(data)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
@ -67,54 +103,7 @@ def get_columns() -> Columns:
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_query_args(filters: Filters) -> QueryArgs:
|
|
||||||
query_args = {}
|
|
||||||
query_args.update(filters)
|
|
||||||
query_args.update(get_filter_conditions(filters))
|
|
||||||
return query_args
|
|
||||||
|
|
||||||
|
|
||||||
def run_query(query_args: QueryArgs) -> Data:
|
|
||||||
return frappe.db.sql(
|
|
||||||
"""
|
|
||||||
SELECT
|
|
||||||
wo.name, wo.status, wo.production_item, wo.qty,
|
|
||||||
wo.produced_qty, wo.process_loss_qty,
|
|
||||||
(wo.produced_qty - wo.process_loss_qty) as actual_produced_qty,
|
|
||||||
sum(se.total_incoming_value) as total_fg_value,
|
|
||||||
sum(se.total_outgoing_value) as total_rm_value
|
|
||||||
FROM
|
|
||||||
`tabWork Order` wo INNER JOIN `tabStock Entry` se
|
|
||||||
ON wo.name=se.work_order
|
|
||||||
WHERE
|
|
||||||
process_loss_qty > 0
|
|
||||||
AND wo.company = %(company)s
|
|
||||||
AND se.docstatus = 1
|
|
||||||
AND se.posting_date BETWEEN %(from_date)s AND %(to_date)s
|
|
||||||
{item_filter}
|
|
||||||
{work_order_filter}
|
|
||||||
GROUP BY
|
|
||||||
se.work_order
|
|
||||||
""".format(
|
|
||||||
**query_args
|
|
||||||
),
|
|
||||||
query_args,
|
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def update_data_with_total_pl_value(data: Data) -> None:
|
def update_data_with_total_pl_value(data: Data) -> None:
|
||||||
for row in data:
|
for row in data:
|
||||||
value_per_unit_fg = row["total_fg_value"] / row["actual_produced_qty"]
|
value_per_unit_fg = row["total_fg_value"] / row["actual_produced_qty"]
|
||||||
row["total_pl_value"] = row["process_loss_qty"] * value_per_unit_fg
|
row["total_pl_value"] = row["process_loss_qty"] * value_per_unit_fg
|
||||||
|
|
||||||
|
|
||||||
def get_filter_conditions(filters: Filters) -> QueryArgs:
|
|
||||||
filter_conditions = dict(item_filter="", work_order_filter="")
|
|
||||||
if "item" in filters:
|
|
||||||
production_item = filters.get("item")
|
|
||||||
filter_conditions.update({"item_filter": f"AND wo.production_item='{production_item}'"})
|
|
||||||
if "work_order" in filters:
|
|
||||||
work_order_name = filters.get("work_order")
|
|
||||||
filter_conditions.update({"work_order_filter": f"AND wo.name='{work_order_name}'"})
|
|
||||||
return filter_conditions
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.query_builder.functions import IfNull
|
||||||
from frappe.utils import cint
|
from frappe.utils import cint
|
||||||
|
|
||||||
|
|
||||||
@ -17,70 +18,70 @@ def execute(filters=None):
|
|||||||
def get_item_list(wo_list, filters):
|
def get_item_list(wo_list, filters):
|
||||||
out = []
|
out = []
|
||||||
|
|
||||||
# Add a row for each item/qty
|
if wo_list:
|
||||||
for wo_details in wo_list:
|
bin = frappe.qb.DocType("Bin")
|
||||||
desc = frappe.db.get_value("BOM", wo_details.bom_no, "description")
|
bom = frappe.qb.DocType("BOM")
|
||||||
|
bom_item = frappe.qb.DocType("BOM Item")
|
||||||
|
|
||||||
for wo_item_details in frappe.db.get_values(
|
# Add a row for each item/qty
|
||||||
"Work Order Item", {"parent": wo_details.name}, ["item_code", "source_warehouse"], as_dict=1
|
for wo_details in wo_list:
|
||||||
):
|
desc = frappe.db.get_value("BOM", wo_details.bom_no, "description")
|
||||||
|
|
||||||
item_list = frappe.db.sql(
|
for wo_item_details in frappe.db.get_values(
|
||||||
"""SELECT
|
"Work Order Item", {"parent": wo_details.name}, ["item_code", "source_warehouse"], as_dict=1
|
||||||
bom_item.item_code as item_code,
|
):
|
||||||
ifnull(ledger.actual_qty*bom.quantity/bom_item.stock_qty,0) as build_qty
|
item_list = (
|
||||||
FROM
|
frappe.qb.from_(bom)
|
||||||
`tabBOM` as bom, `tabBOM Item` AS bom_item
|
.from_(bom_item)
|
||||||
LEFT JOIN `tabBin` AS ledger
|
.left_join(bin)
|
||||||
ON bom_item.item_code = ledger.item_code
|
.on(
|
||||||
AND ledger.warehouse = ifnull(%(warehouse)s,%(filterhouse)s)
|
(bom_item.item_code == bin.item_code)
|
||||||
WHERE
|
& (bin.warehouse == IfNull(wo_item_details.source_warehouse, filters.warehouse))
|
||||||
bom.name = bom_item.parent
|
)
|
||||||
and bom_item.item_code = %(item_code)s
|
.select(
|
||||||
and bom.name = %(bom)s
|
bom_item.item_code.as_("item_code"),
|
||||||
GROUP BY
|
IfNull(bin.actual_qty * bom.quantity / bom_item.stock_qty, 0).as_("build_qty"),
|
||||||
bom_item.item_code""",
|
)
|
||||||
{
|
.where(
|
||||||
"bom": wo_details.bom_no,
|
(bom.name == bom_item.parent)
|
||||||
"warehouse": wo_item_details.source_warehouse,
|
& (bom_item.item_code == wo_item_details.item_code)
|
||||||
"filterhouse": filters.warehouse,
|
& (bom.name == wo_details.bom_no)
|
||||||
"item_code": wo_item_details.item_code,
|
)
|
||||||
},
|
.groupby(bom_item.item_code)
|
||||||
as_dict=1,
|
).run(as_dict=1)
|
||||||
)
|
|
||||||
|
|
||||||
stock_qty = 0
|
stock_qty = 0
|
||||||
count = 0
|
count = 0
|
||||||
buildable_qty = wo_details.qty
|
buildable_qty = wo_details.qty
|
||||||
for item in item_list:
|
for item in item_list:
|
||||||
count = count + 1
|
count = count + 1
|
||||||
if item.build_qty >= (wo_details.qty - wo_details.produced_qty):
|
if item.build_qty >= (wo_details.qty - wo_details.produced_qty):
|
||||||
stock_qty = stock_qty + 1
|
stock_qty = stock_qty + 1
|
||||||
elif buildable_qty >= item.build_qty:
|
elif buildable_qty >= item.build_qty:
|
||||||
buildable_qty = item.build_qty
|
buildable_qty = item.build_qty
|
||||||
|
|
||||||
if count == stock_qty:
|
if count == stock_qty:
|
||||||
build = "Y"
|
build = "Y"
|
||||||
else:
|
else:
|
||||||
build = "N"
|
build = "N"
|
||||||
|
|
||||||
row = frappe._dict(
|
row = frappe._dict(
|
||||||
{
|
{
|
||||||
"work_order": wo_details.name,
|
"work_order": wo_details.name,
|
||||||
"status": wo_details.status,
|
"status": wo_details.status,
|
||||||
"req_items": cint(count),
|
"req_items": cint(count),
|
||||||
"instock": stock_qty,
|
"instock": stock_qty,
|
||||||
"description": desc,
|
"description": desc,
|
||||||
"source_warehouse": wo_item_details.source_warehouse,
|
"source_warehouse": wo_item_details.source_warehouse,
|
||||||
"item_code": wo_item_details.item_code,
|
"item_code": wo_item_details.item_code,
|
||||||
"bom_no": wo_details.bom_no,
|
"bom_no": wo_details.bom_no,
|
||||||
"qty": wo_details.qty,
|
"qty": wo_details.qty,
|
||||||
"buildable_qty": buildable_qty,
|
"buildable_qty": buildable_qty,
|
||||||
"ready_to_build": build,
|
"ready_to_build": build,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
out.append(row)
|
out.append(row)
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
@ -313,4 +313,5 @@ erpnext.patches.v13_0.fix_number_and_frequency_for_monthly_depreciation
|
|||||||
erpnext.patches.v14_0.remove_hr_and_payroll_modules # 20-07-2022
|
erpnext.patches.v14_0.remove_hr_and_payroll_modules # 20-07-2022
|
||||||
erpnext.patches.v14_0.fix_crm_no_of_employees
|
erpnext.patches.v14_0.fix_crm_no_of_employees
|
||||||
erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes
|
erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes
|
||||||
|
erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries
|
||||||
erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger
|
erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
from erpnext.stock.report.stock_and_account_value_comparison.stock_and_account_value_comparison import (
|
||||||
|
get_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
data = []
|
||||||
|
|
||||||
|
for company in frappe.db.get_list("Company", pluck="name"):
|
||||||
|
data += get_data(
|
||||||
|
frappe._dict(
|
||||||
|
{
|
||||||
|
"company": company,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if data:
|
||||||
|
for d in data:
|
||||||
|
if d and d.get("voucher_type") == "Subcontracting Receipt":
|
||||||
|
doc = frappe.new_doc("Repost Item Valuation")
|
||||||
|
doc.voucher_type = d.get("voucher_type")
|
||||||
|
doc.voucher_no = d.get("voucher_no")
|
||||||
|
doc.save()
|
||||||
|
doc.submit()
|
@ -10,7 +10,6 @@ from frappe.model.document import Document
|
|||||||
from frappe.utils import add_days, flt, get_datetime, get_time, get_url, nowtime, today
|
from frappe.utils import add_days, flt, get_datetime, get_time, get_url, nowtime, today
|
||||||
|
|
||||||
from erpnext import get_default_company
|
from erpnext import get_default_company
|
||||||
from erpnext.controllers.employee_boarding_controller import update_employee_boarding_status
|
|
||||||
from erpnext.controllers.queries import get_filters_cond
|
from erpnext.controllers.queries import get_filters_cond
|
||||||
from erpnext.setup.doctype.holiday_list.holiday_list import is_holiday
|
from erpnext.setup.doctype.holiday_list.holiday_list import is_holiday
|
||||||
|
|
||||||
@ -43,7 +42,6 @@ class Project(Document):
|
|||||||
self.send_welcome_email()
|
self.send_welcome_email()
|
||||||
self.update_costing()
|
self.update_costing()
|
||||||
self.update_percent_complete()
|
self.update_percent_complete()
|
||||||
update_employee_boarding_status(self)
|
|
||||||
|
|
||||||
def copy_from_template(self):
|
def copy_from_template(self):
|
||||||
"""
|
"""
|
||||||
@ -145,7 +143,6 @@ class Project(Document):
|
|||||||
def update_project(self):
|
def update_project(self):
|
||||||
"""Called externally by Task"""
|
"""Called externally by Task"""
|
||||||
self.update_percent_complete()
|
self.update_percent_complete()
|
||||||
update_employee_boarding_status(self)
|
|
||||||
self.update_costing()
|
self.update_costing()
|
||||||
self.db_update()
|
self.db_update()
|
||||||
|
|
||||||
|
@ -226,7 +226,7 @@ $.extend(erpnext.utils, {
|
|||||||
if (!found) {
|
if (!found) {
|
||||||
filters.splice(index, 0, {
|
filters.splice(index, 0, {
|
||||||
"fieldname": dimension["fieldname"],
|
"fieldname": dimension["fieldname"],
|
||||||
"label": __(dimension["label"]),
|
"label": __(dimension["doctype"]),
|
||||||
"fieldtype": "MultiSelectList",
|
"fieldtype": "MultiSelectList",
|
||||||
get_data: function(txt) {
|
get_data: function(txt) {
|
||||||
return frappe.db.get_link_options(dimension["doctype"], txt);
|
return frappe.db.get_link_options(dimension["doctype"], txt);
|
||||||
|
@ -84,7 +84,7 @@ def create_qr_code(doc, method=None):
|
|||||||
tlv_array.append("".join([tag, length, value]))
|
tlv_array.append("".join([tag, length, value]))
|
||||||
|
|
||||||
# Invoice Amount
|
# Invoice Amount
|
||||||
invoice_amount = str(doc.grand_total)
|
invoice_amount = str(doc.base_grand_total)
|
||||||
tag = bytes([4]).hex()
|
tag = bytes([4]).hex()
|
||||||
length = bytes([len(invoice_amount)]).hex()
|
length = bytes([len(invoice_amount)]).hex()
|
||||||
value = invoice_amount.encode("utf-8").hex()
|
value = invoice_amount.encode("utf-8").hex()
|
||||||
@ -144,7 +144,7 @@ def get_vat_amount(doc):
|
|||||||
|
|
||||||
for tax in doc.get("taxes"):
|
for tax in doc.get("taxes"):
|
||||||
if tax.account_head in vat_accounts:
|
if tax.account_head in vat_accounts:
|
||||||
vat_amount += tax.tax_amount
|
vat_amount += tax.base_tax_amount
|
||||||
|
|
||||||
return vat_amount
|
return vat_amount
|
||||||
|
|
||||||
|
@ -59,7 +59,36 @@ frappe.ui.form.on("Sales Order", {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (frm.doc.docstatus === 0 && frm.doc.is_internal_customer) {
|
||||||
|
frm.events.get_items_from_internal_purchase_order(frm);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get_items_from_internal_purchase_order(frm) {
|
||||||
|
frm.add_custom_button(__('Purchase Order'), () => {
|
||||||
|
erpnext.utils.map_current_doc({
|
||||||
|
method: 'erpnext.buying.doctype.purchase_order.purchase_order.make_inter_company_sales_order',
|
||||||
|
source_doctype: 'Purchase Order',
|
||||||
|
target: frm,
|
||||||
|
setters: [
|
||||||
|
{
|
||||||
|
label: 'Supplier',
|
||||||
|
fieldname: 'supplier',
|
||||||
|
fieldtype: 'Link',
|
||||||
|
options: 'Supplier'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
get_query_filters: {
|
||||||
|
company: frm.doc.company,
|
||||||
|
is_internal_supplier: 1,
|
||||||
|
docstatus: 1,
|
||||||
|
status: ['!=', 'Completed']
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, __('Get Items From'));
|
||||||
|
},
|
||||||
|
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
if (!frm.doc.transaction_date){
|
if (!frm.doc.transaction_date){
|
||||||
frm.set_value('transaction_date', frappe.datetime.get_today())
|
frm.set_value('transaction_date', frappe.datetime.get_today())
|
||||||
|
@ -92,7 +92,11 @@
|
|||||||
"section_break_63",
|
"section_break_63",
|
||||||
"page_break",
|
"page_break",
|
||||||
"item_tax_rate",
|
"item_tax_rate",
|
||||||
"transaction_date"
|
"transaction_date",
|
||||||
|
"inter_transfer_reference_section",
|
||||||
|
"purchase_order",
|
||||||
|
"column_break_89",
|
||||||
|
"purchase_order_item"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -809,12 +813,36 @@
|
|||||||
"label": "Picked Qty (in Stock UOM)",
|
"label": "Picked Qty (in Stock UOM)",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "inter_transfer_reference_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Inter Transfer Reference"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "purchase_order",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Purchase Order",
|
||||||
|
"options": "Purchase Order",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_89",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "purchase_order_item",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Purchase Order Item",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-06-17 05:27:41.603006",
|
"modified": "2022-09-06 13:24:18.065312",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Sales Order Item",
|
"name": "Sales Order Item",
|
||||||
|
@ -10,79 +10,89 @@
|
|||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
|
"basic_details_tab",
|
||||||
"basic_information",
|
"basic_information",
|
||||||
"employee",
|
"employee",
|
||||||
"naming_series",
|
"naming_series",
|
||||||
"first_name",
|
"first_name",
|
||||||
"middle_name",
|
"middle_name",
|
||||||
"last_name",
|
"last_name",
|
||||||
"salutation",
|
|
||||||
"employee_name",
|
"employee_name",
|
||||||
"image",
|
"column_break_9",
|
||||||
"column_break1",
|
|
||||||
"company",
|
|
||||||
"status",
|
|
||||||
"gender",
|
"gender",
|
||||||
"date_of_birth",
|
"date_of_birth",
|
||||||
|
"salutation",
|
||||||
|
"column_break1",
|
||||||
"date_of_joining",
|
"date_of_joining",
|
||||||
"employee_number",
|
"image",
|
||||||
"emergency_contact_details",
|
"status",
|
||||||
"person_to_be_contacted",
|
|
||||||
"relation",
|
|
||||||
"column_break_19",
|
|
||||||
"emergency_phone_number",
|
|
||||||
"erpnext_user",
|
"erpnext_user",
|
||||||
"user_id",
|
"user_id",
|
||||||
"create_user",
|
"create_user",
|
||||||
"create_user_permission",
|
"create_user_permission",
|
||||||
"employment_details",
|
"company_details_section",
|
||||||
"scheduled_confirmation_date",
|
"company",
|
||||||
"final_confirmation_date",
|
|
||||||
"col_break_22",
|
|
||||||
"contract_end_date",
|
|
||||||
"notice_number_of_days",
|
|
||||||
"date_of_retirement",
|
|
||||||
"job_profile",
|
|
||||||
"department",
|
"department",
|
||||||
|
"employee_number",
|
||||||
|
"column_break_25",
|
||||||
"designation",
|
"designation",
|
||||||
"reports_to",
|
"reports_to",
|
||||||
"column_break_31",
|
"column_break_18",
|
||||||
"branch",
|
"branch",
|
||||||
|
"employment_details",
|
||||||
|
"scheduled_confirmation_date",
|
||||||
|
"column_break_32",
|
||||||
|
"final_confirmation_date",
|
||||||
|
"contract_end_date",
|
||||||
|
"col_break_22",
|
||||||
|
"notice_number_of_days",
|
||||||
|
"date_of_retirement",
|
||||||
|
"contact_details",
|
||||||
|
"cell_number",
|
||||||
|
"column_break_40",
|
||||||
|
"personal_email",
|
||||||
|
"company_email",
|
||||||
|
"column_break4",
|
||||||
|
"prefered_contact_email",
|
||||||
|
"prefered_email",
|
||||||
|
"unsubscribed",
|
||||||
|
"address_section",
|
||||||
|
"current_address",
|
||||||
|
"current_accommodation_type",
|
||||||
|
"column_break_46",
|
||||||
|
"permanent_address",
|
||||||
|
"permanent_accommodation_type",
|
||||||
|
"emergency_contact_details",
|
||||||
|
"person_to_be_contacted",
|
||||||
|
"column_break_55",
|
||||||
|
"emergency_phone_number",
|
||||||
|
"column_break_19",
|
||||||
|
"relation",
|
||||||
"attendance_and_leave_details",
|
"attendance_and_leave_details",
|
||||||
"attendance_device_id",
|
"attendance_device_id",
|
||||||
"column_break_44",
|
"column_break_44",
|
||||||
"holiday_list",
|
"holiday_list",
|
||||||
"salary_information",
|
"salary_information",
|
||||||
"salary_currency",
|
|
||||||
"ctc",
|
"ctc",
|
||||||
"payroll_cost_center",
|
"salary_currency",
|
||||||
"column_break_52",
|
"salary_mode",
|
||||||
|
"bank_details_section",
|
||||||
"bank_name",
|
"bank_name",
|
||||||
"bank_ac_no",
|
"bank_ac_no",
|
||||||
"contact_details",
|
|
||||||
"cell_number",
|
|
||||||
"prefered_email",
|
|
||||||
"personal_email",
|
|
||||||
"unsubscribed",
|
|
||||||
"permanent_accommodation_type",
|
|
||||||
"permanent_address",
|
|
||||||
"column_break4",
|
|
||||||
"prefered_contact_email",
|
|
||||||
"company_email",
|
|
||||||
"current_accommodation_type",
|
|
||||||
"current_address",
|
|
||||||
"sb53",
|
|
||||||
"bio",
|
|
||||||
"personal_details",
|
"personal_details",
|
||||||
"passport_number",
|
|
||||||
"date_of_issue",
|
|
||||||
"valid_upto",
|
|
||||||
"place_of_issue",
|
|
||||||
"marital_status",
|
"marital_status",
|
||||||
"blood_group",
|
|
||||||
"column_break6",
|
|
||||||
"family_background",
|
"family_background",
|
||||||
|
"column_break6",
|
||||||
|
"blood_group",
|
||||||
"health_details",
|
"health_details",
|
||||||
|
"passport_details_section",
|
||||||
|
"passport_number",
|
||||||
|
"valid_upto",
|
||||||
|
"column_break_73",
|
||||||
|
"date_of_issue",
|
||||||
|
"place_of_issue",
|
||||||
|
"profile_tab",
|
||||||
|
"bio",
|
||||||
"educational_qualification",
|
"educational_qualification",
|
||||||
"education",
|
"education",
|
||||||
"previous_work_experience",
|
"previous_work_experience",
|
||||||
@ -92,16 +102,20 @@
|
|||||||
"exit",
|
"exit",
|
||||||
"resignation_letter_date",
|
"resignation_letter_date",
|
||||||
"relieving_date",
|
"relieving_date",
|
||||||
"reason_for_leaving",
|
|
||||||
"leave_encashed",
|
|
||||||
"encashment_date",
|
|
||||||
"exit_interview_details",
|
"exit_interview_details",
|
||||||
"held_on",
|
"held_on",
|
||||||
"new_workplace",
|
"new_workplace",
|
||||||
|
"column_break_99",
|
||||||
|
"leave_encashed",
|
||||||
|
"encashment_date",
|
||||||
|
"feedback_section",
|
||||||
|
"reason_for_leaving",
|
||||||
|
"column_break_104",
|
||||||
"feedback",
|
"feedback",
|
||||||
"lft",
|
"lft",
|
||||||
"rgt",
|
"rgt",
|
||||||
"old_parent"
|
"old_parent",
|
||||||
|
"connections_tab"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -261,7 +275,7 @@
|
|||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"fieldname": "erpnext_user",
|
"fieldname": "erpnext_user",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "ERPNext User"
|
"label": "User Details"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "System User (login) ID. If set, it will become default for all HR forms.",
|
"description": "System User (login) ID. If set, it will become default for all HR forms.",
|
||||||
@ -289,8 +303,8 @@
|
|||||||
"allow_in_quick_entry": 1,
|
"allow_in_quick_entry": 1,
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"fieldname": "employment_details",
|
"fieldname": "employment_details",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "Joining Details"
|
"label": "Joining"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "scheduled_confirmation_date",
|
"fieldname": "scheduled_confirmation_date",
|
||||||
@ -331,12 +345,6 @@
|
|||||||
"oldfieldname": "date_of_retirement",
|
"oldfieldname": "date_of_retirement",
|
||||||
"oldfieldtype": "Date"
|
"oldfieldtype": "Date"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"collapsible": 1,
|
|
||||||
"fieldname": "job_profile",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "Department"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "department",
|
"fieldname": "department",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@ -366,10 +374,6 @@
|
|||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"options": "Employee"
|
"options": "Employee"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "column_break_31",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "branch",
|
"fieldname": "branch",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@ -391,7 +395,7 @@
|
|||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"fieldname": "salary_information",
|
"fieldname": "salary_information",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "Salary Details",
|
"label": "Salary Details",
|
||||||
"oldfieldtype": "Section Break",
|
"oldfieldtype": "Section Break",
|
||||||
"width": "50%"
|
"width": "50%"
|
||||||
@ -423,8 +427,8 @@
|
|||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"fieldname": "contact_details",
|
"fieldname": "contact_details",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "Contact Details"
|
"label": "Contact"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "cell_number",
|
"fieldname": "cell_number",
|
||||||
@ -493,12 +497,6 @@
|
|||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"label": "Current Address"
|
"label": "Current Address"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"collapsible": 1,
|
|
||||||
"fieldname": "sb53",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "Personal Bio"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"description": "Short biography for website and other publications.",
|
"description": "Short biography for website and other publications.",
|
||||||
"fieldname": "bio",
|
"fieldname": "bio",
|
||||||
@ -508,7 +506,7 @@
|
|||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"fieldname": "personal_details",
|
"fieldname": "personal_details",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "Personal Details"
|
"label": "Personal Details"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -601,7 +599,7 @@
|
|||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"fieldname": "exit",
|
"fieldname": "exit",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "Exit",
|
"label": "Exit",
|
||||||
"oldfieldtype": "Section Break"
|
"oldfieldtype": "Section Break"
|
||||||
},
|
},
|
||||||
@ -702,7 +700,7 @@
|
|||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"fieldname": "attendance_and_leave_details",
|
"fieldname": "attendance_and_leave_details",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "Attendance and Leave Details"
|
"label": "Attendance and Leave Details"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -713,10 +711,6 @@
|
|||||||
"fieldname": "column_break_19",
|
"fieldname": "column_break_19",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "column_break_52",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "salary_currency",
|
"fieldname": "salary_currency",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@ -728,13 +722,95 @@
|
|||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Cost to Company (CTC)",
|
"label": "Cost to Company (CTC)",
|
||||||
"options": "salary_currency"
|
"options": "salary_currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "basic_details_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Basic Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company_details_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Company Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_18",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"fieldname": "address_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_46",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "profile_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Profile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "passport_details_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Passport Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_73",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "bank_details_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Bank Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_9",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_25",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "connections_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Connections",
|
||||||
|
"show_dashboard": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_32",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_40",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_55",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_99",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "feedback_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Feedback"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_104",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-user",
|
"icon": "fa fa-user",
|
||||||
"idx": 24,
|
"idx": 24,
|
||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-06-27 01:29:32.952091",
|
"modified": "2022-08-23 13:47:46.944993",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Setup",
|
"module": "Setup",
|
||||||
"name": "Employee",
|
"name": "Employee",
|
||||||
|
@ -178,6 +178,7 @@ class DeliveryNote(SellingController):
|
|||||||
if (
|
if (
|
||||||
cint(frappe.db.get_single_value("Selling Settings", "maintain_same_sales_rate"))
|
cint(frappe.db.get_single_value("Selling Settings", "maintain_same_sales_rate"))
|
||||||
and not self.is_return
|
and not self.is_return
|
||||||
|
and not self.is_internal_customer
|
||||||
):
|
):
|
||||||
self.validate_rate_with_reference_doc(
|
self.validate_rate_with_reference_doc(
|
||||||
[
|
[
|
||||||
@ -896,6 +897,8 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
|||||||
"name": "delivery_note_item",
|
"name": "delivery_note_item",
|
||||||
"batch_no": "batch_no",
|
"batch_no": "batch_no",
|
||||||
"serial_no": "serial_no",
|
"serial_no": "serial_no",
|
||||||
|
"purchase_order": "purchase_order",
|
||||||
|
"purchase_order_item": "purchase_order_item",
|
||||||
},
|
},
|
||||||
"field_no_map": ["warehouse"],
|
"field_no_map": ["warehouse"],
|
||||||
},
|
},
|
||||||
|
@ -86,6 +86,10 @@
|
|||||||
"expense_account",
|
"expense_account",
|
||||||
"allow_zero_valuation_rate",
|
"allow_zero_valuation_rate",
|
||||||
"column_break_71",
|
"column_break_71",
|
||||||
|
"internal_transfer_section",
|
||||||
|
"purchase_order",
|
||||||
|
"column_break_82",
|
||||||
|
"purchase_order_item",
|
||||||
"accounting_dimensions_section",
|
"accounting_dimensions_section",
|
||||||
"cost_center",
|
"cost_center",
|
||||||
"dimension_col_break",
|
"dimension_col_break",
|
||||||
@ -777,13 +781,39 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"depends_on": "eval:parent.is_internal_customer == 1",
|
||||||
|
"fieldname": "internal_transfer_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Internal Transfer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "purchase_order",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Purchase Order",
|
||||||
|
"options": "Purchase Order",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_82",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "purchase_order_item",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Purchase Order Item",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-06-17 05:25:47.711177",
|
"modified": "2022-09-06 14:19:42.876357",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Delivery Note Item",
|
"name": "Delivery Note Item",
|
||||||
|
@ -30,6 +30,7 @@ frappe.ui.form.on('Inventory Dimension', {
|
|||||||
|
|
||||||
onload(frm) {
|
onload(frm) {
|
||||||
frm.trigger('render_traget_field');
|
frm.trigger('render_traget_field');
|
||||||
|
frm.trigger("set_parent_fields");
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh(frm) {
|
refresh(frm) {
|
||||||
@ -52,6 +53,30 @@ frappe.ui.form.on('Inventory Dimension', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
document_type(frm) {
|
||||||
|
frm.trigger("set_parent_fields");
|
||||||
|
},
|
||||||
|
|
||||||
|
set_parent_fields(frm) {
|
||||||
|
if (frm.doc.apply_to_all_doctypes) {
|
||||||
|
frm.set_df_property("fetch_from_parent", "options", frm.doc.reference_document);
|
||||||
|
} else if (frm.doc.document_type && frm.doc.istable) {
|
||||||
|
frappe.call({
|
||||||
|
method: 'erpnext.stock.doctype.inventory_dimension.inventory_dimension.get_parent_fields',
|
||||||
|
args: {
|
||||||
|
child_doctype: frm.doc.document_type,
|
||||||
|
dimension_name: frm.doc.reference_document
|
||||||
|
},
|
||||||
|
callback: (r) => {
|
||||||
|
if (r.message && r.message.length) {
|
||||||
|
frm.set_df_property("fetch_from_parent", "options",
|
||||||
|
[""].concat(r.message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
delete_dimension(frm) {
|
delete_dimension(frm) {
|
||||||
let msg = (`
|
let msg = (`
|
||||||
Custom fields related to this dimension will be deleted on deletion of dimension.
|
Custom fields related to this dimension will be deleted on deletion of dimension.
|
||||||
|
@ -144,16 +144,15 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "istable",
|
|
||||||
"description": "Set fieldname or DocType name like Supplier, Customer etc.",
|
"description": "Set fieldname or DocType name like Supplier, Customer etc.",
|
||||||
"fieldname": "fetch_from_parent",
|
"fieldname": "fetch_from_parent",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Select",
|
||||||
"label": "Fetch Value From Parent Form"
|
"label": "Fetch Value From Parent Form"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-08-17 11:43:24.722441",
|
"modified": "2022-09-02 13:29:04.098469",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Inventory Dimension",
|
"name": "Inventory Dimension",
|
||||||
|
@ -236,3 +236,30 @@ def get_inventory_dimensions():
|
|||||||
def delete_dimension(dimension):
|
def delete_dimension(dimension):
|
||||||
doc = frappe.get_doc("Inventory Dimension", dimension)
|
doc = frappe.get_doc("Inventory Dimension", dimension)
|
||||||
doc.delete()
|
doc.delete()
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_parent_fields(child_doctype, dimension_name):
|
||||||
|
parent_doctypes = frappe.get_all(
|
||||||
|
"DocField", fields=["parent"], filters={"options": child_doctype}
|
||||||
|
)
|
||||||
|
|
||||||
|
fields = []
|
||||||
|
|
||||||
|
fields.extend(
|
||||||
|
frappe.get_all(
|
||||||
|
"DocField",
|
||||||
|
fields=["fieldname as value", "label"],
|
||||||
|
filters={"options": dimension_name, "parent": ("in", [d.parent for d in parent_doctypes])},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fields.extend(
|
||||||
|
frappe.get_all(
|
||||||
|
"Custom Field",
|
||||||
|
fields=["fieldname as value", "label"],
|
||||||
|
filters={"options": dimension_name, "dt": ("in", [d.parent for d in parent_doctypes])},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return fields
|
||||||
|
@ -2,14 +2,17 @@
|
|||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||||
from erpnext.stock.doctype.inventory_dimension.inventory_dimension import (
|
from erpnext.stock.doctype.inventory_dimension.inventory_dimension import (
|
||||||
CanNotBeChildDoc,
|
CanNotBeChildDoc,
|
||||||
CanNotBeDefaultDimension,
|
CanNotBeDefaultDimension,
|
||||||
DoNotChangeError,
|
DoNotChangeError,
|
||||||
delete_dimension,
|
delete_dimension,
|
||||||
)
|
)
|
||||||
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||||
|
|
||||||
@ -136,6 +139,58 @@ class TestInventoryDimension(FrappeTestCase):
|
|||||||
self.assertTrue(inv_dim1.has_stock_ledger())
|
self.assertTrue(inv_dim1.has_stock_ledger())
|
||||||
self.assertRaises(DoNotChangeError, inv_dim1.save)
|
self.assertRaises(DoNotChangeError, inv_dim1.save)
|
||||||
|
|
||||||
|
def test_inventory_dimension_for_purchase_receipt_and_delivery_note(self):
|
||||||
|
create_inventory_dimension(
|
||||||
|
reference_document="Rack",
|
||||||
|
type_of_transaction="Both",
|
||||||
|
dimension_name="Rack",
|
||||||
|
apply_to_all_doctypes=1,
|
||||||
|
fetch_from_parent="Rack",
|
||||||
|
)
|
||||||
|
|
||||||
|
create_custom_field(
|
||||||
|
"Purchase Receipt", dict(fieldname="rack", label="Rack", fieldtype="Link", options="Rack")
|
||||||
|
)
|
||||||
|
|
||||||
|
create_custom_field(
|
||||||
|
"Delivery Note", dict(fieldname="rack", label="Rack", fieldtype="Link", options="Rack")
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.reload_doc("stock", "doctype", "purchase_receipt_item")
|
||||||
|
frappe.reload_doc("stock", "doctype", "delivery_note_item")
|
||||||
|
|
||||||
|
pr_doc = make_purchase_receipt(qty=2, do_not_submit=True)
|
||||||
|
pr_doc.rack = "Rack 1"
|
||||||
|
pr_doc.save()
|
||||||
|
pr_doc.submit()
|
||||||
|
|
||||||
|
pr_doc.load_from_db()
|
||||||
|
|
||||||
|
self.assertEqual(pr_doc.items[0].rack, "Rack 1")
|
||||||
|
sle_rack = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{"voucher_detail_no": pr_doc.items[0].name, "voucher_type": pr_doc.doctype},
|
||||||
|
"rack",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(sle_rack, "Rack 1")
|
||||||
|
|
||||||
|
dn_doc = create_delivery_note(qty=2, do_not_submit=True)
|
||||||
|
dn_doc.rack = "Rack 1"
|
||||||
|
dn_doc.save()
|
||||||
|
dn_doc.submit()
|
||||||
|
|
||||||
|
dn_doc.load_from_db()
|
||||||
|
|
||||||
|
self.assertEqual(dn_doc.items[0].rack, "Rack 1")
|
||||||
|
sle_rack = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{"voucher_detail_no": dn_doc.items[0].name, "voucher_type": dn_doc.doctype},
|
||||||
|
"rack",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(sle_rack, "Rack 1")
|
||||||
|
|
||||||
|
|
||||||
def prepare_test_data():
|
def prepare_test_data():
|
||||||
if not frappe.db.exists("DocType", "Shelf"):
|
if not frappe.db.exists("DocType", "Shelf"):
|
||||||
@ -160,6 +215,28 @@ def prepare_test_data():
|
|||||||
|
|
||||||
create_warehouse("Shelf Warehouse")
|
create_warehouse("Shelf Warehouse")
|
||||||
|
|
||||||
|
if not frappe.db.exists("DocType", "Rack"):
|
||||||
|
frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "DocType",
|
||||||
|
"name": "Rack",
|
||||||
|
"module": "Stock",
|
||||||
|
"custom": 1,
|
||||||
|
"naming_rule": "By fieldname",
|
||||||
|
"autoname": "field:rack_name",
|
||||||
|
"fields": [{"label": "Rack Name", "fieldname": "rack_name", "fieldtype": "Data"}],
|
||||||
|
"permissions": [
|
||||||
|
{"role": "System Manager", "permlevel": 0, "read": 1, "write": 1, "create": 1, "delete": 1}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
).insert(ignore_permissions=True)
|
||||||
|
|
||||||
|
for rack in ["Rack 1"]:
|
||||||
|
if not frappe.db.exists("Rack", rack):
|
||||||
|
frappe.get_doc({"doctype": "Rack", "rack_name": rack}).insert(ignore_permissions=True)
|
||||||
|
|
||||||
|
create_warehouse("Rack Warehouse")
|
||||||
|
|
||||||
|
|
||||||
def create_inventory_dimension(**args):
|
def create_inventory_dimension(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
@ -795,7 +795,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "customer_code",
|
"fieldname": "customer_code",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Small Text",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Customer Code",
|
"label": "Customer Code",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
@ -910,7 +910,7 @@
|
|||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-06-15 09:02:06.177691",
|
"modified": "2022-09-12 15:00:10.130340",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Item",
|
"name": "Item",
|
||||||
|
@ -778,6 +778,14 @@ class TestItem(FrappeTestCase):
|
|||||||
item.has_batch_no = 1
|
item.has_batch_no = 1
|
||||||
item.save()
|
item.save()
|
||||||
|
|
||||||
|
def test_customer_codes_length(self):
|
||||||
|
"""Check if item code with special characters are allowed."""
|
||||||
|
item = make_item(properties={"item_code": "Test Item Code With Special Characters"})
|
||||||
|
for row in range(3):
|
||||||
|
item.append("customer_items", {"ref_code": frappe.generate_hash("", 120)})
|
||||||
|
item.save()
|
||||||
|
self.assertTrue(len(item.customer_code) > 140)
|
||||||
|
|
||||||
|
|
||||||
def set_item_variant_settings(fields):
|
def set_item_variant_settings(fields):
|
||||||
doc = frappe.get_doc("Item Variant Settings")
|
doc = frappe.get_doc("Item Variant Settings")
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Barcode",
|
"label": "Barcode",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
|
"reqd": 1,
|
||||||
"unique": 1
|
"unique": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -36,7 +37,7 @@
|
|||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-06-01 06:24:33.969534",
|
"modified": "2022-08-24 19:59:47.871677",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Item Barcode",
|
"name": "Item Barcode",
|
||||||
|
@ -1,95 +1,43 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"beta": 0,
|
|
||||||
"creation": "2013-02-22 01:28:01",
|
"creation": "2013-02-22 01:28:01",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"supplier",
|
||||||
|
"supplier_part_no"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "supplier",
|
"fieldname": "supplier",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Supplier",
|
"label": "Supplier",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Supplier",
|
"options": "Supplier",
|
||||||
"permlevel": 0,
|
"reqd": 1
|
||||||
"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
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "supplier_part_no",
|
"fieldname": "supplier_part_no",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 1,
|
"in_global_search": 1,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Supplier Part Number",
|
"label": "Supplier Part Number",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "200px",
|
"print_width": "200px",
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "200px"
|
"width": "200px"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"image_view": 0,
|
|
||||||
"in_create": 0,
|
|
||||||
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"max_attachments": 0,
|
"links": [],
|
||||||
"modified": "2017-02-20 13:29:32.569715",
|
"modified": "2022-09-07 12:33:55.780062",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Item Supplier",
|
"name": "Item Supplier",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"quick_entry": 0,
|
"sort_field": "modified",
|
||||||
"read_only": 0,
|
"sort_order": "DESC",
|
||||||
"read_only_onload": 0,
|
"states": [],
|
||||||
"show_name_in_global_search": 0,
|
"track_changes": 1
|
||||||
"track_changes": 1,
|
|
||||||
"track_seen": 0
|
|
||||||
}
|
}
|
@ -183,7 +183,7 @@ class PickList(Document):
|
|||||||
frappe.throw("Row #{0}: Item Code is Mandatory".format(item.idx))
|
frappe.throw("Row #{0}: Item Code is Mandatory".format(item.idx))
|
||||||
item_code = item.item_code
|
item_code = item.item_code
|
||||||
reference = item.sales_order_item or item.material_request_item
|
reference = item.sales_order_item or item.material_request_item
|
||||||
key = (item_code, item.uom, reference)
|
key = (item_code, item.uom, item.warehouse, reference)
|
||||||
|
|
||||||
item.idx = None
|
item.idx = None
|
||||||
item.name = None
|
item.name = None
|
||||||
|
@ -362,6 +362,12 @@ class PurchaseReceipt(BuyingController):
|
|||||||
if credit_currency == self.company_currency
|
if credit_currency == self.company_currency
|
||||||
else flt(d.net_amount, d.precision("net_amount"))
|
else flt(d.net_amount, d.precision("net_amount"))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
outgoing_amount = d.base_net_amount
|
||||||
|
if self.is_internal_supplier and d.valuation_rate:
|
||||||
|
outgoing_amount = d.valuation_rate * d.stock_qty
|
||||||
|
credit_amount = outgoing_amount
|
||||||
|
|
||||||
if credit_amount:
|
if credit_amount:
|
||||||
account = warehouse_account[d.from_warehouse]["account"] if d.from_warehouse else stock_rbnb
|
account = warehouse_account[d.from_warehouse]["account"] if d.from_warehouse else stock_rbnb
|
||||||
|
|
||||||
@ -369,7 +375,7 @@ class PurchaseReceipt(BuyingController):
|
|||||||
gl_entries=gl_entries,
|
gl_entries=gl_entries,
|
||||||
account=account,
|
account=account,
|
||||||
cost_center=d.cost_center,
|
cost_center=d.cost_center,
|
||||||
debit=-1 * flt(d.base_net_amount, d.precision("base_net_amount")),
|
debit=-1 * flt(outgoing_amount, d.precision("base_net_amount")),
|
||||||
credit=0.0,
|
credit=0.0,
|
||||||
remarks=remarks,
|
remarks=remarks,
|
||||||
against_account=warehouse_account_name,
|
against_account=warehouse_account_name,
|
||||||
@ -456,7 +462,7 @@ class PurchaseReceipt(BuyingController):
|
|||||||
|
|
||||||
# divisional loss adjustment
|
# divisional loss adjustment
|
||||||
valuation_amount_as_per_doc = (
|
valuation_amount_as_per_doc = (
|
||||||
flt(d.base_net_amount, d.precision("base_net_amount"))
|
flt(outgoing_amount, d.precision("base_net_amount"))
|
||||||
+ flt(d.landed_cost_voucher_amount)
|
+ flt(d.landed_cost_voucher_amount)
|
||||||
+ flt(d.rm_supp_cost)
|
+ flt(d.rm_supp_cost)
|
||||||
+ flt(d.item_tax_amount)
|
+ flt(d.item_tax_amount)
|
||||||
@ -631,47 +637,6 @@ class PurchaseReceipt(BuyingController):
|
|||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
def add_gl_entry(
|
|
||||||
self,
|
|
||||||
gl_entries,
|
|
||||||
account,
|
|
||||||
cost_center,
|
|
||||||
debit,
|
|
||||||
credit,
|
|
||||||
remarks,
|
|
||||||
against_account,
|
|
||||||
debit_in_account_currency=None,
|
|
||||||
credit_in_account_currency=None,
|
|
||||||
account_currency=None,
|
|
||||||
project=None,
|
|
||||||
voucher_detail_no=None,
|
|
||||||
item=None,
|
|
||||||
posting_date=None,
|
|
||||||
):
|
|
||||||
|
|
||||||
gl_entry = {
|
|
||||||
"account": account,
|
|
||||||
"cost_center": cost_center,
|
|
||||||
"debit": debit,
|
|
||||||
"credit": credit,
|
|
||||||
"against": against_account,
|
|
||||||
"remarks": remarks,
|
|
||||||
}
|
|
||||||
|
|
||||||
if voucher_detail_no:
|
|
||||||
gl_entry.update({"voucher_detail_no": voucher_detail_no})
|
|
||||||
|
|
||||||
if debit_in_account_currency:
|
|
||||||
gl_entry.update({"debit_in_account_currency": debit_in_account_currency})
|
|
||||||
|
|
||||||
if credit_in_account_currency:
|
|
||||||
gl_entry.update({"credit_in_account_currency": credit_in_account_currency})
|
|
||||||
|
|
||||||
if posting_date:
|
|
||||||
gl_entry.update({"posting_date": posting_date})
|
|
||||||
|
|
||||||
gl_entries.append(self.get_gl_dict(gl_entry, item=item))
|
|
||||||
|
|
||||||
def get_asset_gl_entry(self, gl_entries):
|
def get_asset_gl_entry(self, gl_entries):
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
if item.is_fixed_asset:
|
if item.is_fixed_asset:
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||||
from frappe.utils import add_days, cint, cstr, flt, today
|
from frappe.utils import add_days, cint, cstr, flt, today
|
||||||
|
from pypika import functions as fn
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
||||||
@ -1156,6 +1157,125 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
if gle.account == account:
|
if gle.account == account:
|
||||||
self.assertEqual(gle.credit, 50)
|
self.assertEqual(gle.credit, 50)
|
||||||
|
|
||||||
|
def test_backdated_transaction_for_internal_transfer(self):
|
||||||
|
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
|
||||||
|
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||||
|
|
||||||
|
prepare_data_for_internal_transfer()
|
||||||
|
customer = "_Test Internal Customer 2"
|
||||||
|
company = "_Test Company with perpetual inventory"
|
||||||
|
|
||||||
|
from_warehouse = create_warehouse("_Test Internal From Warehouse New", company=company)
|
||||||
|
to_warehouse = create_warehouse("_Test Internal To Warehouse New", company=company)
|
||||||
|
item_doc = create_item("Test Internal Transfer Item")
|
||||||
|
|
||||||
|
target_warehouse = create_warehouse("_Test Internal GIT Warehouse New", company=company)
|
||||||
|
|
||||||
|
make_purchase_receipt(
|
||||||
|
item_code=item_doc.name,
|
||||||
|
company=company,
|
||||||
|
posting_date=add_days(today(), -1),
|
||||||
|
warehouse=from_warehouse,
|
||||||
|
qty=1,
|
||||||
|
rate=100,
|
||||||
|
)
|
||||||
|
|
||||||
|
dn1 = create_delivery_note(
|
||||||
|
item_code=item_doc.name,
|
||||||
|
company=company,
|
||||||
|
customer=customer,
|
||||||
|
cost_center="Main - TCP1",
|
||||||
|
expense_account="Cost of Goods Sold - TCP1",
|
||||||
|
qty=1,
|
||||||
|
rate=500,
|
||||||
|
warehouse=from_warehouse,
|
||||||
|
target_warehouse=target_warehouse,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(dn1.items[0].rate, 100)
|
||||||
|
|
||||||
|
pr1 = make_inter_company_purchase_receipt(dn1.name)
|
||||||
|
pr1.items[0].warehouse = to_warehouse
|
||||||
|
self.assertEqual(pr1.items[0].rate, 100)
|
||||||
|
pr1.submit()
|
||||||
|
|
||||||
|
# Backdated purchase receipt entry, the valuation rate should be updated for DN1 and PR1
|
||||||
|
make_purchase_receipt(
|
||||||
|
item_code=item_doc.name,
|
||||||
|
company=company,
|
||||||
|
posting_date=add_days(today(), -2),
|
||||||
|
warehouse=from_warehouse,
|
||||||
|
qty=1,
|
||||||
|
rate=200,
|
||||||
|
)
|
||||||
|
|
||||||
|
dn_value = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{"voucher_type": "Delivery Note", "voucher_no": dn1.name, "warehouse": target_warehouse},
|
||||||
|
"stock_value_difference",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(abs(dn_value), 200.00)
|
||||||
|
|
||||||
|
pr_value = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{"voucher_type": "Purchase Receipt", "voucher_no": pr1.name, "warehouse": to_warehouse},
|
||||||
|
"stock_value_difference",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(abs(pr_value), 200.00)
|
||||||
|
pr1.load_from_db()
|
||||||
|
|
||||||
|
self.assertEqual(pr1.items[0].valuation_rate, 200)
|
||||||
|
self.assertEqual(pr1.items[0].rate, 100)
|
||||||
|
|
||||||
|
Gl = frappe.qb.DocType("GL Entry")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(Gl)
|
||||||
|
.select(
|
||||||
|
(fn.Sum(Gl.debit) - fn.Sum(Gl.credit)).as_("value"),
|
||||||
|
)
|
||||||
|
.where((Gl.voucher_type == pr1.doctype) & (Gl.voucher_no == pr1.name))
|
||||||
|
).run(as_dict=True)
|
||||||
|
|
||||||
|
self.assertEqual(query[0].value, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_data_for_internal_transfer():
|
||||||
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||||
|
from erpnext.selling.doctype.customer.test_customer import create_internal_customer
|
||||||
|
|
||||||
|
company = "_Test Company with perpetual inventory"
|
||||||
|
|
||||||
|
create_internal_customer(
|
||||||
|
"_Test Internal Customer 2",
|
||||||
|
company,
|
||||||
|
company,
|
||||||
|
)
|
||||||
|
|
||||||
|
create_internal_supplier(
|
||||||
|
"_Test Internal Supplier 2",
|
||||||
|
company,
|
||||||
|
company,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not frappe.db.get_value("Company", company, "unrealized_profit_loss_account"):
|
||||||
|
account = "Unrealized Profit and Loss - TCP1"
|
||||||
|
if not frappe.db.exists("Account", account):
|
||||||
|
frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Account",
|
||||||
|
"account_name": "Unrealized Profit and Loss",
|
||||||
|
"parent_account": "Direct Income - TCP1",
|
||||||
|
"company": company,
|
||||||
|
"is_group": 0,
|
||||||
|
"account_type": "Income Account",
|
||||||
|
}
|
||||||
|
).insert()
|
||||||
|
|
||||||
|
frappe.db.set_value("Company", company, "unrealized_profit_loss_account", account)
|
||||||
|
|
||||||
|
|
||||||
def get_sl_entries(voucher_type, voucher_no):
|
def get_sl_entries(voucher_type, voucher_no):
|
||||||
return frappe.db.sql(
|
return frappe.db.sql(
|
||||||
|
@ -58,6 +58,21 @@ frappe.ui.form.on('Repost Item Valuation', {
|
|||||||
}
|
}
|
||||||
|
|
||||||
frm.trigger('show_reposting_progress');
|
frm.trigger('show_reposting_progress');
|
||||||
|
|
||||||
|
if (frm.doc.status === 'Queued' && frm.doc.docstatus === 1) {
|
||||||
|
frm.trigger('execute_reposting');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
execute_reposting(frm) {
|
||||||
|
frm.add_custom_button(__("Start Reposting"), () => {
|
||||||
|
frappe.call({
|
||||||
|
method: 'erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.execute_repost_item_valuation',
|
||||||
|
callback: function() {
|
||||||
|
frappe.msgprint(__('Reposting has been started in the background.'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
show_reposting_progress: function(frm) {
|
show_reposting_progress: function(frm) {
|
||||||
|
@ -307,3 +307,9 @@ def in_configured_timeslot(repost_settings=None, current_time=None):
|
|||||||
return end_time >= now_time >= start_time
|
return end_time >= now_time >= start_time
|
||||||
else:
|
else:
|
||||||
return now_time >= start_time or now_time <= end_time
|
return now_time >= start_time or now_time <= end_time
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def execute_repost_item_valuation():
|
||||||
|
"""Execute repost item valuation via scheduler."""
|
||||||
|
frappe.get_doc("Scheduled Job Type", "repost_item_valuation.repost_entries").enqueue(force=True)
|
||||||
|
@ -815,7 +815,8 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle
|
|||||||
return {
|
return {
|
||||||
"filters": {
|
"filters": {
|
||||||
"docstatus": 1,
|
"docstatus": 1,
|
||||||
"company": me.frm.doc.company
|
"company": me.frm.doc.company,
|
||||||
|
"status": ["not in", ["Completed", "Closed"]]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -117,6 +117,7 @@ class StockEntry(StockController):
|
|||||||
self.validate_work_order()
|
self.validate_work_order()
|
||||||
self.validate_bom()
|
self.validate_bom()
|
||||||
self.validate_purchase_order()
|
self.validate_purchase_order()
|
||||||
|
self.validate_subcontracting_order()
|
||||||
|
|
||||||
if self.purpose in ("Manufacture", "Repack"):
|
if self.purpose in ("Manufacture", "Repack"):
|
||||||
self.mark_finished_and_scrap_items()
|
self.mark_finished_and_scrap_items()
|
||||||
@ -875,25 +876,24 @@ class StockEntry(StockController):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
parent = frappe.qb.DocType("Stock Entry")
|
se = frappe.qb.DocType("Stock Entry")
|
||||||
child = frappe.qb.DocType("Stock Entry Detail")
|
se_detail = frappe.qb.DocType("Stock Entry Detail")
|
||||||
|
|
||||||
conditions = (
|
|
||||||
(parent.docstatus == 1)
|
|
||||||
& (child.item_code == se_item.item_code)
|
|
||||||
& (
|
|
||||||
(parent.purchase_order == self.purchase_order)
|
|
||||||
if self.subcontract_data.order_doctype == "Purchase Order"
|
|
||||||
else (parent.subcontracting_order == self.subcontracting_order)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
total_supplied = (
|
total_supplied = (
|
||||||
frappe.qb.from_(parent)
|
frappe.qb.from_(se)
|
||||||
.inner_join(child)
|
.inner_join(se_detail)
|
||||||
.on(parent.name == child.parent)
|
.on(se.name == se_detail.parent)
|
||||||
.select(Sum(child.transfer_qty))
|
.select(Sum(se_detail.transfer_qty))
|
||||||
.where(conditions)
|
.where(
|
||||||
|
(se.purpose == "Send to Subcontractor")
|
||||||
|
& (se.docstatus == 1)
|
||||||
|
& (se_detail.item_code == se_item.item_code)
|
||||||
|
& (
|
||||||
|
(se.purchase_order == self.purchase_order)
|
||||||
|
if self.subcontract_data.order_doctype == "Purchase Order"
|
||||||
|
else (se.subcontracting_order == self.subcontracting_order)
|
||||||
|
)
|
||||||
|
)
|
||||||
).run()[0][0]
|
).run()[0][0]
|
||||||
|
|
||||||
if flt(total_supplied, precision) > flt(total_allowed, precision):
|
if flt(total_supplied, precision) > flt(total_allowed, precision):
|
||||||
@ -960,6 +960,20 @@ class StockEntry(StockController):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def validate_subcontracting_order(self):
|
||||||
|
if self.get("subcontracting_order") and self.purpose in [
|
||||||
|
"Send to Subcontractor",
|
||||||
|
"Material Transfer",
|
||||||
|
]:
|
||||||
|
sco_status = frappe.db.get_value("Subcontracting Order", self.subcontracting_order, "status")
|
||||||
|
|
||||||
|
if sco_status == "Closed":
|
||||||
|
frappe.throw(
|
||||||
|
_("Cannot create Stock Entry against a closed Subcontracting Order {0}.").format(
|
||||||
|
self.subcontracting_order
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def mark_finished_and_scrap_items(self):
|
def mark_finished_and_scrap_items(self):
|
||||||
if any([d.item_code for d in self.items if (d.is_finished_item and d.t_warehouse)]):
|
if any([d.item_code for d in self.items if (d.is_finished_item and d.t_warehouse)]):
|
||||||
return
|
return
|
||||||
|
@ -649,21 +649,25 @@ class update_entries_after(object):
|
|||||||
|
|
||||||
elif (
|
elif (
|
||||||
sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"]
|
sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"]
|
||||||
and sle.actual_qty > 0
|
and sle.voucher_detail_no
|
||||||
and frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_internal_supplier")
|
and frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_internal_supplier")
|
||||||
):
|
):
|
||||||
sle_details = frappe.db.get_value(
|
field = (
|
||||||
"Stock Ledger Entry",
|
"delivery_note_item" if sle.voucher_type == "Purchase Receipt" else "sales_invoice_item"
|
||||||
{
|
)
|
||||||
"voucher_type": sle.voucher_type,
|
doctype = (
|
||||||
"voucher_no": sle.voucher_no,
|
"Delivery Note Item" if sle.voucher_type == "Purchase Receipt" else "Sales Invoice Item"
|
||||||
"dependant_sle_voucher_detail_no": sle.voucher_detail_no,
|
)
|
||||||
},
|
refernce_name = frappe.get_cached_value(
|
||||||
["stock_value_difference", "actual_qty"],
|
sle.voucher_type + " Item", sle.voucher_detail_no, field
|
||||||
as_dict=1,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
rate = abs(sle_details.stock_value_difference / sle.actual_qty)
|
if refernce_name:
|
||||||
|
rate = frappe.get_cached_value(
|
||||||
|
doctype,
|
||||||
|
refernce_name,
|
||||||
|
"incoming_rate",
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
if sle.voucher_type in ("Purchase Receipt", "Purchase Invoice"):
|
if sle.voucher_type in ("Purchase Receipt", "Purchase Invoice"):
|
||||||
rate_field = "valuation_rate"
|
rate_field = "valuation_rate"
|
||||||
@ -745,7 +749,12 @@ class update_entries_after(object):
|
|||||||
def update_rate_on_purchase_receipt(self, sle, outgoing_rate):
|
def update_rate_on_purchase_receipt(self, sle, outgoing_rate):
|
||||||
if frappe.db.exists(sle.voucher_type + " Item", sle.voucher_detail_no):
|
if frappe.db.exists(sle.voucher_type + " Item", sle.voucher_detail_no):
|
||||||
frappe.db.set_value(
|
frappe.db.set_value(
|
||||||
sle.voucher_type + " Item", sle.voucher_detail_no, "base_net_rate", outgoing_rate
|
sle.voucher_type + " Item",
|
||||||
|
sle.voucher_detail_no,
|
||||||
|
{
|
||||||
|
"base_net_rate": outgoing_rate,
|
||||||
|
"valuation_rate": outgoing_rate,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
frappe.db.set_value(
|
frappe.db.set_value(
|
||||||
|
@ -107,7 +107,7 @@ frappe.ui.form.on('Subcontracting Order', {
|
|||||||
get_materials_from_supplier: function (frm) {
|
get_materials_from_supplier: function (frm) {
|
||||||
let sco_rm_details = [];
|
let sco_rm_details = [];
|
||||||
|
|
||||||
if (frm.doc.supplied_items && frm.doc.per_received > 0) {
|
if (frm.doc.status != "Closed" && frm.doc.supplied_items && frm.doc.per_received > 0) {
|
||||||
frm.doc.supplied_items.forEach(d => {
|
frm.doc.supplied_items.forEach(d => {
|
||||||
if (d.total_supplied_qty > 0 && d.total_supplied_qty != d.consumed_qty) {
|
if (d.total_supplied_qty > 0 && d.total_supplied_qty != d.consumed_qty) {
|
||||||
sco_rm_details.push(d.name);
|
sco_rm_details.push(d.name);
|
||||||
|
@ -187,22 +187,13 @@ class TestSubcontractingOrder(FrappeTestCase):
|
|||||||
self.assertEqual(len(ste.items), len(rm_items))
|
self.assertEqual(len(ste.items), len(rm_items))
|
||||||
|
|
||||||
def test_update_reserved_qty_for_subcontracting(self):
|
def test_update_reserved_qty_for_subcontracting(self):
|
||||||
# Make stock available for raw materials
|
# Create RM Material Receipt
|
||||||
make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100)
|
make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item", qty=10, basic_rate=100)
|
||||||
make_stock_entry(
|
make_stock_entry(
|
||||||
target="_Test Warehouse - _TC", item_code="_Test Item Home Desktop 100", qty=20, basic_rate=100
|
target="_Test Warehouse - _TC", item_code="_Test Item Home Desktop 100", qty=20, basic_rate=100
|
||||||
)
|
)
|
||||||
make_stock_entry(
|
|
||||||
target="_Test Warehouse 1 - _TC", item_code="_Test Item", qty=30, basic_rate=100
|
|
||||||
)
|
|
||||||
make_stock_entry(
|
|
||||||
target="_Test Warehouse 1 - _TC",
|
|
||||||
item_code="_Test Item Home Desktop 100",
|
|
||||||
qty=30,
|
|
||||||
basic_rate=100,
|
|
||||||
)
|
|
||||||
|
|
||||||
bin1 = frappe.db.get_value(
|
bin_before_sco = frappe.db.get_value(
|
||||||
"Bin",
|
"Bin",
|
||||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
||||||
fieldname=["reserved_qty_for_sub_contract", "projected_qty", "modified"],
|
fieldname=["reserved_qty_for_sub_contract", "projected_qty", "modified"],
|
||||||
@ -222,102 +213,97 @@ class TestSubcontractingOrder(FrappeTestCase):
|
|||||||
]
|
]
|
||||||
sco = get_subcontracting_order(service_items=service_items)
|
sco = get_subcontracting_order(service_items=service_items)
|
||||||
|
|
||||||
bin2 = frappe.db.get_value(
|
bin_after_sco = frappe.db.get_value(
|
||||||
"Bin",
|
"Bin",
|
||||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
||||||
fieldname=["reserved_qty_for_sub_contract", "projected_qty", "modified"],
|
fieldname=["reserved_qty_for_sub_contract", "projected_qty", "modified"],
|
||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(bin2.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10)
|
# reserved_qty_for_sub_contract should be increased by 10
|
||||||
self.assertEqual(bin2.projected_qty, bin1.projected_qty - 10)
|
self.assertEqual(
|
||||||
self.assertNotEqual(bin1.modified, bin2.modified)
|
bin_after_sco.reserved_qty_for_sub_contract, bin_before_sco.reserved_qty_for_sub_contract + 10
|
||||||
|
)
|
||||||
|
|
||||||
# Create stock transfer
|
# projected_qty should be decreased by 10
|
||||||
|
self.assertEqual(bin_after_sco.projected_qty, bin_before_sco.projected_qty - 10)
|
||||||
|
|
||||||
|
self.assertNotEqual(bin_before_sco.modified, bin_after_sco.modified)
|
||||||
|
|
||||||
|
# Create Stock Entry(Send to Subcontractor)
|
||||||
rm_items = [
|
rm_items = [
|
||||||
{
|
{
|
||||||
"item_code": "_Test FG Item",
|
"item_code": "_Test FG Item",
|
||||||
"rm_item_code": "_Test Item",
|
"rm_item_code": "_Test Item",
|
||||||
"item_name": "_Test Item",
|
"item_name": "_Test Item",
|
||||||
"qty": 6,
|
"qty": 10,
|
||||||
"warehouse": "_Test Warehouse - _TC",
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
"rate": 100,
|
"rate": 100,
|
||||||
"amount": 600,
|
"amount": 1000,
|
||||||
"stock_uom": "Nos",
|
"stock_uom": "Nos",
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"item_code": "_Test FG Item",
|
||||||
|
"rm_item_code": "_Test Item Home Desktop 100",
|
||||||
|
"item_name": "_Test Item Home Desktop 100",
|
||||||
|
"qty": 20,
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
"rate": 100,
|
||||||
|
"amount": 2000,
|
||||||
|
"stock_uom": "Nos",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
ste = frappe.get_doc(make_rm_stock_entry(sco.name, rm_items))
|
ste = frappe.get_doc(make_rm_stock_entry(sco.name, rm_items))
|
||||||
ste.to_warehouse = "_Test Warehouse 1 - _TC"
|
ste.to_warehouse = "_Test Warehouse 1 - _TC"
|
||||||
ste.save()
|
ste.save()
|
||||||
ste.submit()
|
ste.submit()
|
||||||
|
|
||||||
bin3 = frappe.db.get_value(
|
bin_after_rm_transfer = frappe.db.get_value(
|
||||||
"Bin",
|
"Bin",
|
||||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
||||||
fieldname="reserved_qty_for_sub_contract",
|
fieldname="reserved_qty_for_sub_contract",
|
||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(bin3.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
|
# reserved_qty_for_sub_contract should be decreased by 10
|
||||||
|
self.assertEqual(
|
||||||
make_stock_entry(
|
bin_after_rm_transfer.reserved_qty_for_sub_contract,
|
||||||
target="_Test Warehouse 1 - _TC", item_code="_Test Item", qty=40, basic_rate=100
|
bin_after_sco.reserved_qty_for_sub_contract - 10,
|
||||||
)
|
|
||||||
make_stock_entry(
|
|
||||||
target="_Test Warehouse 1 - _TC",
|
|
||||||
item_code="_Test Item Home Desktop 100",
|
|
||||||
qty=40,
|
|
||||||
basic_rate=100,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Make SCR against the SCO
|
# Cancel Stock Entry(Send to Subcontractor)
|
||||||
scr = make_subcontracting_receipt(sco.name)
|
|
||||||
scr.save()
|
|
||||||
scr.submit()
|
|
||||||
|
|
||||||
bin4 = frappe.db.get_value(
|
|
||||||
"Bin",
|
|
||||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
|
||||||
fieldname="reserved_qty_for_sub_contract",
|
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(bin4.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
|
|
||||||
|
|
||||||
# Cancel SCR
|
|
||||||
scr.reload()
|
|
||||||
scr.cancel()
|
|
||||||
bin5 = frappe.db.get_value(
|
|
||||||
"Bin",
|
|
||||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
|
||||||
fieldname="reserved_qty_for_sub_contract",
|
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(bin5.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
|
|
||||||
|
|
||||||
# Cancel Stock Entry
|
|
||||||
ste.cancel()
|
ste.cancel()
|
||||||
bin6 = frappe.db.get_value(
|
bin_after_cancel_ste = frappe.db.get_value(
|
||||||
"Bin",
|
"Bin",
|
||||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
||||||
fieldname="reserved_qty_for_sub_contract",
|
fieldname="reserved_qty_for_sub_contract",
|
||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(bin6.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10)
|
# reserved_qty_for_sub_contract should be increased by 10
|
||||||
|
self.assertEqual(
|
||||||
|
bin_after_cancel_ste.reserved_qty_for_sub_contract,
|
||||||
|
bin_after_rm_transfer.reserved_qty_for_sub_contract + 10,
|
||||||
|
)
|
||||||
|
|
||||||
# Cancel PO
|
# Cancel SCO
|
||||||
sco.reload()
|
sco.reload()
|
||||||
sco.cancel()
|
sco.cancel()
|
||||||
bin7 = frappe.db.get_value(
|
bin_after_cancel_sco = frappe.db.get_value(
|
||||||
"Bin",
|
"Bin",
|
||||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
||||||
fieldname="reserved_qty_for_sub_contract",
|
fieldname="reserved_qty_for_sub_contract",
|
||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(bin7.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
|
# reserved_qty_for_sub_contract should be decreased by 10
|
||||||
|
self.assertEqual(
|
||||||
|
bin_after_cancel_sco.reserved_qty_for_sub_contract,
|
||||||
|
bin_after_cancel_ste.reserved_qty_for_sub_contract - 10,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
bin_after_cancel_sco.reserved_qty_for_sub_contract, bin_before_sco.reserved_qty_for_sub_contract
|
||||||
|
)
|
||||||
|
|
||||||
def test_exploded_items(self):
|
def test_exploded_items(self):
|
||||||
item_code = "_Test Subcontracted FG Item 11"
|
item_code = "_Test Subcontracted FG Item 11"
|
||||||
@ -516,6 +502,35 @@ class TestSubcontractingOrder(FrappeTestCase):
|
|||||||
|
|
||||||
set_backflush_based_on("BOM")
|
set_backflush_based_on("BOM")
|
||||||
|
|
||||||
|
def test_get_materials_from_supplier(self):
|
||||||
|
# Create SCO
|
||||||
|
sco = get_subcontracting_order()
|
||||||
|
|
||||||
|
# Transfer RM
|
||||||
|
rm_items = get_rm_items(sco.supplied_items)
|
||||||
|
itemwise_details = make_stock_in_entry(rm_items=rm_items)
|
||||||
|
make_stock_transfer_entry(
|
||||||
|
sco_no=sco.name,
|
||||||
|
rm_items=rm_items,
|
||||||
|
itemwise_details=copy.deepcopy(itemwise_details),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create SCR (Partial)
|
||||||
|
scr = make_subcontracting_receipt(sco.name)
|
||||||
|
scr.items[0].qty -= 5
|
||||||
|
scr.save()
|
||||||
|
scr.submit()
|
||||||
|
|
||||||
|
# Get RM from Supplier
|
||||||
|
ste = get_materials_from_supplier(sco.name, [d.name for d in sco.supplied_items])
|
||||||
|
ste.save()
|
||||||
|
ste.submit()
|
||||||
|
|
||||||
|
sco.load_from_db()
|
||||||
|
|
||||||
|
self.assertEqual(sco.status, "Closed")
|
||||||
|
self.assertEqual(sco.supplied_items[0].returned_qty, 5)
|
||||||
|
|
||||||
|
|
||||||
def create_subcontracting_order(**args):
|
def create_subcontracting_order(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
@ -524,7 +539,7 @@ def create_subcontracting_order(**args):
|
|||||||
for item in sco.items:
|
for item in sco.items:
|
||||||
item.include_exploded_items = args.get("include_exploded_items", 1)
|
item.include_exploded_items = args.get("include_exploded_items", 1)
|
||||||
|
|
||||||
if args.get("warehouse"):
|
if args.warehouse:
|
||||||
for item in sco.items:
|
for item in sco.items:
|
||||||
item.warehouse = args.warehouse
|
item.warehouse = args.warehouse
|
||||||
else:
|
else:
|
||||||
|
@ -5,6 +5,8 @@ import frappe
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import cint, flt, getdate, nowdate
|
from frappe.utils import cint, flt, getdate, nowdate
|
||||||
|
|
||||||
|
import erpnext
|
||||||
|
from erpnext.accounts.utils import get_account_currency
|
||||||
from erpnext.controllers.subcontracting_controller import SubcontractingController
|
from erpnext.controllers.subcontracting_controller import SubcontractingController
|
||||||
|
|
||||||
|
|
||||||
@ -75,6 +77,7 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
self.get_current_stock()
|
self.get_current_stock()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
|
self.validate_available_qty_for_consumption()
|
||||||
self.update_status_updater_args()
|
self.update_status_updater_args()
|
||||||
self.update_prevdoc_status()
|
self.update_prevdoc_status()
|
||||||
self.set_subcontracting_order_status()
|
self.set_subcontracting_order_status()
|
||||||
@ -107,10 +110,42 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
self.set_missing_values_in_supplied_items()
|
self.set_missing_values_in_supplied_items()
|
||||||
self.set_missing_values_in_items()
|
self.set_missing_values_in_items()
|
||||||
|
|
||||||
|
def set_available_qty_for_consumption(self):
|
||||||
|
supplied_items_details = {}
|
||||||
|
|
||||||
|
sco_supplied_item = frappe.qb.DocType("Subcontracting Order Supplied Item")
|
||||||
|
for item in self.get("items"):
|
||||||
|
supplied_items = (
|
||||||
|
frappe.qb.from_(sco_supplied_item)
|
||||||
|
.select(
|
||||||
|
sco_supplied_item.rm_item_code,
|
||||||
|
sco_supplied_item.reference_name,
|
||||||
|
(sco_supplied_item.total_supplied_qty - sco_supplied_item.consumed_qty).as_("available_qty"),
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
(sco_supplied_item.parent == item.subcontracting_order)
|
||||||
|
& (sco_supplied_item.main_item_code == item.item_code)
|
||||||
|
& (sco_supplied_item.reference_name == item.subcontracting_order_item)
|
||||||
|
)
|
||||||
|
).run(as_dict=True)
|
||||||
|
|
||||||
|
if supplied_items:
|
||||||
|
supplied_items_details[item.name] = {}
|
||||||
|
|
||||||
|
for supplied_item in supplied_items:
|
||||||
|
supplied_items_details[item.name][supplied_item.rm_item_code] = supplied_item.available_qty
|
||||||
|
else:
|
||||||
|
for item in self.get("supplied_items"):
|
||||||
|
item.available_qty_for_consumption = supplied_items_details.get(item.reference_name, {}).get(
|
||||||
|
item.rm_item_code, 0
|
||||||
|
)
|
||||||
|
|
||||||
def set_missing_values_in_supplied_items(self):
|
def set_missing_values_in_supplied_items(self):
|
||||||
for item in self.get("supplied_items") or []:
|
for item in self.get("supplied_items") or []:
|
||||||
item.amount = item.rate * item.consumed_qty
|
item.amount = item.rate * item.consumed_qty
|
||||||
|
|
||||||
|
self.set_available_qty_for_consumption()
|
||||||
|
|
||||||
def set_missing_values_in_items(self):
|
def set_missing_values_in_items(self):
|
||||||
rm_supp_cost = {}
|
rm_supp_cost = {}
|
||||||
for item in self.get("supplied_items") or []:
|
for item in self.get("supplied_items") or []:
|
||||||
@ -147,6 +182,17 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
_("Rejected Warehouse is mandatory against rejected Item {0}").format(item.item_code)
|
_("Rejected Warehouse is mandatory against rejected Item {0}").format(item.item_code)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def validate_available_qty_for_consumption(self):
|
||||||
|
for item in self.get("supplied_items"):
|
||||||
|
if (
|
||||||
|
item.available_qty_for_consumption and item.available_qty_for_consumption < item.consumed_qty
|
||||||
|
):
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"Row {0}: Consumed Qty must be less than or equal to Available Qty For Consumption in Consumed Items Table."
|
||||||
|
).format(item.idx)
|
||||||
|
)
|
||||||
|
|
||||||
def set_items_cost_center(self):
|
def set_items_cost_center(self):
|
||||||
if self.company:
|
if self.company:
|
||||||
cost_center = frappe.get_cached_value("Company", self.company, "cost_center")
|
cost_center = frappe.get_cached_value("Company", self.company, "cost_center")
|
||||||
@ -181,6 +227,137 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
if status:
|
if status:
|
||||||
frappe.db.set_value("Subcontracting Receipt", self.name, "status", status, update_modified)
|
frappe.db.set_value("Subcontracting Receipt", self.name, "status", status, update_modified)
|
||||||
|
|
||||||
|
def get_gl_entries(self, warehouse_account=None):
|
||||||
|
from erpnext.accounts.general_ledger import process_gl_map
|
||||||
|
|
||||||
|
gl_entries = []
|
||||||
|
self.make_item_gl_entries(gl_entries, warehouse_account)
|
||||||
|
|
||||||
|
return process_gl_map(gl_entries)
|
||||||
|
|
||||||
|
def make_item_gl_entries(self, gl_entries, warehouse_account=None):
|
||||||
|
if erpnext.is_perpetual_inventory_enabled(self.company):
|
||||||
|
stock_rbnb = self.get_company_default("stock_received_but_not_billed")
|
||||||
|
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
|
||||||
|
|
||||||
|
warehouse_with_no_account = []
|
||||||
|
|
||||||
|
for item in self.items:
|
||||||
|
if flt(item.rate) and flt(item.qty):
|
||||||
|
if warehouse_account.get(item.warehouse):
|
||||||
|
stock_value_diff = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{
|
||||||
|
"voucher_type": "Subcontracting Receipt",
|
||||||
|
"voucher_no": self.name,
|
||||||
|
"voucher_detail_no": item.name,
|
||||||
|
"warehouse": item.warehouse,
|
||||||
|
"is_cancelled": 0,
|
||||||
|
},
|
||||||
|
"stock_value_difference",
|
||||||
|
)
|
||||||
|
|
||||||
|
warehouse_account_name = warehouse_account[item.warehouse]["account"]
|
||||||
|
warehouse_account_currency = warehouse_account[item.warehouse]["account_currency"]
|
||||||
|
supplier_warehouse_account = warehouse_account.get(self.supplier_warehouse, {}).get("account")
|
||||||
|
supplier_warehouse_account_currency = warehouse_account.get(self.supplier_warehouse, {}).get(
|
||||||
|
"account_currency"
|
||||||
|
)
|
||||||
|
remarks = self.get("remarks") or _("Accounting Entry for Stock")
|
||||||
|
|
||||||
|
# FG Warehouse Account (Debit)
|
||||||
|
self.add_gl_entry(
|
||||||
|
gl_entries=gl_entries,
|
||||||
|
account=warehouse_account_name,
|
||||||
|
cost_center=item.cost_center,
|
||||||
|
debit=stock_value_diff,
|
||||||
|
credit=0.0,
|
||||||
|
remarks=remarks,
|
||||||
|
against_account=stock_rbnb,
|
||||||
|
account_currency=warehouse_account_currency,
|
||||||
|
item=item,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Supplier Warehouse Account (Credit)
|
||||||
|
if flt(item.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse):
|
||||||
|
self.add_gl_entry(
|
||||||
|
gl_entries=gl_entries,
|
||||||
|
account=supplier_warehouse_account,
|
||||||
|
cost_center=item.cost_center,
|
||||||
|
debit=0.0,
|
||||||
|
credit=flt(item.rm_supp_cost),
|
||||||
|
remarks=remarks,
|
||||||
|
against_account=warehouse_account_name,
|
||||||
|
account_currency=supplier_warehouse_account_currency,
|
||||||
|
item=item,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Expense Account (Credit)
|
||||||
|
if flt(item.service_cost_per_qty):
|
||||||
|
self.add_gl_entry(
|
||||||
|
gl_entries=gl_entries,
|
||||||
|
account=item.expense_account,
|
||||||
|
cost_center=item.cost_center,
|
||||||
|
debit=0.0,
|
||||||
|
credit=flt(item.service_cost_per_qty) * flt(item.qty),
|
||||||
|
remarks=remarks,
|
||||||
|
against_account=warehouse_account_name,
|
||||||
|
account_currency=get_account_currency(item.expense_account),
|
||||||
|
item=item,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Loss Account (Credit)
|
||||||
|
divisional_loss = flt(item.amount - stock_value_diff, item.precision("amount"))
|
||||||
|
|
||||||
|
if divisional_loss:
|
||||||
|
if self.is_return:
|
||||||
|
loss_account = expenses_included_in_valuation
|
||||||
|
else:
|
||||||
|
loss_account = item.expense_account
|
||||||
|
|
||||||
|
self.add_gl_entry(
|
||||||
|
gl_entries=gl_entries,
|
||||||
|
account=loss_account,
|
||||||
|
cost_center=item.cost_center,
|
||||||
|
debit=divisional_loss,
|
||||||
|
credit=0.0,
|
||||||
|
remarks=remarks,
|
||||||
|
against_account=warehouse_account_name,
|
||||||
|
account_currency=get_account_currency(loss_account),
|
||||||
|
project=item.project,
|
||||||
|
item=item,
|
||||||
|
)
|
||||||
|
elif (
|
||||||
|
item.warehouse not in warehouse_with_no_account
|
||||||
|
or item.rejected_warehouse not in warehouse_with_no_account
|
||||||
|
):
|
||||||
|
warehouse_with_no_account.append(item.warehouse)
|
||||||
|
|
||||||
|
# Additional Costs Expense Accounts (Credit)
|
||||||
|
for row in self.additional_costs:
|
||||||
|
credit_amount = (
|
||||||
|
flt(row.base_amount)
|
||||||
|
if (row.base_amount or row.account_currency != self.company_currency)
|
||||||
|
else flt(row.amount)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.add_gl_entry(
|
||||||
|
gl_entries=gl_entries,
|
||||||
|
account=row.expense_account,
|
||||||
|
cost_center=self.cost_center or self.get_company_default("cost_center"),
|
||||||
|
debit=0.0,
|
||||||
|
credit=credit_amount,
|
||||||
|
remarks=remarks,
|
||||||
|
against_account=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
if warehouse_with_no_account:
|
||||||
|
frappe.msgprint(
|
||||||
|
_("No accounting entries for the following warehouses")
|
||||||
|
+ ": \n"
|
||||||
|
+ "\n".join(warehouse_with_no_account)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_subcontract_return(source_name, target_doc=None):
|
def make_subcontract_return(source_name, target_doc=None):
|
||||||
|
@ -6,8 +6,10 @@ import copy
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests.utils import FrappeTestCase
|
||||||
from frappe.utils import flt
|
from frappe.utils import cint, flt
|
||||||
|
|
||||||
|
import erpnext
|
||||||
|
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
||||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||||
from erpnext.controllers.tests.test_subcontracting_controller import (
|
from erpnext.controllers.tests.test_subcontracting_controller import (
|
||||||
get_rm_items,
|
get_rm_items,
|
||||||
@ -22,6 +24,7 @@ from erpnext.controllers.tests.test_subcontracting_controller import (
|
|||||||
set_backflush_based_on,
|
set_backflush_based_on,
|
||||||
)
|
)
|
||||||
from erpnext.stock.doctype.item.test_item import make_item
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries
|
||||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||||
from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
|
from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
|
||||||
make_subcontracting_receipt,
|
make_subcontracting_receipt,
|
||||||
@ -70,6 +73,55 @@ class TestSubcontractingReceipt(FrappeTestCase):
|
|||||||
rm_supp_cost = sum(item.amount for item in scr.get("supplied_items"))
|
rm_supp_cost = sum(item.amount for item in scr.get("supplied_items"))
|
||||||
self.assertEqual(scr.get("items")[0].rm_supp_cost, flt(rm_supp_cost))
|
self.assertEqual(scr.get("items")[0].rm_supp_cost, flt(rm_supp_cost))
|
||||||
|
|
||||||
|
def test_available_qty_for_consumption(self):
|
||||||
|
make_stock_entry(
|
||||||
|
item_code="_Test Item", qty=100, target="_Test Warehouse 1 - _TC", basic_rate=100
|
||||||
|
)
|
||||||
|
make_stock_entry(
|
||||||
|
item_code="_Test Item Home Desktop 100",
|
||||||
|
qty=100,
|
||||||
|
target="_Test Warehouse 1 - _TC",
|
||||||
|
basic_rate=100,
|
||||||
|
)
|
||||||
|
service_items = [
|
||||||
|
{
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
"item_code": "Subcontracted Service Item 1",
|
||||||
|
"qty": 10,
|
||||||
|
"rate": 100,
|
||||||
|
"fg_item": "_Test FG Item",
|
||||||
|
"fg_item_qty": 10,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
sco = get_subcontracting_order(service_items=service_items)
|
||||||
|
rm_items = [
|
||||||
|
{
|
||||||
|
"main_item_code": "_Test FG Item",
|
||||||
|
"item_code": "_Test Item",
|
||||||
|
"qty": 5.0,
|
||||||
|
"rate": 100.0,
|
||||||
|
"stock_uom": "_Test UOM",
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"main_item_code": "_Test FG Item",
|
||||||
|
"item_code": "_Test Item Home Desktop 100",
|
||||||
|
"qty": 10.0,
|
||||||
|
"rate": 100.0,
|
||||||
|
"stock_uom": "_Test UOM",
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
itemwise_details = make_stock_in_entry(rm_items=rm_items)
|
||||||
|
make_stock_transfer_entry(
|
||||||
|
sco_no=sco.name,
|
||||||
|
rm_items=rm_items,
|
||||||
|
itemwise_details=copy.deepcopy(itemwise_details),
|
||||||
|
)
|
||||||
|
scr = make_subcontracting_receipt(sco.name)
|
||||||
|
scr.save()
|
||||||
|
self.assertRaises(frappe.ValidationError, scr.submit)
|
||||||
|
|
||||||
def test_subcontracting_gle_fg_item_rate_zero(self):
|
def test_subcontracting_gle_fg_item_rate_zero(self):
|
||||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries
|
||||||
|
|
||||||
@ -317,6 +369,103 @@ class TestSubcontractingReceipt(FrappeTestCase):
|
|||||||
args = frappe._dict(scr_name=scr1.name, qty=-15)
|
args = frappe._dict(scr_name=scr1.name, qty=-15)
|
||||||
self.assertRaises(OverAllowanceError, make_return_subcontracting_receipt, **args)
|
self.assertRaises(OverAllowanceError, make_return_subcontracting_receipt, **args)
|
||||||
|
|
||||||
|
def test_subcontracting_receipt_no_gl_entry(self):
|
||||||
|
sco = get_subcontracting_order()
|
||||||
|
rm_items = get_rm_items(sco.supplied_items)
|
||||||
|
itemwise_details = make_stock_in_entry(rm_items=rm_items)
|
||||||
|
make_stock_transfer_entry(
|
||||||
|
sco_no=sco.name,
|
||||||
|
rm_items=rm_items,
|
||||||
|
itemwise_details=copy.deepcopy(itemwise_details),
|
||||||
|
)
|
||||||
|
|
||||||
|
scr = make_subcontracting_receipt(sco.name)
|
||||||
|
scr.append(
|
||||||
|
"additional_costs",
|
||||||
|
{
|
||||||
|
"expense_account": "Expenses Included In Valuation - _TC",
|
||||||
|
"description": "Test Additional Costs",
|
||||||
|
"amount": 100,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
scr.save()
|
||||||
|
scr.submit()
|
||||||
|
|
||||||
|
stock_value_difference = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{
|
||||||
|
"voucher_type": "Subcontracting Receipt",
|
||||||
|
"voucher_no": scr.name,
|
||||||
|
"item_code": "Subcontracted Item SA7",
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
},
|
||||||
|
"stock_value_difference",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Service Cost(100 * 10) + Raw Materials Cost(50 * 10) + Additional Costs(100) = 1600
|
||||||
|
self.assertEqual(stock_value_difference, 1600)
|
||||||
|
self.assertFalse(get_gl_entries("Subcontracting Receipt", scr.name))
|
||||||
|
|
||||||
|
def test_subcontracting_receipt_gl_entry(self):
|
||||||
|
sco = get_subcontracting_order(
|
||||||
|
company="_Test Company with perpetual inventory",
|
||||||
|
warehouse="Stores - TCP1",
|
||||||
|
supplier_warehouse="Work In Progress - TCP1",
|
||||||
|
)
|
||||||
|
rm_items = get_rm_items(sco.supplied_items)
|
||||||
|
itemwise_details = make_stock_in_entry(rm_items=rm_items)
|
||||||
|
make_stock_transfer_entry(
|
||||||
|
sco_no=sco.name,
|
||||||
|
rm_items=rm_items,
|
||||||
|
itemwise_details=copy.deepcopy(itemwise_details),
|
||||||
|
)
|
||||||
|
|
||||||
|
scr = make_subcontracting_receipt(sco.name)
|
||||||
|
additional_costs_expense_account = "Expenses Included In Valuation - TCP1"
|
||||||
|
scr.append(
|
||||||
|
"additional_costs",
|
||||||
|
{
|
||||||
|
"expense_account": additional_costs_expense_account,
|
||||||
|
"description": "Test Additional Costs",
|
||||||
|
"amount": 100,
|
||||||
|
"base_amount": 100,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
scr.save()
|
||||||
|
scr.submit()
|
||||||
|
|
||||||
|
self.assertEqual(cint(erpnext.is_perpetual_inventory_enabled(scr.company)), 1)
|
||||||
|
|
||||||
|
gl_entries = get_gl_entries("Subcontracting Receipt", scr.name)
|
||||||
|
|
||||||
|
self.assertTrue(gl_entries)
|
||||||
|
|
||||||
|
fg_warehouse_ac = get_inventory_account(scr.company, scr.items[0].warehouse)
|
||||||
|
supplier_warehouse_ac = get_inventory_account(scr.company, scr.supplier_warehouse)
|
||||||
|
expense_account = scr.items[0].expense_account
|
||||||
|
|
||||||
|
if fg_warehouse_ac == supplier_warehouse_ac:
|
||||||
|
expected_values = {
|
||||||
|
fg_warehouse_ac: [2100.0, 1000.0], # FG Amount (D), RM Cost (C)
|
||||||
|
expense_account: [0.0, 1000.0], # Service Cost (C)
|
||||||
|
additional_costs_expense_account: [0.0, 100.0], # Additional Cost (C)
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
expected_values = {
|
||||||
|
fg_warehouse_ac: [2100.0, 0.0], # FG Amount (D)
|
||||||
|
supplier_warehouse_ac: [0.0, 1000.0], # RM Cost (C)
|
||||||
|
expense_account: [0.0, 1000.0], # Service Cost (C)
|
||||||
|
additional_costs_expense_account: [0.0, 100.0], # Additional Cost (C)
|
||||||
|
}
|
||||||
|
|
||||||
|
for gle in gl_entries:
|
||||||
|
self.assertEqual(expected_values[gle.account][0], gle.debit)
|
||||||
|
self.assertEqual(expected_values[gle.account][1], gle.credit)
|
||||||
|
|
||||||
|
scr.reload()
|
||||||
|
scr.cancel()
|
||||||
|
self.assertTrue(get_gl_entries("Subcontracting Receipt", scr.name))
|
||||||
|
|
||||||
|
|
||||||
def make_return_subcontracting_receipt(**args):
|
def make_return_subcontracting_receipt(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
"col_break2",
|
"col_break2",
|
||||||
"amount",
|
"amount",
|
||||||
"secbreak_2",
|
"secbreak_2",
|
||||||
|
"available_qty_for_consumption",
|
||||||
"required_qty",
|
"required_qty",
|
||||||
"col_break3",
|
"col_break3",
|
||||||
"consumed_qty",
|
"consumed_qty",
|
||||||
@ -75,8 +76,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "required_qty",
|
"fieldname": "required_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"in_list_view": 1,
|
"label": "Required Qty",
|
||||||
"label": "Available Qty For Consumption",
|
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@ -85,7 +85,7 @@
|
|||||||
"fieldname": "consumed_qty",
|
"fieldname": "consumed_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Qty to be Consumed",
|
"label": "Consumed Qty",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -179,12 +179,21 @@
|
|||||||
"options": "Subcontracting Order",
|
"options": "Subcontracting Order",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "available_qty_for_consumption",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Available Qty For Consumption",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-04-18 10:45:16.538479",
|
"modified": "2022-09-02 22:28:53.392381",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Subcontracting",
|
"module": "Subcontracting",
|
||||||
"name": "Subcontracting Receipt Supplied Item",
|
"name": "Subcontracting Receipt Supplied Item",
|
||||||
@ -193,6 +202,6 @@
|
|||||||
"permissions": [],
|
"permissions": [],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1,
|
"states": [],
|
||||||
"states": []
|
"track_changes": 1
|
||||||
}
|
}
|
@ -71,6 +71,9 @@ class TransactionBase(StatusUpdater):
|
|||||||
self.validate_value(field, condition, prevdoc_values[field], doc)
|
self.validate_value(field, condition, prevdoc_values[field], doc)
|
||||||
|
|
||||||
def validate_rate_with_reference_doc(self, ref_details):
|
def validate_rate_with_reference_doc(self, ref_details):
|
||||||
|
if self.get("is_internal_supplier"):
|
||||||
|
return
|
||||||
|
|
||||||
buying_doctypes = ["Purchase Order", "Purchase Invoice", "Purchase Receipt"]
|
buying_doctypes = ["Purchase Order", "Purchase Invoice", "Purchase Receipt"]
|
||||||
|
|
||||||
if self.doctype in buying_doctypes:
|
if self.doctype in buying_doctypes:
|
||||||
|
713
license.txt
713
license.txt
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user