Merge branch 'develop' into refactor/report/bom-stock-calculated
This commit is contained in:
commit
d3cd3bc5ef
@ -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).
|
||||||
|
@ -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")
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
)
|
)
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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")
|
||||||
|
@ -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()
|
||||||
@ -959,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
|
||||||
|
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