Merge branch 'develop' into loan_repayment_issue
This commit is contained in:
commit
4727e774c0
@ -6,6 +6,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
[![CI](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml/badge.svg?branch=develop)](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml)
|
[![CI](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml/badge.svg?branch=develop)](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml)
|
||||||
|
[![UI](https://github.com/erpnext/erpnext_ui_tests/actions/workflows/ui-tests.yml/badge.svg?branch=develop&event=schedule)](https://github.com/erpnext/erpnext_ui_tests/actions/workflows/ui-tests.yml)
|
||||||
[![Open Source Helpers](https://www.codetriage.com/frappe/erpnext/badges/users.svg)](https://www.codetriage.com/frappe/erpnext)
|
[![Open Source Helpers](https://www.codetriage.com/frappe/erpnext/badges/users.svg)](https://www.codetriage.com/frappe/erpnext)
|
||||||
[![codecov](https://codecov.io/gh/frappe/erpnext/branch/develop/graph/badge.svg?token=0TwvyUg3I5)](https://codecov.io/gh/frappe/erpnext)
|
[![codecov](https://codecov.io/gh/frappe/erpnext/branch/develop/graph/badge.svg?token=0TwvyUg3I5)](https://codecov.io/gh/frappe/erpnext)
|
||||||
[![docker pulls](https://img.shields.io/docker/pulls/frappe/erpnext-worker.svg)](https://hub.docker.com/r/frappe/erpnext-worker)
|
[![docker pulls](https://img.shields.io/docker/pulls/frappe/erpnext-worker.svg)](https://hub.docker.com/r/frappe/erpnext-worker)
|
||||||
|
@ -160,7 +160,7 @@ frappe.treeview_settings["Account"] = {
|
|||||||
let root_company = treeview.page.fields_dict.root_company.get_value();
|
let root_company = treeview.page.fields_dict.root_company.get_value();
|
||||||
|
|
||||||
if(root_company) {
|
if(root_company) {
|
||||||
frappe.throw(__("Please add the account to root level Company - ") + root_company);
|
frappe.throw(__("Please add the account to root level Company - {0}"), [root_company]);
|
||||||
} else {
|
} else {
|
||||||
treeview.new_node();
|
treeview.new_node();
|
||||||
}
|
}
|
||||||
|
@ -200,7 +200,7 @@ frappe.ui.form.on("Bank Statement Import", {
|
|||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (result.length > 0) {
|
if (result.length > 0) {
|
||||||
frm.add_custom_button("Report Error", () => {
|
frm.add_custom_button(__("Report Error"), () => {
|
||||||
let fake_xhr = {
|
let fake_xhr = {
|
||||||
responseText: JSON.stringify({
|
responseText: JSON.stringify({
|
||||||
exc: result[0].error,
|
exc: result[0].error,
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
@ -16,6 +17,6 @@ class CashFlowMapping(Document):
|
|||||||
]
|
]
|
||||||
if len(checked_fields) > 1:
|
if len(checked_fields) > 1:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
frappe._("You can only select a maximum of one option from the list of check boxes."),
|
_("You can only select a maximum of one option from the list of check boxes."),
|
||||||
title="Error",
|
title=_("Error"),
|
||||||
)
|
)
|
||||||
|
@ -68,9 +68,8 @@ class CurrencyExchangeSettings(Document):
|
|||||||
str(key.key).format(transaction_date=nowdate(), to_currency="INR", from_currency="USD")
|
str(key.key).format(transaction_date=nowdate(), to_currency="INR", from_currency="USD")
|
||||||
]
|
]
|
||||||
except Exception:
|
except Exception:
|
||||||
frappe.throw("Invalid result key. Response: " + response.text)
|
frappe.throw(_("Invalid result key. Response:") + " " + response.text)
|
||||||
if not isinstance(value, (int, float)):
|
if not isinstance(value, (int, float)):
|
||||||
frappe.throw(_("Returned exchange rate is neither integer not float."))
|
frappe.throw(_("Returned exchange rate is neither integer not float."))
|
||||||
|
|
||||||
self.url = response.url
|
self.url = response.url
|
||||||
frappe.msgprint("Exchange rate of USD to INR is " + str(value))
|
|
||||||
|
@ -42,12 +42,7 @@ class ModeofPayment(Document):
|
|||||||
pos_profiles = list(map(lambda x: x[0], pos_profiles))
|
pos_profiles = list(map(lambda x: x[0], pos_profiles))
|
||||||
|
|
||||||
if pos_profiles:
|
if pos_profiles:
|
||||||
message = (
|
message = _(
|
||||||
"POS Profile "
|
"POS Profile {} contains Mode of Payment {}. Please remove them to disable this mode."
|
||||||
+ frappe.bold(", ".join(pos_profiles))
|
).format(frappe.bold(", ".join(pos_profiles)), frappe.bold(str(self.name)))
|
||||||
+ " contains \
|
frappe.throw(message, title=_("Not Allowed"))
|
||||||
Mode of Payment "
|
|
||||||
+ frappe.bold(str(self.name))
|
|
||||||
+ ". Please remove them to disable this mode."
|
|
||||||
)
|
|
||||||
frappe.throw(_(message), title="Not Allowed")
|
|
||||||
|
@ -61,13 +61,13 @@ class POSProfile(Document):
|
|||||||
|
|
||||||
if len(item_groups) != len(set(item_groups)):
|
if len(item_groups) != len(set(item_groups)):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Duplicate item group found in the item group table"), title="Duplicate Item Group"
|
_("Duplicate item group found in the item group table"), title=_("Duplicate Item Group")
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(customer_groups) != len(set(customer_groups)):
|
if len(customer_groups) != len(set(customer_groups)):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Duplicate customer group found in the cutomer group table"),
|
_("Duplicate customer group found in the cutomer group table"),
|
||||||
title="Duplicate Customer Group",
|
title=_("Duplicate Customer Group"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def validate_payment_methods(self):
|
def validate_payment_methods(self):
|
||||||
|
@ -8,7 +8,7 @@ frappe.ui.form.on('Process Statement Of Accounts', {
|
|||||||
},
|
},
|
||||||
refresh: function(frm){
|
refresh: function(frm){
|
||||||
if(!frm.doc.__islocal) {
|
if(!frm.doc.__islocal) {
|
||||||
frm.add_custom_button('Send Emails',function(){
|
frm.add_custom_button(__('Send Emails'), function(){
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_emails",
|
method: "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_emails",
|
||||||
args: {
|
args: {
|
||||||
@ -24,7 +24,7 @@ frappe.ui.form.on('Process Statement Of Accounts', {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
frm.add_custom_button('Download',function(){
|
frm.add_custom_button(__('Download'), function(){
|
||||||
var url = frappe.urllib.get_full_url(
|
var url = frappe.urllib.get_full_url(
|
||||||
'/api/method/erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.download_statements?'
|
'/api/method/erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.download_statements?'
|
||||||
+ 'document_name='+encodeURIComponent(frm.doc.name))
|
+ 'document_name='+encodeURIComponent(frm.doc.name))
|
||||||
|
@ -141,7 +141,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
|||||||
})
|
})
|
||||||
}, __("Get Items From"));
|
}, __("Get Items From"));
|
||||||
}
|
}
|
||||||
this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted==="Yes");
|
this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted);
|
||||||
|
|
||||||
if (doc.docstatus == 1 && !doc.inter_company_invoice_reference) {
|
if (doc.docstatus == 1 && !doc.inter_company_invoice_reference) {
|
||||||
frappe.model.with_doc("Supplier", me.frm.doc.supplier, function() {
|
frappe.model.with_doc("Supplier", me.frm.doc.supplier, function() {
|
||||||
@ -571,10 +571,10 @@ frappe.ui.form.on("Purchase Invoice", {
|
|||||||
},
|
},
|
||||||
|
|
||||||
is_subcontracted: function(frm) {
|
is_subcontracted: function(frm) {
|
||||||
if (frm.doc.is_subcontracted === "Yes") {
|
if (frm.doc.is_subcontracted) {
|
||||||
erpnext.buying.get_default_bom(frm);
|
erpnext.buying.get_default_bom(frm);
|
||||||
}
|
}
|
||||||
frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted==="Yes");
|
frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted);
|
||||||
},
|
},
|
||||||
|
|
||||||
update_stock: function(frm) {
|
update_stock: function(frm) {
|
||||||
|
@ -543,11 +543,10 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "No",
|
"default": "0",
|
||||||
"fieldname": "is_subcontracted",
|
"fieldname": "is_subcontracted",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Check",
|
||||||
"label": "Raw Materials Supplied",
|
"label": "Is Subcontracted",
|
||||||
"options": "No\nYes",
|
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1366,7 +1365,7 @@
|
|||||||
"width": "50px"
|
"width": "50px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.update_stock && doc.is_subcontracted==\"Yes\"",
|
"depends_on": "eval:doc.update_stock && doc.is_subcontracted",
|
||||||
"fieldname": "supplier_warehouse",
|
"fieldname": "supplier_warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Supplier Warehouse",
|
"label": "Supplier Warehouse",
|
||||||
|
@ -901,7 +901,7 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
pi = make_purchase_invoice(
|
pi = make_purchase_invoice(
|
||||||
item_code="_Test FG Item", qty=10, rate=500, update_stock=1, is_subcontracted="Yes"
|
item_code="_Test FG Item", qty=10, rate=500, update_stock=1, is_subcontracted=1
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(len(pi.get("supplied_items")), 2)
|
self.assertEqual(len(pi.get("supplied_items")), 2)
|
||||||
@ -1611,7 +1611,7 @@ def make_purchase_invoice(**args):
|
|||||||
pi.conversion_rate = args.conversion_rate or 1
|
pi.conversion_rate = args.conversion_rate or 1
|
||||||
pi.is_return = args.is_return
|
pi.is_return = args.is_return
|
||||||
pi.return_against = args.return_against
|
pi.return_against = args.return_against
|
||||||
pi.is_subcontracted = args.is_subcontracted or "No"
|
pi.is_subcontracted = args.is_subcontracted or 0
|
||||||
pi.supplier_warehouse = args.supplier_warehouse or "_Test Warehouse 1 - _TC"
|
pi.supplier_warehouse = args.supplier_warehouse or "_Test Warehouse 1 - _TC"
|
||||||
pi.cost_center = args.parent_cost_center
|
pi.cost_center = args.parent_cost_center
|
||||||
|
|
||||||
@ -1674,7 +1674,7 @@ def make_purchase_invoice_against_cost_center(**args):
|
|||||||
pi.is_return = args.is_return
|
pi.is_return = args.is_return
|
||||||
pi.is_return = args.is_return
|
pi.is_return = args.is_return
|
||||||
pi.credit_to = args.return_against or "Creditors - _TC"
|
pi.credit_to = args.return_against or "Creditors - _TC"
|
||||||
pi.is_subcontracted = args.is_subcontracted or "No"
|
pi.is_subcontracted = args.is_subcontracted or 0
|
||||||
if args.supplier_warehouse:
|
if args.supplier_warehouse:
|
||||||
pi.supplier_warehouse = "_Test Warehouse 1 - _TC"
|
pi.supplier_warehouse = "_Test Warehouse 1 - _TC"
|
||||||
|
|
||||||
|
@ -623,7 +623,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"depends_on": "eval:parent.is_subcontracted == 'Yes'",
|
"depends_on": "eval:parent.is_subcontracted",
|
||||||
"fieldname": "include_exploded_items",
|
"fieldname": "include_exploded_items",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Include Exploded Items",
|
"label": "Include Exploded Items",
|
||||||
|
@ -1412,7 +1412,7 @@ class SalesInvoice(SellingController):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
frappe.throw(_("Select change amount account"), title="Mandatory Field")
|
frappe.throw(_("Select change amount account"), title=_("Mandatory Field"))
|
||||||
|
|
||||||
def make_write_off_gl_entry(self, gl_entries):
|
def make_write_off_gl_entry(self, gl_entries):
|
||||||
# write off entries, applicable if only pos
|
# write off entries, applicable if only pos
|
||||||
|
@ -34,7 +34,9 @@ class TaxWithholdingCategory(Document):
|
|||||||
|
|
||||||
def validate_thresholds(self):
|
def validate_thresholds(self):
|
||||||
for d in self.get("rates"):
|
for d in self.get("rates"):
|
||||||
if d.cumulative_threshold and d.cumulative_threshold < d.single_threshold:
|
if (
|
||||||
|
d.cumulative_threshold and d.single_threshold and d.cumulative_threshold < d.single_threshold
|
||||||
|
):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Row #{0}: Cumulative threshold cannot be less than Single Transaction threshold").format(
|
_("Row #{0}: Cumulative threshold cannot be less than Single Transaction threshold").format(
|
||||||
d.idx
|
d.idx
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
{%- from "templates/print_formats/standard_macros.html" import add_header, render_field, print_value -%}
|
{%- from "templates/print_formats/standard_macros.html" import add_header, render_field, print_value -%}
|
||||||
{%- set einvoice = json.loads(doc.signed_einvoice) -%}
|
|
||||||
|
|
||||||
<div class="page-break">
|
<div class="page-break">
|
||||||
|
{% if doc.signed_einvoice %}
|
||||||
|
{%- set einvoice = json.loads(doc.signed_einvoice) -%}
|
||||||
<div {% if print_settings.repeat_header_footer %} id="header-html" class="hidden-pdf" {% endif %}>
|
<div {% if print_settings.repeat_header_footer %} id="header-html" class="hidden-pdf" {% endif %}>
|
||||||
{% if letter_head and not no_letterhead %}
|
{% if letter_head and not no_letterhead %}
|
||||||
<div class="letter-head">{{ letter_head }}</div>
|
<div class="letter-head">{{ letter_head }}</div>
|
||||||
@ -170,4 +171,10 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="text-center" style="color: var(--gray-500); font-size: 14px;">
|
||||||
|
You must generate IRN before you can preview GST E-Invoice.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -201,17 +201,17 @@ def get_report_summary(
|
|||||||
net_provisional_profit_loss += provisional_profit_loss.get(key)
|
net_provisional_profit_loss += provisional_profit_loss.get(key)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{"value": net_asset, "label": "Total Asset", "datatype": "Currency", "currency": currency},
|
{"value": net_asset, "label": _("Total Asset"), "datatype": "Currency", "currency": currency},
|
||||||
{
|
{
|
||||||
"value": net_liability,
|
"value": net_liability,
|
||||||
"label": "Total Liability",
|
"label": _("Total Liability"),
|
||||||
"datatype": "Currency",
|
"datatype": "Currency",
|
||||||
"currency": currency,
|
"currency": currency,
|
||||||
},
|
},
|
||||||
{"value": net_equity, "label": "Total Equity", "datatype": "Currency", "currency": currency},
|
{"value": net_equity, "label": _("Total Equity"), "datatype": "Currency", "currency": currency},
|
||||||
{
|
{
|
||||||
"value": net_provisional_profit_loss,
|
"value": net_provisional_profit_loss,
|
||||||
"label": "Provisional Profit / Loss (Credit)",
|
"label": _("Provisional Profit / Loss (Credit)"),
|
||||||
"indicator": "Green" if net_provisional_profit_loss > 0 else "Red",
|
"indicator": "Green" if net_provisional_profit_loss > 0 else "Red",
|
||||||
"datatype": "Currency",
|
"datatype": "Currency",
|
||||||
"currency": currency,
|
"currency": currency,
|
||||||
|
@ -97,8 +97,8 @@ def get_columns(filters):
|
|||||||
if filters["period"] == "Yearly":
|
if filters["period"] == "Yearly":
|
||||||
labels = [
|
labels = [
|
||||||
_("Budget") + " " + str(year[0]),
|
_("Budget") + " " + str(year[0]),
|
||||||
_("Actual ") + " " + str(year[0]),
|
_("Actual") + " " + str(year[0]),
|
||||||
_("Variance ") + " " + str(year[0]),
|
_("Variance") + " " + str(year[0]),
|
||||||
]
|
]
|
||||||
for label in labels:
|
for label in labels:
|
||||||
columns.append(
|
columns.append(
|
||||||
|
@ -230,7 +230,7 @@ def get_columns(dimension_list):
|
|||||||
columns.append(
|
columns.append(
|
||||||
{
|
{
|
||||||
"fieldname": "total",
|
"fieldname": "total",
|
||||||
"label": "Total",
|
"label": _("Total"),
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"options": "currency",
|
"options": "currency",
|
||||||
"width": 150,
|
"width": 150,
|
||||||
|
@ -29,7 +29,7 @@ def get_columns():
|
|||||||
"options": "Item Group",
|
"options": "Item Group",
|
||||||
"width": 150,
|
"width": 150,
|
||||||
},
|
},
|
||||||
{"fieldname": "item", "fieldtype": "Link", "options": "Item", "label": "Item", "width": 150},
|
{"fieldname": "item", "fieldtype": "Link", "options": "Item", "label": _("Item"), "width": 150},
|
||||||
{"fieldname": "item_name", "fieldtype": "Data", "label": _("Item Name"), "width": 150},
|
{"fieldname": "item_name", "fieldtype": "Data", "label": _("Item Name"), "width": 150},
|
||||||
{
|
{
|
||||||
"fieldname": "customer",
|
"fieldname": "customer",
|
||||||
|
@ -115,9 +115,9 @@ def get_columns(filters):
|
|||||||
{"fieldname": "credit", "label": _("Credit"), "fieldtype": "Currency", "width": 140},
|
{"fieldname": "credit", "label": _("Credit"), "fieldtype": "Currency", "width": 140},
|
||||||
{"fieldname": "remarks", "label": _("Remarks"), "fieldtype": "Data", "width": 200},
|
{"fieldname": "remarks", "label": _("Remarks"), "fieldtype": "Data", "width": 200},
|
||||||
{"fieldname": "age", "label": _("Age"), "fieldtype": "Int", "width": 50},
|
{"fieldname": "age", "label": _("Age"), "fieldtype": "Int", "width": 50},
|
||||||
{"fieldname": "range1", "label": "0-30", "fieldtype": "Currency", "width": 140},
|
{"fieldname": "range1", "label": _("0-30"), "fieldtype": "Currency", "width": 140},
|
||||||
{"fieldname": "range2", "label": "30-60", "fieldtype": "Currency", "width": 140},
|
{"fieldname": "range2", "label": _("30-60"), "fieldtype": "Currency", "width": 140},
|
||||||
{"fieldname": "range3", "label": "60-90", "fieldtype": "Currency", "width": 140},
|
{"fieldname": "range3", "label": _("60-90"), "fieldtype": "Currency", "width": 140},
|
||||||
{"fieldname": "range4", "label": _("90 Above"), "fieldtype": "Currency", "width": 140},
|
{"fieldname": "range4", "label": _("90 Above"), "fieldtype": "Currency", "width": 140},
|
||||||
{
|
{
|
||||||
"fieldname": "delay_in_payment",
|
"fieldname": "delay_in_payment",
|
||||||
|
@ -302,7 +302,7 @@
|
|||||||
"is_opening": "No",
|
"is_opening": "No",
|
||||||
"is_paid": 0,
|
"is_paid": 0,
|
||||||
"is_return": 0,
|
"is_return": 0,
|
||||||
"is_subcontracted": "No",
|
"is_subcontracted": 0,
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"allow_zero_valuation_rate": 0,
|
"allow_zero_valuation_rate": 0,
|
||||||
|
@ -87,7 +87,7 @@ class AssetCategory(Document):
|
|||||||
missing_cwip_accounts_for_company.append(get_link_to_form("Company", d.company_name))
|
missing_cwip_accounts_for_company.append(get_link_to_form("Company", d.company_name))
|
||||||
|
|
||||||
if missing_cwip_accounts_for_company:
|
if missing_cwip_accounts_for_company:
|
||||||
msg = _("""To enable Capital Work in Progress Accounting, """)
|
msg = _("""To enable Capital Work in Progress Accounting,""") + " "
|
||||||
msg += _("""you must select Capital Work in Progress Account in accounts table""")
|
msg += _("""you must select Capital Work in Progress Account in accounts table""")
|
||||||
msg += "<br><br>"
|
msg += "<br><br>"
|
||||||
msg += _("You can also set default CWIP account in Company {}").format(
|
msg += _("You can also set default CWIP account in Company {}").format(
|
||||||
|
@ -46,10 +46,9 @@ class AssetMovement(Document):
|
|||||||
if d.target_location:
|
if d.target_location:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"Issuing cannot be done to a location. \
|
"Issuing cannot be done to a location. Please enter employee who has issued Asset {0}"
|
||||||
Please enter employee who has issued Asset {0}"
|
|
||||||
).format(d.asset),
|
).format(d.asset),
|
||||||
title="Incorrect Movement Purpose",
|
title=_("Incorrect Movement Purpose"),
|
||||||
)
|
)
|
||||||
if not d.to_employee:
|
if not d.to_employee:
|
||||||
frappe.throw(_("Employee is required while issuing Asset {0}").format(d.asset))
|
frappe.throw(_("Employee is required while issuing Asset {0}").format(d.asset))
|
||||||
@ -58,10 +57,9 @@ class AssetMovement(Document):
|
|||||||
if d.to_employee:
|
if d.to_employee:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"Transferring cannot be done to an Employee. \
|
"Transferring cannot be done to an Employee. Please enter location where Asset {0} has to be transferred"
|
||||||
Please enter location where Asset {0} has to be transferred"
|
|
||||||
).format(d.asset),
|
).format(d.asset),
|
||||||
title="Incorrect Movement Purpose",
|
title=_("Incorrect Movement Purpose"),
|
||||||
)
|
)
|
||||||
if not d.target_location:
|
if not d.target_location:
|
||||||
frappe.throw(_("Target Location is required while transferring Asset {0}").format(d.asset))
|
frappe.throw(_("Target Location is required while transferring Asset {0}").format(d.asset))
|
||||||
@ -89,8 +87,7 @@ class AssetMovement(Document):
|
|||||||
if d.to_employee and d.target_location:
|
if d.to_employee and d.target_location:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"Asset {0} cannot be received at a location and \
|
"Asset {0} cannot be received at a location and given to employee in a single movement"
|
||||||
given to employee in a single movement"
|
|
||||||
).format(d.asset)
|
).format(d.asset)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ frappe.ui.form.on('Asset Repair', {
|
|||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
if (frm.doc.docstatus) {
|
if (frm.doc.docstatus) {
|
||||||
frm.add_custom_button("View General Ledger", function() {
|
frm.add_custom_button(__("View General Ledger"), function() {
|
||||||
frappe.route_options = {
|
frappe.route_options = {
|
||||||
"voucher_no": frm.doc.name
|
"voucher_no": frm.doc.name
|
||||||
};
|
};
|
||||||
|
@ -37,7 +37,7 @@ class AssetValueAdjustment(Document):
|
|||||||
_("Asset Value Adjustment cannot be posted before Asset's purchase date <b>{0}</b>.").format(
|
_("Asset Value Adjustment cannot be posted before Asset's purchase date <b>{0}</b>.").format(
|
||||||
formatdate(asset_purchase_date)
|
formatdate(asset_purchase_date)
|
||||||
),
|
),
|
||||||
title="Incorrect Date",
|
title=_("Incorrect Date"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_difference_amount(self):
|
def set_difference_amount(self):
|
||||||
|
@ -179,7 +179,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
|
|||||||
if (doc.status != "On Hold") {
|
if (doc.status != "On Hold") {
|
||||||
if(flt(doc.per_received) < 100 && allow_receipt) {
|
if(flt(doc.per_received) < 100 && allow_receipt) {
|
||||||
cur_frm.add_custom_button(__('Purchase Receipt'), this.make_purchase_receipt, __('Create'));
|
cur_frm.add_custom_button(__('Purchase Receipt'), this.make_purchase_receipt, __('Create'));
|
||||||
if(doc.is_subcontracted==="Yes" && me.has_unsupplied_items()) {
|
if(doc.is_subcontracted && me.has_unsupplied_items()) {
|
||||||
cur_frm.add_custom_button(__('Material to Supplier'),
|
cur_frm.add_custom_button(__('Material to Supplier'),
|
||||||
function() { me.make_stock_entry(); }, __("Transfer"));
|
function() { me.make_stock_entry(); }, __("Transfer"));
|
||||||
}
|
}
|
||||||
@ -636,7 +636,7 @@ function set_schedule_date(frm) {
|
|||||||
frappe.provide("erpnext.buying");
|
frappe.provide("erpnext.buying");
|
||||||
|
|
||||||
frappe.ui.form.on("Purchase Order", "is_subcontracted", function(frm) {
|
frappe.ui.form.on("Purchase Order", "is_subcontracted", function(frm) {
|
||||||
if (frm.doc.is_subcontracted === "Yes") {
|
if (frm.doc.is_subcontracted) {
|
||||||
erpnext.buying.get_default_bom(frm);
|
erpnext.buying.get_default_bom(frm);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -457,16 +457,15 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "No",
|
"default": "0",
|
||||||
"fieldname": "is_subcontracted",
|
"fieldname": "is_subcontracted",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Check",
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Supply Raw Materials",
|
"label": "Is Subcontracted",
|
||||||
"options": "No\nYes",
|
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.is_subcontracted==\"Yes\"",
|
"depends_on": "eval:doc.is_subcontracted",
|
||||||
"fieldname": "supplier_warehouse",
|
"fieldname": "supplier_warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Supplier Warehouse",
|
"label": "Supplier Warehouse",
|
||||||
|
@ -194,7 +194,7 @@ class PurchaseOrder(BuyingController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def validate_bom_for_subcontracting_items(self):
|
def validate_bom_for_subcontracting_items(self):
|
||||||
if self.is_subcontracted == "Yes":
|
if self.is_subcontracted:
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
if not item.bom:
|
if not item.bom:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
@ -294,7 +294,7 @@ class PurchaseOrder(BuyingController):
|
|||||||
self.set_status(update=True, status=status)
|
self.set_status(update=True, status=status)
|
||||||
self.update_requested_qty()
|
self.update_requested_qty()
|
||||||
self.update_ordered_qty()
|
self.update_ordered_qty()
|
||||||
if self.is_subcontracted == "Yes":
|
if self.is_subcontracted:
|
||||||
self.update_reserved_qty_for_subcontract()
|
self.update_reserved_qty_for_subcontract()
|
||||||
|
|
||||||
self.notify_update()
|
self.notify_update()
|
||||||
@ -311,7 +311,7 @@ class PurchaseOrder(BuyingController):
|
|||||||
self.update_ordered_qty()
|
self.update_ordered_qty()
|
||||||
self.validate_budget()
|
self.validate_budget()
|
||||||
|
|
||||||
if self.is_subcontracted == "Yes":
|
if self.is_subcontracted:
|
||||||
self.update_reserved_qty_for_subcontract()
|
self.update_reserved_qty_for_subcontract()
|
||||||
|
|
||||||
frappe.get_doc("Authorization Control").validate_approving_authority(
|
frappe.get_doc("Authorization Control").validate_approving_authority(
|
||||||
@ -331,7 +331,7 @@ class PurchaseOrder(BuyingController):
|
|||||||
if self.has_drop_ship_item():
|
if self.has_drop_ship_item():
|
||||||
self.update_delivered_qty_in_sales_order()
|
self.update_delivered_qty_in_sales_order()
|
||||||
|
|
||||||
if self.is_subcontracted == "Yes":
|
if self.is_subcontracted:
|
||||||
self.update_reserved_qty_for_subcontract()
|
self.update_reserved_qty_for_subcontract()
|
||||||
|
|
||||||
self.check_on_hold_or_closed_status()
|
self.check_on_hold_or_closed_status()
|
||||||
|
@ -390,7 +390,7 @@ class TestPurchaseOrder(FrappeTestCase):
|
|||||||
frappe.get_doc("Item Tax Template", "Test Update Items Template - _TC").delete()
|
frappe.get_doc("Item Tax Template", "Test Update Items Template - _TC").delete()
|
||||||
|
|
||||||
def test_update_child_uom_conv_factor_change(self):
|
def test_update_child_uom_conv_factor_change(self):
|
||||||
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes")
|
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted=1)
|
||||||
total_reqd_qty = sum([d.get("required_qty") for d in po.as_dict().get("supplied_items")])
|
total_reqd_qty = sum([d.get("required_qty") for d in po.as_dict().get("supplied_items")])
|
||||||
|
|
||||||
trans_item = json.dumps(
|
trans_item = json.dumps(
|
||||||
@ -573,7 +573,7 @@ class TestPurchaseOrder(FrappeTestCase):
|
|||||||
automatically_fetch_payment_terms(enable=0)
|
automatically_fetch_payment_terms(enable=0)
|
||||||
|
|
||||||
def test_subcontracting(self):
|
def test_subcontracting(self):
|
||||||
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes")
|
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted=1)
|
||||||
self.assertEqual(len(po.get("supplied_items")), 2)
|
self.assertEqual(len(po.get("supplied_items")), 2)
|
||||||
|
|
||||||
def test_warehouse_company_validation(self):
|
def test_warehouse_company_validation(self):
|
||||||
@ -617,7 +617,7 @@ class TestPurchaseOrder(FrappeTestCase):
|
|||||||
"doctype": "Purchase Order",
|
"doctype": "Purchase Order",
|
||||||
"company": "_Test Company",
|
"company": "_Test Company",
|
||||||
"supplier": "_Test Supplier",
|
"supplier": "_Test Supplier",
|
||||||
"is_subcontracted": "No",
|
"is_subcontracted": 0,
|
||||||
"schedule_date": add_days(nowdate(), 1),
|
"schedule_date": add_days(nowdate(), 1),
|
||||||
"currency": frappe.get_cached_value("Company", "_Test Company", "default_currency"),
|
"currency": frappe.get_cached_value("Company", "_Test Company", "default_currency"),
|
||||||
"conversion_factor": 1,
|
"conversion_factor": 1,
|
||||||
@ -764,7 +764,7 @@ class TestPurchaseOrder(FrappeTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Submit PO
|
# Submit PO
|
||||||
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes")
|
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted=1)
|
||||||
|
|
||||||
bin2 = frappe.db.get_value(
|
bin2 = frappe.db.get_value(
|
||||||
"Bin",
|
"Bin",
|
||||||
@ -919,7 +919,7 @@ class TestPurchaseOrder(FrappeTestCase):
|
|||||||
po = create_purchase_order(
|
po = create_purchase_order(
|
||||||
item_code=item_code,
|
item_code=item_code,
|
||||||
qty=1,
|
qty=1,
|
||||||
is_subcontracted="Yes",
|
is_subcontracted=1,
|
||||||
supplier_warehouse="_Test Warehouse 1 - _TC",
|
supplier_warehouse="_Test Warehouse 1 - _TC",
|
||||||
include_exploded_items=1,
|
include_exploded_items=1,
|
||||||
)
|
)
|
||||||
@ -936,7 +936,7 @@ class TestPurchaseOrder(FrappeTestCase):
|
|||||||
po1 = create_purchase_order(
|
po1 = create_purchase_order(
|
||||||
item_code=item_code,
|
item_code=item_code,
|
||||||
qty=1,
|
qty=1,
|
||||||
is_subcontracted="Yes",
|
is_subcontracted=1,
|
||||||
supplier_warehouse="_Test Warehouse 1 - _TC",
|
supplier_warehouse="_Test Warehouse 1 - _TC",
|
||||||
include_exploded_items=0,
|
include_exploded_items=0,
|
||||||
)
|
)
|
||||||
@ -957,7 +957,7 @@ class TestPurchaseOrder(FrappeTestCase):
|
|||||||
po = create_purchase_order(
|
po = create_purchase_order(
|
||||||
item_code=item_code,
|
item_code=item_code,
|
||||||
qty=order_qty,
|
qty=order_qty,
|
||||||
is_subcontracted="Yes",
|
is_subcontracted=1,
|
||||||
supplier_warehouse="_Test Warehouse 1 - _TC",
|
supplier_warehouse="_Test Warehouse 1 - _TC",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1050,7 +1050,7 @@ class TestPurchaseOrder(FrappeTestCase):
|
|||||||
po = create_purchase_order(
|
po = create_purchase_order(
|
||||||
item_code=item_code,
|
item_code=item_code,
|
||||||
qty=order_qty,
|
qty=order_qty,
|
||||||
is_subcontracted="Yes",
|
is_subcontracted=1,
|
||||||
supplier_warehouse="_Test Warehouse 1 - _TC",
|
supplier_warehouse="_Test Warehouse 1 - _TC",
|
||||||
do_not_save=True,
|
do_not_save=True,
|
||||||
)
|
)
|
||||||
@ -1283,7 +1283,7 @@ def create_purchase_order(**args):
|
|||||||
po.schedule_date = add_days(nowdate(), 1)
|
po.schedule_date = add_days(nowdate(), 1)
|
||||||
po.company = args.company or "_Test Company"
|
po.company = args.company or "_Test Company"
|
||||||
po.supplier = args.supplier or "_Test Supplier"
|
po.supplier = args.supplier or "_Test Supplier"
|
||||||
po.is_subcontracted = args.is_subcontracted or "No"
|
po.is_subcontracted = args.is_subcontracted or 0
|
||||||
po.currency = args.currency or frappe.get_cached_value("Company", po.company, "default_currency")
|
po.currency = args.currency or frappe.get_cached_value("Company", po.company, "default_currency")
|
||||||
po.conversion_factor = args.conversion_factor or 1
|
po.conversion_factor = args.conversion_factor or 1
|
||||||
po.supplier_warehouse = args.supplier_warehouse or None
|
po.supplier_warehouse = args.supplier_warehouse or None
|
||||||
@ -1309,7 +1309,7 @@ def create_purchase_order(**args):
|
|||||||
if not args.do_not_save:
|
if not args.do_not_save:
|
||||||
po.insert()
|
po.insert()
|
||||||
if not args.do_not_submit:
|
if not args.do_not_submit:
|
||||||
if po.is_subcontracted == "Yes":
|
if po.is_subcontracted:
|
||||||
supp_items = po.get("supplied_items")
|
supp_items = po.get("supplied_items")
|
||||||
for d in supp_items:
|
for d in supp_items:
|
||||||
if not d.reserve_warehouse:
|
if not d.reserve_warehouse:
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
"doctype": "Purchase Order",
|
"doctype": "Purchase Order",
|
||||||
"base_grand_total": 5000.0,
|
"base_grand_total": 5000.0,
|
||||||
"grand_total": 5000.0,
|
"grand_total": 5000.0,
|
||||||
"is_subcontracted": "Yes",
|
"is_subcontracted": 1,
|
||||||
"naming_series": "_T-Purchase Order-",
|
"naming_series": "_T-Purchase Order-",
|
||||||
"base_net_total": 5000.0,
|
"base_net_total": 5000.0,
|
||||||
"items": [
|
"items": [
|
||||||
@ -42,7 +42,7 @@
|
|||||||
"doctype": "Purchase Order",
|
"doctype": "Purchase Order",
|
||||||
"base_grand_total": 5000.0,
|
"base_grand_total": 5000.0,
|
||||||
"grand_total": 5000.0,
|
"grand_total": 5000.0,
|
||||||
"is_subcontracted": "No",
|
"is_subcontracted": 0,
|
||||||
"naming_series": "_T-Purchase Order-",
|
"naming_series": "_T-Purchase Order-",
|
||||||
"base_net_total": 5000.0,
|
"base_net_total": 5000.0,
|
||||||
"items": [
|
"items": [
|
||||||
|
@ -572,7 +572,7 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:parent.is_subcontracted == 'Yes'",
|
"depends_on": "eval:parent.is_subcontracted",
|
||||||
"fieldname": "bom",
|
"fieldname": "bom",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "BOM",
|
"label": "BOM",
|
||||||
@ -581,7 +581,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"depends_on": "eval:parent.is_subcontracted == 'Yes'",
|
"depends_on": "eval:parent.is_subcontracted",
|
||||||
"fieldname": "include_exploded_items",
|
"fieldname": "include_exploded_items",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Include Exploded Items",
|
"label": "Include Exploded Items",
|
||||||
|
@ -773,11 +773,10 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "No",
|
"default": "0",
|
||||||
"fieldname": "is_subcontracted",
|
"fieldname": "is_subcontracted",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Check",
|
||||||
"label": "Is Subcontracted",
|
"label": "Is Subcontracted",
|
||||||
"options": "\nYes\nNo",
|
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"doctype": "Supplier Quotation",
|
"doctype": "Supplier Quotation",
|
||||||
"base_grand_total": 5000.0,
|
"base_grand_total": 5000.0,
|
||||||
"grand_total": 5000.0,
|
"grand_total": 5000.0,
|
||||||
"is_subcontracted": "No",
|
"is_subcontracted": 0,
|
||||||
"naming_series": "_T-Supplier Quotation-",
|
"naming_series": "_T-Supplier Quotation-",
|
||||||
"base_net_total": 5000.0,
|
"base_net_total": 5000.0,
|
||||||
"items": [
|
"items": [
|
||||||
|
@ -213,7 +213,8 @@ def make_all_scorecards(docname):
|
|||||||
end_date = get_scorecard_date(sc.period, start_date)
|
end_date = get_scorecard_date(sc.period, start_date)
|
||||||
if scp_count > 0:
|
if scp_count > 0:
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
_("Created {0} scorecards for {1} between: ").format(scp_count, sc.supplier)
|
_("Created {0} scorecards for {1} between:").format(scp_count, sc.supplier)
|
||||||
|
+ " "
|
||||||
+ str(first_start_date)
|
+ str(first_start_date)
|
||||||
+ " - "
|
+ " - "
|
||||||
+ str(last_end_date)
|
+ str(last_end_date)
|
||||||
|
@ -80,6 +80,6 @@ def _get_variables(criteria):
|
|||||||
)[0]
|
)[0]
|
||||||
my_variables.append(var)
|
my_variables.append(var)
|
||||||
except Exception:
|
except Exception:
|
||||||
frappe.throw(_("Unable to find variable: ") + str(match.group(1)), InvalidFormulaVariable)
|
frappe.throw(_("Unable to find variable:") + " " + str(match.group(1)), InvalidFormulaVariable)
|
||||||
|
|
||||||
return my_variables
|
return my_variables
|
||||||
|
@ -48,7 +48,7 @@ def get_chart_data(data, conditions, filters):
|
|||||||
"data": {
|
"data": {
|
||||||
"labels": labels,
|
"labels": labels,
|
||||||
"datasets": [
|
"datasets": [
|
||||||
{"name": _("{0}").format(filters.get("period")) + _(" Purchase Value"), "values": datapoints}
|
{"name": _(filters.get("period")) + " " + _("Purchase Value"), "values": datapoints}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"type": "line",
|
"type": "line",
|
||||||
|
@ -35,7 +35,7 @@ frappe.query_reports["Subcontract Order Summary"] = {
|
|||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
docstatus: 1,
|
docstatus: 1,
|
||||||
is_subcontracted: 'Yes',
|
is_subcontracted: 1,
|
||||||
company: frappe.query_report.get_filter_value('company')
|
company: frappe.query_report.get_filter_value('company')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ def get_subcontracted_orders(report_filters):
|
|||||||
def get_filters(report_filters):
|
def get_filters(report_filters):
|
||||||
filters = [
|
filters = [
|
||||||
["Purchase Order", "docstatus", "=", 1],
|
["Purchase Order", "docstatus", "=", 1],
|
||||||
["Purchase Order", "is_subcontracted", "=", "Yes"],
|
["Purchase Order", "is_subcontracted", "=", 1],
|
||||||
[
|
[
|
||||||
"Purchase Order",
|
"Purchase Order",
|
||||||
"transaction_date",
|
"transaction_date",
|
||||||
|
@ -78,7 +78,7 @@ def get_data(data, filters):
|
|||||||
|
|
||||||
def get_po(filters):
|
def get_po(filters):
|
||||||
record_filters = [
|
record_filters = [
|
||||||
["is_subcontracted", "=", "Yes"],
|
["is_subcontracted", "=", 1],
|
||||||
["supplier", "=", filters.supplier],
|
["supplier", "=", filters.supplier],
|
||||||
["transaction_date", "<=", filters.to_date],
|
["transaction_date", "<=", filters.to_date],
|
||||||
["transaction_date", ">=", filters.from_date],
|
["transaction_date", ">=", filters.from_date],
|
||||||
|
@ -17,7 +17,7 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
|||||||
|
|
||||||
class TestSubcontractedItemToBeReceived(FrappeTestCase):
|
class TestSubcontractedItemToBeReceived(FrappeTestCase):
|
||||||
def test_pending_and_received_qty(self):
|
def test_pending_and_received_qty(self):
|
||||||
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes")
|
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted=1)
|
||||||
transfer_param = []
|
transfer_param = []
|
||||||
make_stock_entry(
|
make_stock_entry(
|
||||||
item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100
|
item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100
|
||||||
|
@ -72,7 +72,7 @@ def get_po_items_to_supply(filters):
|
|||||||
],
|
],
|
||||||
filters=[
|
filters=[
|
||||||
["Purchase Order", "per_received", "<", "100"],
|
["Purchase Order", "per_received", "<", "100"],
|
||||||
["Purchase Order", "is_subcontracted", "=", "Yes"],
|
["Purchase Order", "is_subcontracted", "=", 1],
|
||||||
["Purchase Order", "supplier", "=", filters.supplier],
|
["Purchase Order", "supplier", "=", filters.supplier],
|
||||||
["Purchase Order", "transaction_date", "<=", filters.to_date],
|
["Purchase Order", "transaction_date", "<=", filters.to_date],
|
||||||
["Purchase Order", "transaction_date", ">=", filters.from_date],
|
["Purchase Order", "transaction_date", ">=", filters.from_date],
|
||||||
|
@ -19,7 +19,7 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
|||||||
class TestSubcontractedItemToBeTransferred(FrappeTestCase):
|
class TestSubcontractedItemToBeTransferred(FrappeTestCase):
|
||||||
def test_pending_and_transferred_qty(self):
|
def test_pending_and_transferred_qty(self):
|
||||||
po = create_purchase_order(
|
po = create_purchase_order(
|
||||||
item_code="_Test FG Item", is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC"
|
item_code="_Test FG Item", is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Material Receipt of RMs
|
# Material Receipt of RMs
|
||||||
|
@ -2586,7 +2586,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
|||||||
parent.update_ordered_qty()
|
parent.update_ordered_qty()
|
||||||
parent.update_ordered_and_reserved_qty()
|
parent.update_ordered_and_reserved_qty()
|
||||||
parent.update_receiving_percentage()
|
parent.update_receiving_percentage()
|
||||||
if parent.is_subcontracted == "Yes":
|
if parent.is_subcontracted:
|
||||||
parent.update_reserved_qty_for_subcontract()
|
parent.update_reserved_qty_for_subcontract()
|
||||||
parent.create_raw_materials_supplied("supplied_items")
|
parent.create_raw_materials_supplied("supplied_items")
|
||||||
parent.save()
|
parent.save()
|
||||||
|
@ -167,7 +167,7 @@ class BuyingController(StockController, Subcontracting):
|
|||||||
_("Row #{0}: Accepted Warehouse and Supplier Warehouse cannot be same").format(item.idx)
|
_("Row #{0}: Accepted Warehouse and Supplier Warehouse cannot be same").format(item.idx)
|
||||||
)
|
)
|
||||||
|
|
||||||
if item.get("from_warehouse") and self.get("is_subcontracted") == "Yes":
|
if item.get("from_warehouse") and self.get("is_subcontracted"):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"Row #{0}: Cannot select Supplier Warehouse while suppling raw materials to subcontractor"
|
"Row #{0}: Cannot select Supplier Warehouse while suppling raw materials to subcontractor"
|
||||||
@ -339,10 +339,7 @@ class BuyingController(StockController, Subcontracting):
|
|||||||
return supplied_items_cost
|
return supplied_items_cost
|
||||||
|
|
||||||
def validate_for_subcontracting(self):
|
def validate_for_subcontracting(self):
|
||||||
if not self.is_subcontracted and self.sub_contracted_items:
|
if self.is_subcontracted:
|
||||||
frappe.throw(_("Please enter 'Is Subcontracted' as Yes or No"))
|
|
||||||
|
|
||||||
if self.is_subcontracted == "Yes":
|
|
||||||
if self.doctype in ["Purchase Receipt", "Purchase Invoice"] and not self.supplier_warehouse:
|
if self.doctype in ["Purchase Receipt", "Purchase Invoice"] and not self.supplier_warehouse:
|
||||||
frappe.throw(_("Supplier Warehouse mandatory for sub-contracted {0}").format(self.doctype))
|
frappe.throw(_("Supplier Warehouse mandatory for sub-contracted {0}").format(self.doctype))
|
||||||
|
|
||||||
@ -363,14 +360,14 @@ class BuyingController(StockController, Subcontracting):
|
|||||||
item.bom = None
|
item.bom = None
|
||||||
|
|
||||||
def create_raw_materials_supplied(self, raw_material_table):
|
def create_raw_materials_supplied(self, raw_material_table):
|
||||||
if self.is_subcontracted == "Yes":
|
if self.is_subcontracted:
|
||||||
self.set_materials_for_subcontracted_items(raw_material_table)
|
self.set_materials_for_subcontracted_items(raw_material_table)
|
||||||
|
|
||||||
elif self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
|
elif self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
item.rm_supp_cost = 0.0
|
item.rm_supp_cost = 0.0
|
||||||
|
|
||||||
if self.is_subcontracted == "No" and self.get("supplied_items"):
|
if not self.is_subcontracted and self.get("supplied_items"):
|
||||||
self.set("supplied_items", [])
|
self.set("supplied_items", [])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -466,7 +463,10 @@ class BuyingController(StockController, Subcontracting):
|
|||||||
stock_items = self.get_stock_items()
|
stock_items = self.get_stock_items()
|
||||||
|
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
if d.item_code in stock_items and d.warehouse:
|
if d.item_code not in stock_items:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if d.warehouse:
|
||||||
pr_qty = flt(d.qty) * flt(d.conversion_factor)
|
pr_qty = flt(d.qty) * flt(d.conversion_factor)
|
||||||
|
|
||||||
if pr_qty:
|
if pr_qty:
|
||||||
@ -491,6 +491,7 @@ class BuyingController(StockController, Subcontracting):
|
|||||||
sle = self.get_sl_entries(
|
sle = self.get_sl_entries(
|
||||||
d, {"actual_qty": flt(pr_qty), "serial_no": cstr(d.serial_no).strip()}
|
d, {"actual_qty": flt(pr_qty), "serial_no": cstr(d.serial_no).strip()}
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.is_return:
|
if self.is_return:
|
||||||
outgoing_rate = get_rate_for_return(
|
outgoing_rate = get_rate_for_return(
|
||||||
self.doctype, self.name, d.item_code, self.return_against, item_row=d
|
self.doctype, self.name, d.item_code, self.return_against, item_row=d
|
||||||
@ -520,18 +521,18 @@ class BuyingController(StockController, Subcontracting):
|
|||||||
|
|
||||||
sl_entries.append(from_warehouse_sle)
|
sl_entries.append(from_warehouse_sle)
|
||||||
|
|
||||||
if flt(d.rejected_qty) != 0:
|
if flt(d.rejected_qty) != 0:
|
||||||
sl_entries.append(
|
sl_entries.append(
|
||||||
self.get_sl_entries(
|
self.get_sl_entries(
|
||||||
d,
|
d,
|
||||||
{
|
{
|
||||||
"warehouse": d.rejected_warehouse,
|
"warehouse": d.rejected_warehouse,
|
||||||
"actual_qty": flt(d.rejected_qty) * flt(d.conversion_factor),
|
"actual_qty": flt(d.rejected_qty) * flt(d.conversion_factor),
|
||||||
"serial_no": cstr(d.rejected_serial_no).strip(),
|
"serial_no": cstr(d.rejected_serial_no).strip(),
|
||||||
"incoming_rate": 0.0,
|
"incoming_rate": 0.0,
|
||||||
},
|
},
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
self.make_sl_entries_for_supplier_warehouse(sl_entries)
|
self.make_sl_entries_for_supplier_warehouse(sl_entries)
|
||||||
self.make_sl_entries(
|
self.make_sl_entries(
|
||||||
@ -803,7 +804,7 @@ class BuyingController(StockController, Subcontracting):
|
|||||||
if self.doctype == "Material Request":
|
if self.doctype == "Material Request":
|
||||||
return
|
return
|
||||||
|
|
||||||
if hasattr(self, "is_subcontracted") and self.is_subcontracted == "Yes":
|
if hasattr(self, "is_subcontracted") and self.is_subcontracted:
|
||||||
validate_item_type(self, "is_sub_contracted_item", "subcontracted")
|
validate_item_type(self, "is_sub_contracted_item", "subcontracted")
|
||||||
else:
|
else:
|
||||||
validate_item_type(self, "is_purchase_item", "purchase")
|
validate_item_type(self, "is_purchase_item", "purchase")
|
||||||
|
@ -407,7 +407,7 @@ class Subcontracting:
|
|||||||
|
|
||||||
def set_consumed_qty_in_po(self):
|
def set_consumed_qty_in_po(self):
|
||||||
# Update consumed qty back in the purchase order
|
# Update consumed qty back in the purchase order
|
||||||
if self.is_subcontracted != "Yes":
|
if not self.is_subcontracted:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.__get_purchase_orders()
|
self.__get_purchase_orders()
|
||||||
|
@ -37,7 +37,7 @@ frappe.ui.form.on('LinkedIn Settings', {
|
|||||||
let msg,color;
|
let msg,color;
|
||||||
|
|
||||||
if (days>0){
|
if (days>0){
|
||||||
msg = __("Your Session will be expire in ") + days + __(" days.");
|
msg = __("Your Session will be expire in {0} days.", [days]);
|
||||||
color = "green";
|
color = "green";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -86,7 +86,7 @@ frappe.ui.form.on('Social Media Post', {
|
|||||||
frm.trigger('add_post_btn');
|
frm.trigger('add_post_btn');
|
||||||
}
|
}
|
||||||
if (frm.doc.post_status !='Deleted') {
|
if (frm.doc.post_status !='Deleted') {
|
||||||
frm.add_custom_button(('Delete Post'), function() {
|
frm.add_custom_button(__('Delete Post'), function() {
|
||||||
frappe.confirm(__('Are you sure want to delete the Post from Social Media platforms?'),
|
frappe.confirm(__('Are you sure want to delete the Post from Social Media platforms?'),
|
||||||
function() {
|
function() {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
columns = [
|
columns = [
|
||||||
{"fieldname": "creation_date", "label": "Date", "fieldtype": "Date", "width": 300},
|
{"fieldname": "creation_date", "label": _("Date"), "fieldtype": "Date", "width": 300},
|
||||||
{
|
{
|
||||||
"fieldname": "first_response_time",
|
"fieldname": "first_response_time",
|
||||||
"fieldtype": "Duration",
|
"fieldtype": "Duration",
|
||||||
|
@ -47,7 +47,7 @@
|
|||||||
"item_search_settings_section",
|
"item_search_settings_section",
|
||||||
"redisearch_warning",
|
"redisearch_warning",
|
||||||
"search_index_fields",
|
"search_index_fields",
|
||||||
"show_categories_in_search_autocomplete",
|
"is_redisearch_enabled",
|
||||||
"is_redisearch_loaded",
|
"is_redisearch_loaded",
|
||||||
"shop_by_category_section",
|
"shop_by_category_section",
|
||||||
"slideshow",
|
"slideshow",
|
||||||
@ -293,6 +293,7 @@
|
|||||||
"fieldname": "search_index_fields",
|
"fieldname": "search_index_fields",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"label": "Search Index Fields",
|
"label": "Search Index Fields",
|
||||||
|
"mandatory_depends_on": "is_redisearch_enabled",
|
||||||
"read_only_depends_on": "eval:!doc.is_redisearch_loaded"
|
"read_only_depends_on": "eval:!doc.is_redisearch_loaded"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -301,13 +302,6 @@
|
|||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Item Search Settings"
|
"label": "Item Search Settings"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"default": "1",
|
|
||||||
"fieldname": "show_categories_in_search_autocomplete",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Show Categories in Search Autocomplete",
|
|
||||||
"read_only_depends_on": "eval:!doc.is_redisearch_loaded"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "is_redisearch_loaded",
|
"fieldname": "is_redisearch_loaded",
|
||||||
@ -365,12 +359,19 @@
|
|||||||
"fieldname": "show_price_in_quotation",
|
"fieldname": "show_price_in_quotation",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Show Price in Quotation"
|
"label": "Show Price in Quotation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "is_redisearch_enabled",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Enable Redisearch",
|
||||||
|
"read_only_depends_on": "eval:!doc.is_redisearch_loaded"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-09-02 14:02:44.785824",
|
"modified": "2022-04-01 18:35:56.106756",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "E-commerce",
|
"module": "E-commerce",
|
||||||
"name": "E Commerce Settings",
|
"name": "E Commerce Settings",
|
||||||
@ -389,5 +390,6 @@
|
|||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
@ -9,6 +9,7 @@ from frappe.utils import comma_and, flt, unique
|
|||||||
|
|
||||||
from erpnext.e_commerce.redisearch_utils import (
|
from erpnext.e_commerce.redisearch_utils import (
|
||||||
create_website_items_index,
|
create_website_items_index,
|
||||||
|
define_autocomplete_dictionary,
|
||||||
get_indexable_web_fields,
|
get_indexable_web_fields,
|
||||||
is_search_module_loaded,
|
is_search_module_loaded,
|
||||||
)
|
)
|
||||||
@ -21,6 +22,8 @@ class ShoppingCartSetupError(frappe.ValidationError):
|
|||||||
class ECommerceSettings(Document):
|
class ECommerceSettings(Document):
|
||||||
def onload(self):
|
def onload(self):
|
||||||
self.get("__onload").quotation_series = frappe.get_meta("Quotation").get_options("naming_series")
|
self.get("__onload").quotation_series = frappe.get_meta("Quotation").get_options("naming_series")
|
||||||
|
|
||||||
|
# flag >> if redisearch is installed and loaded
|
||||||
self.is_redisearch_loaded = is_search_module_loaded()
|
self.is_redisearch_loaded = is_search_module_loaded()
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
@ -34,6 +37,20 @@ class ECommerceSettings(Document):
|
|||||||
|
|
||||||
frappe.clear_document_cache("E Commerce Settings", "E Commerce Settings")
|
frappe.clear_document_cache("E Commerce Settings", "E Commerce Settings")
|
||||||
|
|
||||||
|
self.is_redisearch_enabled_pre_save = frappe.db.get_single_value(
|
||||||
|
"E Commerce Settings", "is_redisearch_enabled"
|
||||||
|
)
|
||||||
|
|
||||||
|
def after_save(self):
|
||||||
|
self.create_redisearch_indexes()
|
||||||
|
|
||||||
|
def create_redisearch_indexes(self):
|
||||||
|
# if redisearch is enabled (value changed) create indexes and dictionary
|
||||||
|
value_changed = self.is_redisearch_enabled != self.is_redisearch_enabled_pre_save
|
||||||
|
if self.is_redisearch_loaded and self.is_redisearch_enabled and value_changed:
|
||||||
|
define_autocomplete_dictionary()
|
||||||
|
create_website_items_index()
|
||||||
|
|
||||||
def validate_field_filters(self):
|
def validate_field_filters(self):
|
||||||
if not (self.enable_field_filters and self.filter_fields):
|
if not (self.enable_field_filters and self.filter_fields):
|
||||||
return
|
return
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on('Website Item', {
|
frappe.ui.form.on('Website Item', {
|
||||||
onload: function(frm) {
|
onload: (frm) => {
|
||||||
// should never check Private
|
// should never check Private
|
||||||
frm.fields_dict["website_image"].df.is_private = 0;
|
frm.fields_dict["website_image"].df.is_private = 0;
|
||||||
|
|
||||||
@ -13,18 +13,35 @@ frappe.ui.form.on('Website Item', {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
image: function() {
|
refresh: (frm) => {
|
||||||
|
frm.add_custom_button(__("Prices"), function() {
|
||||||
|
frappe.set_route("List", "Item Price", {"item_code": frm.doc.item_code});
|
||||||
|
}, __("View"));
|
||||||
|
|
||||||
|
frm.add_custom_button(__("Stock"), function() {
|
||||||
|
frappe.route_options = {
|
||||||
|
"item_code": frm.doc.item_code
|
||||||
|
};
|
||||||
|
frappe.set_route("query-report", "Stock Balance");
|
||||||
|
}, __("View"));
|
||||||
|
|
||||||
|
frm.add_custom_button(__("E Commerce Settings"), function() {
|
||||||
|
frappe.set_route("Form", "E Commerce Settings");
|
||||||
|
}, __("View"));
|
||||||
|
},
|
||||||
|
|
||||||
|
image: () => {
|
||||||
refresh_field("image_view");
|
refresh_field("image_view");
|
||||||
},
|
},
|
||||||
|
|
||||||
copy_from_item_group: function(frm) {
|
copy_from_item_group: (frm) => {
|
||||||
return frm.call({
|
return frm.call({
|
||||||
doc: frm.doc,
|
doc: frm.doc,
|
||||||
method: "copy_specification_from_item_group"
|
method: "copy_specification_from_item_group"
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
set_meta_tags(frm) {
|
set_meta_tags: (frm) => {
|
||||||
frappe.utils.set_meta_tag(frm.doc.route);
|
frappe.utils.set_meta_tag(frm.doc.route);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe import _
|
||||||
from frappe.utils.redis_wrapper import RedisWrapper
|
from frappe.utils.redis_wrapper import RedisWrapper
|
||||||
|
from redis import ResponseError
|
||||||
from redisearch import AutoCompleter, Client, IndexDefinition, Suggestion, TagField, TextField
|
from redisearch import AutoCompleter, Client, IndexDefinition, Suggestion, TagField, TextField
|
||||||
|
|
||||||
WEBSITE_ITEM_INDEX = "website_items_index"
|
WEBSITE_ITEM_INDEX = "website_items_index"
|
||||||
@ -22,6 +26,12 @@ def get_indexable_web_fields():
|
|||||||
return [df.fieldname for df in valid_fields]
|
return [df.fieldname for df in valid_fields]
|
||||||
|
|
||||||
|
|
||||||
|
def is_redisearch_enabled():
|
||||||
|
"Return True only if redisearch is loaded and enabled."
|
||||||
|
is_redisearch_enabled = frappe.db.get_single_value("E Commerce Settings", "is_redisearch_enabled")
|
||||||
|
return is_search_module_loaded() and is_redisearch_enabled
|
||||||
|
|
||||||
|
|
||||||
def is_search_module_loaded():
|
def is_search_module_loaded():
|
||||||
try:
|
try:
|
||||||
cache = frappe.cache()
|
cache = frappe.cache()
|
||||||
@ -32,14 +42,14 @@ def is_search_module_loaded():
|
|||||||
)
|
)
|
||||||
return "search" in parsed_output
|
return "search" in parsed_output
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False # handling older redis versions
|
||||||
|
|
||||||
|
|
||||||
def if_redisearch_loaded(function):
|
def if_redisearch_enabled(function):
|
||||||
"Decorator to check if Redisearch is loaded."
|
"Decorator to check if Redisearch is enabled."
|
||||||
|
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
if is_search_module_loaded():
|
if is_redisearch_enabled():
|
||||||
func = function(*args, **kwargs)
|
func = function(*args, **kwargs)
|
||||||
return func
|
return func
|
||||||
return
|
return
|
||||||
@ -51,22 +61,25 @@ def make_key(key):
|
|||||||
return "{0}|{1}".format(frappe.conf.db_name, key).encode("utf-8")
|
return "{0}|{1}".format(frappe.conf.db_name, key).encode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
@if_redisearch_loaded
|
@if_redisearch_enabled
|
||||||
def create_website_items_index():
|
def create_website_items_index():
|
||||||
"Creates Index Definition."
|
"Creates Index Definition."
|
||||||
|
|
||||||
# CREATE index
|
# CREATE index
|
||||||
client = Client(make_key(WEBSITE_ITEM_INDEX), conn=frappe.cache())
|
client = Client(make_key(WEBSITE_ITEM_INDEX), conn=frappe.cache())
|
||||||
|
|
||||||
# DROP if already exists
|
|
||||||
try:
|
try:
|
||||||
client.drop_index()
|
client.drop_index() # drop if already exists
|
||||||
except Exception:
|
except ResponseError:
|
||||||
|
# will most likely raise a ResponseError if index does not exist
|
||||||
|
# ignore and create index
|
||||||
pass
|
pass
|
||||||
|
except Exception:
|
||||||
|
raise_redisearch_error()
|
||||||
|
|
||||||
idx_def = IndexDefinition([make_key(WEBSITE_ITEM_KEY_PREFIX)])
|
idx_def = IndexDefinition([make_key(WEBSITE_ITEM_KEY_PREFIX)])
|
||||||
|
|
||||||
# Based on e-commerce settings
|
# Index fields mentioned in e-commerce settings
|
||||||
idx_fields = frappe.db.get_single_value("E Commerce Settings", "search_index_fields")
|
idx_fields = frappe.db.get_single_value("E Commerce Settings", "search_index_fields")
|
||||||
idx_fields = idx_fields.split(",") if idx_fields else []
|
idx_fields = idx_fields.split(",") if idx_fields else []
|
||||||
|
|
||||||
@ -91,20 +104,20 @@ def to_search_field(field):
|
|||||||
return TextField(field)
|
return TextField(field)
|
||||||
|
|
||||||
|
|
||||||
@if_redisearch_loaded
|
@if_redisearch_enabled
|
||||||
def insert_item_to_index(website_item_doc):
|
def insert_item_to_index(website_item_doc):
|
||||||
# Insert item to index
|
# Insert item to index
|
||||||
key = get_cache_key(website_item_doc.name)
|
key = get_cache_key(website_item_doc.name)
|
||||||
cache = frappe.cache()
|
cache = frappe.cache()
|
||||||
web_item = create_web_item_map(website_item_doc)
|
web_item = create_web_item_map(website_item_doc)
|
||||||
|
|
||||||
for k, v in web_item.items():
|
for field, value in web_item.items():
|
||||||
super(RedisWrapper, cache).hset(make_key(key), k, v)
|
super(RedisWrapper, cache).hset(make_key(key), field, value)
|
||||||
|
|
||||||
insert_to_name_ac(website_item_doc.web_item_name, website_item_doc.name)
|
insert_to_name_ac(website_item_doc.web_item_name, website_item_doc.name)
|
||||||
|
|
||||||
|
|
||||||
@if_redisearch_loaded
|
@if_redisearch_enabled
|
||||||
def insert_to_name_ac(web_name, doc_name):
|
def insert_to_name_ac(web_name, doc_name):
|
||||||
ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=frappe.cache())
|
ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=frappe.cache())
|
||||||
ac.add_suggestions(Suggestion(web_name, payload=doc_name))
|
ac.add_suggestions(Suggestion(web_name, payload=doc_name))
|
||||||
@ -114,20 +127,20 @@ def create_web_item_map(website_item_doc):
|
|||||||
fields_to_index = get_fields_indexed()
|
fields_to_index = get_fields_indexed()
|
||||||
web_item = {}
|
web_item = {}
|
||||||
|
|
||||||
for f in fields_to_index:
|
for field in fields_to_index:
|
||||||
web_item[f] = website_item_doc.get(f) or ""
|
web_item[field] = website_item_doc.get(field) or ""
|
||||||
|
|
||||||
return web_item
|
return web_item
|
||||||
|
|
||||||
|
|
||||||
@if_redisearch_loaded
|
@if_redisearch_enabled
|
||||||
def update_index_for_item(website_item_doc):
|
def update_index_for_item(website_item_doc):
|
||||||
# Reinsert to Cache
|
# Reinsert to Cache
|
||||||
insert_item_to_index(website_item_doc)
|
insert_item_to_index(website_item_doc)
|
||||||
define_autocomplete_dictionary()
|
define_autocomplete_dictionary()
|
||||||
|
|
||||||
|
|
||||||
@if_redisearch_loaded
|
@if_redisearch_enabled
|
||||||
def delete_item_from_index(website_item_doc):
|
def delete_item_from_index(website_item_doc):
|
||||||
cache = frappe.cache()
|
cache = frappe.cache()
|
||||||
key = get_cache_key(website_item_doc.name)
|
key = get_cache_key(website_item_doc.name)
|
||||||
@ -135,13 +148,13 @@ def delete_item_from_index(website_item_doc):
|
|||||||
try:
|
try:
|
||||||
cache.delete(key)
|
cache.delete(key)
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
raise_redisearch_error()
|
||||||
|
|
||||||
delete_from_ac_dict(website_item_doc)
|
delete_from_ac_dict(website_item_doc)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@if_redisearch_loaded
|
@if_redisearch_enabled
|
||||||
def delete_from_ac_dict(website_item_doc):
|
def delete_from_ac_dict(website_item_doc):
|
||||||
"""Removes this items's name from autocomplete dictionary"""
|
"""Removes this items's name from autocomplete dictionary"""
|
||||||
cache = frappe.cache()
|
cache = frappe.cache()
|
||||||
@ -149,40 +162,60 @@ def delete_from_ac_dict(website_item_doc):
|
|||||||
name_ac.delete(website_item_doc.web_item_name)
|
name_ac.delete(website_item_doc.web_item_name)
|
||||||
|
|
||||||
|
|
||||||
@if_redisearch_loaded
|
@if_redisearch_enabled
|
||||||
def define_autocomplete_dictionary():
|
def define_autocomplete_dictionary():
|
||||||
"""Creates an autocomplete search dictionary for `name`.
|
"""
|
||||||
Also creats autocomplete dictionary for `categories` if
|
Defines/Redefines an autocomplete search dictionary for Website Item Name.
|
||||||
checked in E Commerce Settings"""
|
Also creats autocomplete dictionary for Published Item Groups.
|
||||||
|
"""
|
||||||
|
|
||||||
cache = frappe.cache()
|
cache = frappe.cache()
|
||||||
name_ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=cache)
|
item_ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=cache)
|
||||||
cat_ac = AutoCompleter(make_key(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE), conn=cache)
|
item_group_ac = AutoCompleter(make_key(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE), conn=cache)
|
||||||
|
|
||||||
ac_categories = frappe.db.get_single_value(
|
|
||||||
"E Commerce Settings", "show_categories_in_search_autocomplete"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Delete both autocomplete dicts
|
# Delete both autocomplete dicts
|
||||||
try:
|
try:
|
||||||
cache.delete(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE))
|
cache.delete(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE))
|
||||||
cache.delete(make_key(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE))
|
cache.delete(make_key(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE))
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
raise_redisearch_error()
|
||||||
|
|
||||||
|
create_items_autocomplete_dict(autocompleter=item_ac)
|
||||||
|
create_item_groups_autocomplete_dict(autocompleter=item_group_ac)
|
||||||
|
|
||||||
|
|
||||||
|
@if_redisearch_enabled
|
||||||
|
def create_items_autocomplete_dict(autocompleter):
|
||||||
|
"Add items as suggestions in Autocompleter."
|
||||||
items = frappe.get_all(
|
items = frappe.get_all(
|
||||||
"Website Item", fields=["web_item_name", "item_group"], filters={"published": 1}
|
"Website Item", fields=["web_item_name", "item_group"], filters={"published": 1}
|
||||||
)
|
)
|
||||||
|
|
||||||
for item in items:
|
for item in items:
|
||||||
name_ac.add_suggestions(Suggestion(item.web_item_name))
|
autocompleter.add_suggestions(Suggestion(item.web_item_name))
|
||||||
if ac_categories and item.item_group:
|
|
||||||
cat_ac.add_suggestions(Suggestion(item.item_group))
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
@if_redisearch_loaded
|
@if_redisearch_enabled
|
||||||
|
def create_item_groups_autocomplete_dict(autocompleter):
|
||||||
|
"Add item groups with weightage as suggestions in Autocompleter."
|
||||||
|
published_item_groups = frappe.get_all(
|
||||||
|
"Item Group", fields=["name", "route", "weightage"], filters={"show_in_website": 1}
|
||||||
|
)
|
||||||
|
if not published_item_groups:
|
||||||
|
return
|
||||||
|
|
||||||
|
for item_group in published_item_groups:
|
||||||
|
payload = json.dumps({"name": item_group.name, "route": item_group.route})
|
||||||
|
autocompleter.add_suggestions(
|
||||||
|
Suggestion(
|
||||||
|
string=item_group.name,
|
||||||
|
score=frappe.utils.flt(item_group.weightage) or 1.0,
|
||||||
|
payload=payload, # additional info that can be retrieved later
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@if_redisearch_enabled
|
||||||
def reindex_all_web_items():
|
def reindex_all_web_items():
|
||||||
items = frappe.get_all("Website Item", fields=get_fields_indexed(), filters={"published": True})
|
items = frappe.get_all("Website Item", fields=get_fields_indexed(), filters={"published": True})
|
||||||
|
|
||||||
@ -191,8 +224,8 @@ def reindex_all_web_items():
|
|||||||
web_item = create_web_item_map(item)
|
web_item = create_web_item_map(item)
|
||||||
key = make_key(get_cache_key(item.name))
|
key = make_key(get_cache_key(item.name))
|
||||||
|
|
||||||
for k, v in web_item.items():
|
for field, value in web_item.items():
|
||||||
super(RedisWrapper, cache).hset(key, k, v)
|
super(RedisWrapper, cache).hset(key, field, value)
|
||||||
|
|
||||||
|
|
||||||
def get_cache_key(name):
|
def get_cache_key(name):
|
||||||
@ -210,7 +243,12 @@ def get_fields_indexed():
|
|||||||
return fields_to_index
|
return fields_to_index
|
||||||
|
|
||||||
|
|
||||||
# TODO: Remove later
|
def raise_redisearch_error():
|
||||||
# # Figure out a way to run this at startup
|
"Create an Error Log and raise error."
|
||||||
define_autocomplete_dictionary()
|
traceback = frappe.get_traceback()
|
||||||
create_website_items_index()
|
log = frappe.log_error(traceback, frappe._("Redisearch Error"))
|
||||||
|
log_link = frappe.utils.get_link_to_form("Error Log", log.name)
|
||||||
|
|
||||||
|
frappe.throw(
|
||||||
|
msg=_("Something went wrong. Check {0}").format(log_link), title=_("Redisearch Error")
|
||||||
|
)
|
||||||
|
@ -41,10 +41,8 @@ class CourseSchedulingTool(Document):
|
|||||||
if self.day == calendar.day_name[getdate(date).weekday()]:
|
if self.day == calendar.day_name[getdate(date).weekday()]:
|
||||||
course_schedule = self.make_course_schedule(date)
|
course_schedule = self.make_course_schedule(date)
|
||||||
try:
|
try:
|
||||||
print("pass")
|
|
||||||
course_schedule.save()
|
course_schedule.save()
|
||||||
except OverlapError:
|
except OverlapError:
|
||||||
print("fail")
|
|
||||||
course_schedules_errors.append(date)
|
course_schedules_errors.append(date)
|
||||||
else:
|
else:
|
||||||
course_schedules.append(course_schedule)
|
course_schedules.append(course_schedule)
|
||||||
|
@ -69,13 +69,13 @@ class StudentGroupCreationTool(Document):
|
|||||||
l = len(self.courses)
|
l = len(self.courses)
|
||||||
for d in self.courses:
|
for d in self.courses:
|
||||||
if not d.student_group_name:
|
if not d.student_group_name:
|
||||||
frappe.throw(_("""Student Group Name is mandatory in row {0}""".format(d.idx)))
|
frappe.throw(_("Student Group Name is mandatory in row {0}").format(d.idx))
|
||||||
|
|
||||||
if d.group_based_on == "Course" and not d.course:
|
if d.group_based_on == "Course" and not d.course:
|
||||||
frappe.throw(_("""Course is mandatory in row {0}""".format(d.idx)))
|
frappe.throw(_("Course is mandatory in row {0}").format(d.idx))
|
||||||
|
|
||||||
if d.group_based_on == "Batch" and not d.batch:
|
if d.group_based_on == "Batch" and not d.batch:
|
||||||
frappe.throw(_("""Batch is mandatory in row {0}""".format(d.idx)))
|
frappe.throw(_("Batch is mandatory in row {0}").format(d.idx))
|
||||||
|
|
||||||
frappe.publish_realtime(
|
frappe.publish_realtime(
|
||||||
"student_group_creation_progress", {"progress": [d.idx, l]}, user=frappe.session.user
|
"student_group_creation_progress", {"progress": [d.idx, l]}, user=frappe.session.user
|
||||||
|
@ -87,7 +87,7 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
field_name, frappe.bold(employee.employee_name)
|
field_name, frappe.bold(employee.employee_name)
|
||||||
)
|
)
|
||||||
if department_list:
|
if department_list:
|
||||||
error_msg += _(" or for Department: {0}").format(frappe.bold(employee_department))
|
error_msg += " " + _("or for Department: {0}").format(frappe.bold(employee_department))
|
||||||
frappe.throw(error_msg, title=_(field_name + " Missing"))
|
frappe.throw(error_msg, title=_(field_name + " Missing"))
|
||||||
|
|
||||||
return set(tuple(approver) for approver in approvers)
|
return set(tuple(approver) for approver in approvers)
|
||||||
|
@ -39,11 +39,15 @@ class LeaveAllocation(Document):
|
|||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_period()
|
self.validate_period()
|
||||||
self.validate_allocation_overlap()
|
self.validate_allocation_overlap()
|
||||||
self.validate_back_dated_allocation()
|
|
||||||
self.set_total_leaves_allocated()
|
|
||||||
self.validate_total_leaves_allocated()
|
|
||||||
self.validate_lwp()
|
self.validate_lwp()
|
||||||
set_employee_name(self)
|
set_employee_name(self)
|
||||||
|
self.set_total_leaves_allocated()
|
||||||
|
self.validate_leave_days_and_dates()
|
||||||
|
|
||||||
|
def validate_leave_days_and_dates(self):
|
||||||
|
# all validations that should run on save as well as on update after submit
|
||||||
|
self.validate_back_dated_allocation()
|
||||||
|
self.validate_total_leaves_allocated()
|
||||||
self.validate_leave_allocation_days()
|
self.validate_leave_allocation_days()
|
||||||
|
|
||||||
def validate_leave_allocation_days(self):
|
def validate_leave_allocation_days(self):
|
||||||
@ -56,14 +60,19 @@ class LeaveAllocation(Document):
|
|||||||
leave_allocated = 0
|
leave_allocated = 0
|
||||||
if leave_period:
|
if leave_period:
|
||||||
leave_allocated = get_leave_allocation_for_period(
|
leave_allocated = get_leave_allocation_for_period(
|
||||||
self.employee, self.leave_type, leave_period[0].from_date, leave_period[0].to_date
|
self.employee,
|
||||||
|
self.leave_type,
|
||||||
|
leave_period[0].from_date,
|
||||||
|
leave_period[0].to_date,
|
||||||
|
exclude_allocation=self.name,
|
||||||
)
|
)
|
||||||
leave_allocated += flt(self.new_leaves_allocated)
|
leave_allocated += flt(self.new_leaves_allocated)
|
||||||
if leave_allocated > max_leaves_allowed:
|
if leave_allocated > max_leaves_allowed:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"Total allocated leaves are more days than maximum allocation of {0} leave type for employee {1} in the period"
|
"Total allocated leaves are more than maximum allocation allowed for {0} leave type for employee {1} in the period"
|
||||||
).format(self.leave_type, self.employee)
|
).format(self.leave_type, self.employee),
|
||||||
|
OverAllocationError,
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
@ -84,6 +93,12 @@ class LeaveAllocation(Document):
|
|||||||
def on_update_after_submit(self):
|
def on_update_after_submit(self):
|
||||||
if self.has_value_changed("new_leaves_allocated"):
|
if self.has_value_changed("new_leaves_allocated"):
|
||||||
self.validate_against_leave_applications()
|
self.validate_against_leave_applications()
|
||||||
|
|
||||||
|
# recalculate total leaves allocated
|
||||||
|
self.total_leaves_allocated = flt(self.unused_leaves) + flt(self.new_leaves_allocated)
|
||||||
|
# run required validations again since total leaves are being updated
|
||||||
|
self.validate_leave_days_and_dates()
|
||||||
|
|
||||||
leaves_to_be_added = self.new_leaves_allocated - self.get_existing_leave_count()
|
leaves_to_be_added = self.new_leaves_allocated - self.get_existing_leave_count()
|
||||||
args = {
|
args = {
|
||||||
"leaves": leaves_to_be_added,
|
"leaves": leaves_to_be_added,
|
||||||
@ -92,6 +107,7 @@ class LeaveAllocation(Document):
|
|||||||
"is_carry_forward": 0,
|
"is_carry_forward": 0,
|
||||||
}
|
}
|
||||||
create_leave_ledger_entry(self, args, True)
|
create_leave_ledger_entry(self, args, True)
|
||||||
|
self.db_update()
|
||||||
|
|
||||||
def get_existing_leave_count(self):
|
def get_existing_leave_count(self):
|
||||||
ledger_entries = frappe.get_all(
|
ledger_entries = frappe.get_all(
|
||||||
@ -279,27 +295,27 @@ def get_previous_allocation(from_date, leave_type, employee):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_leave_allocation_for_period(employee, leave_type, from_date, to_date):
|
def get_leave_allocation_for_period(
|
||||||
leave_allocated = 0
|
employee, leave_type, from_date, to_date, exclude_allocation=None
|
||||||
leave_allocations = frappe.db.sql(
|
):
|
||||||
"""
|
from frappe.query_builder.functions import Sum
|
||||||
select employee, leave_type, from_date, to_date, total_leaves_allocated
|
|
||||||
from `tabLeave Allocation`
|
|
||||||
where employee=%(employee)s and leave_type=%(leave_type)s
|
|
||||||
and docstatus=1
|
|
||||||
and (from_date between %(from_date)s and %(to_date)s
|
|
||||||
or to_date between %(from_date)s and %(to_date)s
|
|
||||||
or (from_date < %(from_date)s and to_date > %(to_date)s))
|
|
||||||
""",
|
|
||||||
{"from_date": from_date, "to_date": to_date, "employee": employee, "leave_type": leave_type},
|
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
if leave_allocations:
|
Allocation = frappe.qb.DocType("Leave Allocation")
|
||||||
for leave_alloc in leave_allocations:
|
return (
|
||||||
leave_allocated += leave_alloc.total_leaves_allocated
|
frappe.qb.from_(Allocation)
|
||||||
|
.select(Sum(Allocation.total_leaves_allocated).as_("total_allocated_leaves"))
|
||||||
return leave_allocated
|
.where(
|
||||||
|
(Allocation.employee == employee)
|
||||||
|
& (Allocation.leave_type == leave_type)
|
||||||
|
& (Allocation.docstatus == 1)
|
||||||
|
& (Allocation.name != exclude_allocation)
|
||||||
|
& (
|
||||||
|
(Allocation.from_date.between(from_date, to_date))
|
||||||
|
| (Allocation.to_date.between(from_date, to_date))
|
||||||
|
| ((Allocation.from_date < from_date) & (Allocation.to_date > to_date))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).run()[0][0] or 0.0
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
@ -1,24 +1,26 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
from frappe.utils import add_days, add_months, getdate, nowdate
|
from frappe.utils import add_days, add_months, getdate, nowdate
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.hr.doctype.employee.test_employee import make_employee
|
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||||
|
from erpnext.hr.doctype.leave_allocation.leave_allocation import (
|
||||||
|
BackDatedAllocationError,
|
||||||
|
OverAllocationError,
|
||||||
|
)
|
||||||
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import process_expired_allocation
|
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import process_expired_allocation
|
||||||
from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
|
from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
|
||||||
|
|
||||||
|
|
||||||
class TestLeaveAllocation(unittest.TestCase):
|
class TestLeaveAllocation(FrappeTestCase):
|
||||||
@classmethod
|
def setUp(self):
|
||||||
def setUpClass(cls):
|
frappe.db.delete("Leave Period")
|
||||||
frappe.db.sql("delete from `tabLeave Period`")
|
frappe.db.delete("Leave Allocation")
|
||||||
|
|
||||||
emp_id = make_employee("test_emp_leave_allocation@salary.com")
|
emp_id = make_employee("test_emp_leave_allocation@salary.com", company="_Test Company")
|
||||||
cls.employee = frappe.get_doc("Employee", emp_id)
|
self.employee = frappe.get_doc("Employee", emp_id)
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
frappe.db.rollback()
|
|
||||||
|
|
||||||
def test_overlapping_allocation(self):
|
def test_overlapping_allocation(self):
|
||||||
leaves = [
|
leaves = [
|
||||||
@ -65,7 +67,7 @@ class TestLeaveAllocation(unittest.TestCase):
|
|||||||
# invalid period
|
# invalid period
|
||||||
self.assertRaises(frappe.ValidationError, doc.save)
|
self.assertRaises(frappe.ValidationError, doc.save)
|
||||||
|
|
||||||
def test_allocated_leave_days_over_period(self):
|
def test_validation_for_over_allocation(self):
|
||||||
doc = frappe.get_doc(
|
doc = frappe.get_doc(
|
||||||
{
|
{
|
||||||
"doctype": "Leave Allocation",
|
"doctype": "Leave Allocation",
|
||||||
@ -80,7 +82,135 @@ class TestLeaveAllocation(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# allocated leave more than period
|
# allocated leave more than period
|
||||||
self.assertRaises(frappe.ValidationError, doc.save)
|
self.assertRaises(OverAllocationError, doc.save)
|
||||||
|
|
||||||
|
def test_validation_for_over_allocation_post_submission(self):
|
||||||
|
allocation = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Leave Allocation",
|
||||||
|
"__islocal": 1,
|
||||||
|
"employee": self.employee.name,
|
||||||
|
"employee_name": self.employee.employee_name,
|
||||||
|
"leave_type": "_Test Leave Type",
|
||||||
|
"from_date": getdate("2015-09-1"),
|
||||||
|
"to_date": getdate("2015-09-30"),
|
||||||
|
"new_leaves_allocated": 15,
|
||||||
|
}
|
||||||
|
).submit()
|
||||||
|
allocation.reload()
|
||||||
|
# allocated leaves more than period after submission
|
||||||
|
allocation.new_leaves_allocated = 35
|
||||||
|
self.assertRaises(OverAllocationError, allocation.save)
|
||||||
|
|
||||||
|
def test_validation_for_over_allocation_based_on_leave_setup(self):
|
||||||
|
frappe.delete_doc_if_exists("Leave Period", "Test Allocation Period")
|
||||||
|
leave_period = frappe.get_doc(
|
||||||
|
dict(
|
||||||
|
name="Test Allocation Period",
|
||||||
|
doctype="Leave Period",
|
||||||
|
from_date=add_months(nowdate(), -6),
|
||||||
|
to_date=add_months(nowdate(), 6),
|
||||||
|
company="_Test Company",
|
||||||
|
is_active=1,
|
||||||
|
)
|
||||||
|
).insert()
|
||||||
|
|
||||||
|
leave_type = create_leave_type(leave_type_name="_Test Allocation Validation", is_carry_forward=1)
|
||||||
|
leave_type.max_leaves_allowed = 25
|
||||||
|
leave_type.save()
|
||||||
|
|
||||||
|
# 15 leaves allocated in this period
|
||||||
|
allocation = create_leave_allocation(
|
||||||
|
leave_type=leave_type.name,
|
||||||
|
employee=self.employee.name,
|
||||||
|
employee_name=self.employee.employee_name,
|
||||||
|
from_date=leave_period.from_date,
|
||||||
|
to_date=nowdate(),
|
||||||
|
)
|
||||||
|
allocation.submit()
|
||||||
|
|
||||||
|
# trying to allocate additional 15 leaves
|
||||||
|
allocation = create_leave_allocation(
|
||||||
|
leave_type=leave_type.name,
|
||||||
|
employee=self.employee.name,
|
||||||
|
employee_name=self.employee.employee_name,
|
||||||
|
from_date=add_days(nowdate(), 1),
|
||||||
|
to_date=leave_period.to_date,
|
||||||
|
)
|
||||||
|
self.assertRaises(OverAllocationError, allocation.save)
|
||||||
|
|
||||||
|
def test_validation_for_over_allocation_based_on_leave_setup_post_submission(self):
|
||||||
|
frappe.delete_doc_if_exists("Leave Period", "Test Allocation Period")
|
||||||
|
leave_period = frappe.get_doc(
|
||||||
|
dict(
|
||||||
|
name="Test Allocation Period",
|
||||||
|
doctype="Leave Period",
|
||||||
|
from_date=add_months(nowdate(), -6),
|
||||||
|
to_date=add_months(nowdate(), 6),
|
||||||
|
company="_Test Company",
|
||||||
|
is_active=1,
|
||||||
|
)
|
||||||
|
).insert()
|
||||||
|
|
||||||
|
leave_type = create_leave_type(leave_type_name="_Test Allocation Validation", is_carry_forward=1)
|
||||||
|
leave_type.max_leaves_allowed = 30
|
||||||
|
leave_type.save()
|
||||||
|
|
||||||
|
# 15 leaves allocated
|
||||||
|
allocation = create_leave_allocation(
|
||||||
|
leave_type=leave_type.name,
|
||||||
|
employee=self.employee.name,
|
||||||
|
employee_name=self.employee.employee_name,
|
||||||
|
from_date=leave_period.from_date,
|
||||||
|
to_date=nowdate(),
|
||||||
|
)
|
||||||
|
allocation.submit()
|
||||||
|
allocation.reload()
|
||||||
|
|
||||||
|
# allocate additional 15 leaves
|
||||||
|
allocation = create_leave_allocation(
|
||||||
|
leave_type=leave_type.name,
|
||||||
|
employee=self.employee.name,
|
||||||
|
employee_name=self.employee.employee_name,
|
||||||
|
from_date=add_days(nowdate(), 1),
|
||||||
|
to_date=leave_period.to_date,
|
||||||
|
)
|
||||||
|
allocation.submit()
|
||||||
|
allocation.reload()
|
||||||
|
|
||||||
|
# trying to allocate 25 leaves in 2nd alloc within leave period
|
||||||
|
# total leaves = 40 which is more than `max_leaves_allowed` setting i.e. 30
|
||||||
|
allocation.new_leaves_allocated = 25
|
||||||
|
self.assertRaises(OverAllocationError, allocation.save)
|
||||||
|
|
||||||
|
def test_validate_back_dated_allocation_update(self):
|
||||||
|
leave_type = create_leave_type(leave_type_name="_Test_CF_leave", is_carry_forward=1)
|
||||||
|
leave_type.save()
|
||||||
|
|
||||||
|
# initial leave allocation = 15
|
||||||
|
leave_allocation = create_leave_allocation(
|
||||||
|
employee=self.employee.name,
|
||||||
|
employee_name=self.employee.employee_name,
|
||||||
|
leave_type="_Test_CF_leave",
|
||||||
|
from_date=add_months(nowdate(), -12),
|
||||||
|
to_date=add_months(nowdate(), -1),
|
||||||
|
carry_forward=0,
|
||||||
|
)
|
||||||
|
leave_allocation.submit()
|
||||||
|
|
||||||
|
# new_leaves = 15, carry_forwarded = 10
|
||||||
|
leave_allocation_1 = create_leave_allocation(
|
||||||
|
employee=self.employee.name,
|
||||||
|
employee_name=self.employee.employee_name,
|
||||||
|
leave_type="_Test_CF_leave",
|
||||||
|
carry_forward=1,
|
||||||
|
)
|
||||||
|
leave_allocation_1.submit()
|
||||||
|
|
||||||
|
# try updating initial leave allocation
|
||||||
|
leave_allocation.reload()
|
||||||
|
leave_allocation.new_leaves_allocated = 20
|
||||||
|
self.assertRaises(BackDatedAllocationError, leave_allocation.save)
|
||||||
|
|
||||||
def test_carry_forward_calculation(self):
|
def test_carry_forward_calculation(self):
|
||||||
leave_type = create_leave_type(leave_type_name="_Test_CF_leave", is_carry_forward=1)
|
leave_type = create_leave_type(leave_type_name="_Test_CF_leave", is_carry_forward=1)
|
||||||
@ -108,8 +238,10 @@ class TestLeaveAllocation(unittest.TestCase):
|
|||||||
carry_forward=1,
|
carry_forward=1,
|
||||||
)
|
)
|
||||||
leave_allocation_1.submit()
|
leave_allocation_1.submit()
|
||||||
|
leave_allocation_1.reload()
|
||||||
|
|
||||||
self.assertEqual(leave_allocation_1.unused_leaves, 10)
|
self.assertEqual(leave_allocation_1.unused_leaves, 10)
|
||||||
|
self.assertEqual(leave_allocation_1.total_leaves_allocated, 25)
|
||||||
|
|
||||||
leave_allocation_1.cancel()
|
leave_allocation_1.cancel()
|
||||||
|
|
||||||
@ -197,9 +329,12 @@ class TestLeaveAllocation(unittest.TestCase):
|
|||||||
employee=self.employee.name, employee_name=self.employee.employee_name
|
employee=self.employee.name, employee_name=self.employee.employee_name
|
||||||
)
|
)
|
||||||
leave_allocation.submit()
|
leave_allocation.submit()
|
||||||
|
leave_allocation.reload()
|
||||||
self.assertTrue(leave_allocation.total_leaves_allocated, 15)
|
self.assertTrue(leave_allocation.total_leaves_allocated, 15)
|
||||||
|
|
||||||
leave_allocation.new_leaves_allocated = 40
|
leave_allocation.new_leaves_allocated = 40
|
||||||
leave_allocation.submit()
|
leave_allocation.submit()
|
||||||
|
leave_allocation.reload()
|
||||||
self.assertTrue(leave_allocation.total_leaves_allocated, 40)
|
self.assertTrue(leave_allocation.total_leaves_allocated, 40)
|
||||||
|
|
||||||
def test_leave_subtraction_after_submit(self):
|
def test_leave_subtraction_after_submit(self):
|
||||||
@ -207,9 +342,12 @@ class TestLeaveAllocation(unittest.TestCase):
|
|||||||
employee=self.employee.name, employee_name=self.employee.employee_name
|
employee=self.employee.name, employee_name=self.employee.employee_name
|
||||||
)
|
)
|
||||||
leave_allocation.submit()
|
leave_allocation.submit()
|
||||||
|
leave_allocation.reload()
|
||||||
self.assertTrue(leave_allocation.total_leaves_allocated, 15)
|
self.assertTrue(leave_allocation.total_leaves_allocated, 15)
|
||||||
|
|
||||||
leave_allocation.new_leaves_allocated = 10
|
leave_allocation.new_leaves_allocated = 10
|
||||||
leave_allocation.submit()
|
leave_allocation.submit()
|
||||||
|
leave_allocation.reload()
|
||||||
self.assertTrue(leave_allocation.total_leaves_allocated, 10)
|
self.assertTrue(leave_allocation.total_leaves_allocated, 10)
|
||||||
|
|
||||||
def test_validation_against_leave_application_after_submit(self):
|
def test_validation_against_leave_application_after_submit(self):
|
||||||
|
@ -73,10 +73,10 @@ class ShiftAssignment(Document):
|
|||||||
frappe.bold(self.employee), frappe.bold(self.shift_type), frappe.bold(shift_details.name)
|
frappe.bold(self.employee), frappe.bold(self.shift_type), frappe.bold(shift_details.name)
|
||||||
)
|
)
|
||||||
if shift_details.start_date:
|
if shift_details.start_date:
|
||||||
msg += _(" from {0}").format(getdate(self.start_date).strftime("%d-%m-%Y"))
|
msg += " " + _("from {0}").format(getdate(self.start_date).strftime("%d-%m-%Y"))
|
||||||
title = "Ongoing Shift"
|
title = "Ongoing Shift"
|
||||||
if shift_details.end_date:
|
if shift_details.end_date:
|
||||||
msg += _(" to {0}").format(getdate(self.end_date).strftime("%d-%m-%Y"))
|
msg += " " + _("to {0}").format(getdate(self.end_date).strftime("%d-%m-%Y"))
|
||||||
title = "Active Shift"
|
title = "Active Shift"
|
||||||
if msg:
|
if msg:
|
||||||
frappe.throw(msg, title=title)
|
frappe.throw(msg, title=title)
|
||||||
|
@ -109,7 +109,7 @@ class ShiftRequest(Document):
|
|||||||
self.throw_overlap_error(date_overlap)
|
self.throw_overlap_error(date_overlap)
|
||||||
|
|
||||||
def throw_overlap_error(self, d):
|
def throw_overlap_error(self, d):
|
||||||
msg = _("Employee {0} has already applied for {1} between {2} and {3} : ").format(
|
msg = _("Employee {0} has already applied for {1} between {2} and {3}").format(
|
||||||
self.employee, d["shift_type"], formatdate(d["from_date"]), formatdate(d["to_date"])
|
self.employee, d["shift_type"], formatdate(d["from_date"]), formatdate(d["to_date"])
|
||||||
) + """ <b><a href="/app/Form/Shift Request/{0}">{0}</a></b>""".format(d["name"])
|
) + """ : <b><a href="/app/Form/Shift Request/{0}">{0}</a></b>""".format(d["name"])
|
||||||
frappe.throw(msg, OverlapError)
|
frappe.throw(msg, OverlapError)
|
||||||
|
@ -91,8 +91,7 @@ class StaffingPlan(Document):
|
|||||||
) > flt(parent_plan_details[0].total_estimated_cost):
|
) > flt(parent_plan_details[0].total_estimated_cost):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"You can only plan for upto {0} vacancies and budget {1} \
|
"You can only plan for upto {0} vacancies and budget {1} for {2} as per staffing plan {3} for parent company {4}."
|
||||||
for {2} as per staffing plan {3} for parent company {4}."
|
|
||||||
).format(
|
).format(
|
||||||
cint(parent_plan_details[0].vacancies),
|
cint(parent_plan_details[0].vacancies),
|
||||||
parent_plan_details[0].total_estimated_cost,
|
parent_plan_details[0].total_estimated_cost,
|
||||||
@ -128,8 +127,7 @@ class StaffingPlan(Document):
|
|||||||
):
|
):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"{0} vacancies and {1} budget for {2} already planned for subsidiary companies of {3}. \
|
"{0} vacancies and {1} budget for {2} already planned for subsidiary companies of {3}. You can only plan for upto {4} vacancies and and budget {5} as per staffing plan {6} for parent company {3}."
|
||||||
You can only plan for upto {4} vacancies and and budget {5} as per staffing plan {6} for parent company {3}."
|
|
||||||
).format(
|
).format(
|
||||||
cint(all_sibling_details.vacancies),
|
cint(all_sibling_details.vacancies),
|
||||||
all_sibling_details.total_estimated_cost,
|
all_sibling_details.total_estimated_cost,
|
||||||
@ -162,8 +160,7 @@ class StaffingPlan(Document):
|
|||||||
):
|
):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"Subsidiary companies have already planned for {1} vacancies at a budget of {2}. \
|
"Subsidiary companies have already planned for {1} vacancies at a budget of {2}. Staffing Plan for {0} should allocate more vacancies and budget for {3} than planned for its subsidiary companies"
|
||||||
Staffing Plan for {0} should allocate more vacancies and budget for {3} than planned for its subsidiary companies"
|
|
||||||
).format(
|
).format(
|
||||||
self.company,
|
self.company,
|
||||||
cint(children_details.vacancies),
|
cint(children_details.vacancies),
|
||||||
|
@ -387,13 +387,13 @@ class LoanRepayment(AccountsController):
|
|||||||
gle_map = []
|
gle_map = []
|
||||||
|
|
||||||
if self.shortfall_amount and self.amount_paid > self.shortfall_amount:
|
if self.shortfall_amount and self.amount_paid > self.shortfall_amount:
|
||||||
remarks = _("Shortfall Repayment of {0}.\nRepayment against Loan: {1}").format(
|
remarks = _("Shortfall Repayment of {0}.<br>Repayment against Loan: {1}").format(
|
||||||
self.shortfall_amount, self.against_loan
|
self.shortfall_amount, self.against_loan
|
||||||
)
|
)
|
||||||
elif self.shortfall_amount:
|
elif self.shortfall_amount:
|
||||||
remarks = _("Shortfall Repayment of {0}").format(self.shortfall_amount)
|
remarks = _("Shortfall Repayment of {0}").format(self.shortfall_amount)
|
||||||
else:
|
else:
|
||||||
remarks = _("Repayment against Loan: ") + self.against_loan
|
remarks = _("Repayment against Loan:") + " " + self.against_loan
|
||||||
|
|
||||||
if self.repay_from_salary:
|
if self.repay_from_salary:
|
||||||
payment_account = self.payroll_payable_account
|
payment_account = self.payroll_payable_account
|
||||||
@ -746,6 +746,8 @@ def calculate_amounts(against_loan, posting_date, payment_type=""):
|
|||||||
if payment_type == "Loan Closure":
|
if payment_type == "Loan Closure":
|
||||||
amounts["payable_principal_amount"] = amounts["pending_principal_amount"]
|
amounts["payable_principal_amount"] = amounts["pending_principal_amount"]
|
||||||
amounts["interest_amount"] += amounts["unaccrued_interest"]
|
amounts["interest_amount"] += amounts["unaccrued_interest"]
|
||||||
amounts["payable_amount"] = amounts["payable_principal_amount"] + amounts["interest_amount"]
|
amounts["payable_amount"] = (
|
||||||
|
amounts["payable_principal_amount"] + amounts["interest_amount"] + amounts["penalty_amount"]
|
||||||
|
)
|
||||||
|
|
||||||
return amounts
|
return amounts
|
||||||
|
@ -250,7 +250,7 @@ class MaintenanceSchedule(TransactionBase):
|
|||||||
_("Serial No {0} does not belong to Item {1}").format(
|
_("Serial No {0} does not belong to Item {1}").format(
|
||||||
frappe.bold(serial_no), frappe.bold(item_code)
|
frappe.bold(serial_no), frappe.bold(item_code)
|
||||||
),
|
),
|
||||||
title="Invalid",
|
title=_("Invalid"),
|
||||||
)
|
)
|
||||||
|
|
||||||
if sr_details.warranty_expiry_date and getdate(sr_details.warranty_expiry_date) >= getdate(
|
if sr_details.warranty_expiry_date and getdate(sr_details.warranty_expiry_date) >= getdate(
|
||||||
|
@ -20,7 +20,7 @@ class MaintenanceVisit(TransactionBase):
|
|||||||
|
|
||||||
def validate_purpose_table(self):
|
def validate_purpose_table(self):
|
||||||
if not self.purposes:
|
if not self.purposes:
|
||||||
frappe.throw(_("Add Items in the Purpose Table"), title="Purposes Required")
|
frappe.throw(_("Add Items in the Purpose Table"), title=_("Purposes Required"))
|
||||||
|
|
||||||
def validate_maintenance_date(self):
|
def validate_maintenance_date(self):
|
||||||
if self.maintenance_type == "Scheduled" and self.maintenance_schedule_detail:
|
if self.maintenance_type == "Scheduled" and self.maintenance_schedule_detail:
|
||||||
|
@ -251,7 +251,7 @@ class TestBOM(FrappeTestCase):
|
|||||||
self.assertEqual(bom.items[2].rate, 0)
|
self.assertEqual(bom.items[2].rate, 0)
|
||||||
# test in Purchase Order sourced_by_supplier is not added to Supplied Item
|
# test in Purchase Order sourced_by_supplier is not added to Supplied Item
|
||||||
po = create_purchase_order(
|
po = create_purchase_order(
|
||||||
item_code=item_code, qty=1, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC"
|
item_code=item_code, qty=1, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
|
||||||
)
|
)
|
||||||
bom_items = sorted([d.item_code for d in bom.items if d.sourced_by_supplier != 1])
|
bom_items = sorted([d.item_code for d in bom.items if d.sourced_by_supplier != 1])
|
||||||
supplied_items = sorted([d.rm_item_code for d in po.supplied_items])
|
supplied_items = sorted([d.rm_item_code for d in po.supplied_items])
|
||||||
|
@ -501,7 +501,7 @@ class ProductionPlan(Document):
|
|||||||
po = frappe.new_doc("Purchase Order")
|
po = frappe.new_doc("Purchase Order")
|
||||||
po.supplier = supplier
|
po.supplier = supplier
|
||||||
po.schedule_date = getdate(po_list[0].schedule_date) if po_list[0].schedule_date else nowdate()
|
po.schedule_date = getdate(po_list[0].schedule_date) if po_list[0].schedule_date else nowdate()
|
||||||
po.is_subcontracted = "Yes"
|
po.is_subcontracted = 1
|
||||||
for row in po_list:
|
for row in po_list:
|
||||||
po_data = {
|
po_data = {
|
||||||
"item_code": row.production_item,
|
"item_code": row.production_item,
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
@ -46,17 +47,22 @@ def get_exploded_items(bom, data, indent=0, qty=1):
|
|||||||
def get_columns():
|
def get_columns():
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"label": "Item Code",
|
"label": _("Item Code"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"fieldname": "item_code",
|
"fieldname": "item_code",
|
||||||
"width": 300,
|
"width": 300,
|
||||||
"options": "Item",
|
"options": "Item",
|
||||||
},
|
},
|
||||||
{"label": "Item Name", "fieldtype": "data", "fieldname": "item_name", "width": 100},
|
{"label": _("Item Name"), "fieldtype": "data", "fieldname": "item_name", "width": 100},
|
||||||
{"label": "BOM", "fieldtype": "Link", "fieldname": "bom", "width": 150, "options": "BOM"},
|
{"label": _("BOM"), "fieldtype": "Link", "fieldname": "bom", "width": 150, "options": "BOM"},
|
||||||
{"label": "Qty", "fieldtype": "data", "fieldname": "qty", "width": 100},
|
{"label": _("Qty"), "fieldtype": "data", "fieldname": "qty", "width": 100},
|
||||||
{"label": "UOM", "fieldtype": "data", "fieldname": "uom", "width": 100},
|
{"label": _("UOM"), "fieldtype": "data", "fieldname": "uom", "width": 100},
|
||||||
{"label": "BOM Level", "fieldtype": "Int", "fieldname": "bom_level", "width": 100},
|
{"label": _("BOM Level"), "fieldtype": "Int", "fieldname": "bom_level", "width": 100},
|
||||||
{"label": "Standard Description", "fieldtype": "data", "fieldname": "description", "width": 150},
|
{
|
||||||
{"label": "Scrap", "fieldtype": "data", "fieldname": "scrap", "width": 100},
|
"label": _("Standard Description"),
|
||||||
|
"fieldtype": "data",
|
||||||
|
"fieldname": "description",
|
||||||
|
"width": 150,
|
||||||
|
},
|
||||||
|
{"label": _("Scrap"), "fieldtype": "data", "fieldname": "scrap", "width": 100},
|
||||||
]
|
]
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe import _
|
||||||
from frappe.utils import flt
|
from frappe.utils import flt
|
||||||
|
|
||||||
|
|
||||||
@ -114,28 +115,28 @@ def get_purchase_order_details(filters, order_details):
|
|||||||
def get_column(filters):
|
def get_column(filters):
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"label": "Finished Good",
|
"label": _("Finished Good"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"fieldname": "item_code",
|
"fieldname": "item_code",
|
||||||
"width": 300,
|
"width": 300,
|
||||||
"options": "Item",
|
"options": "Item",
|
||||||
},
|
},
|
||||||
{"label": "Item Name", "fieldtype": "data", "fieldname": "item_name", "width": 100},
|
{"label": _("Item Name"), "fieldtype": "data", "fieldname": "item_name", "width": 100},
|
||||||
{
|
{
|
||||||
"label": "Document Type",
|
"label": _("Document Type"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"fieldname": "document_type",
|
"fieldname": "document_type",
|
||||||
"width": 150,
|
"width": 150,
|
||||||
"options": "DocType",
|
"options": "DocType",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Document Name",
|
"label": _("Document Name"),
|
||||||
"fieldtype": "Dynamic Link",
|
"fieldtype": "Dynamic Link",
|
||||||
"fieldname": "document_name",
|
"fieldname": "document_name",
|
||||||
"width": 150,
|
"width": 150,
|
||||||
},
|
},
|
||||||
{"label": "BOM Level", "fieldtype": "Int", "fieldname": "bom_level", "width": 100},
|
{"label": _("BOM Level"), "fieldtype": "Int", "fieldname": "bom_level", "width": 100},
|
||||||
{"label": "Order Qty", "fieldtype": "Float", "fieldname": "qty", "width": 120},
|
{"label": _("Order Qty"), "fieldtype": "Float", "fieldname": "qty", "width": 120},
|
||||||
{"label": "Received Qty", "fieldtype": "Float", "fieldname": "produced_qty", "width": 160},
|
{"label": _("Received Qty"), "fieldtype": "Float", "fieldname": "produced_qty", "width": 160},
|
||||||
{"label": "Pending Qty", "fieldtype": "Float", "fieldname": "pending_qty", "width": 110},
|
{"label": _("Pending Qty"), "fieldtype": "Float", "fieldname": "pending_qty", "width": 110},
|
||||||
]
|
]
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe import _
|
||||||
from frappe.utils import cint
|
from frappe.utils import cint
|
||||||
|
|
||||||
|
|
||||||
@ -99,59 +100,65 @@ def get_columns():
|
|||||||
columns = [
|
columns = [
|
||||||
{
|
{
|
||||||
"fieldname": "work_order",
|
"fieldname": "work_order",
|
||||||
"label": "Work Order",
|
"label": _("Work Order"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Work Order",
|
"options": "Work Order",
|
||||||
"width": 110,
|
"width": 110,
|
||||||
},
|
},
|
||||||
{"fieldname": "bom_no", "label": "BOM", "fieldtype": "Link", "options": "BOM", "width": 120},
|
{"fieldname": "bom_no", "label": _("BOM"), "fieldtype": "Link", "options": "BOM", "width": 120},
|
||||||
{
|
{
|
||||||
"fieldname": "description",
|
"fieldname": "description",
|
||||||
"label": "Description",
|
"label": _("Description"),
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"options": "",
|
"options": "",
|
||||||
"width": 230,
|
"width": 230,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "item_code",
|
"fieldname": "item_code",
|
||||||
"label": "Item Code",
|
"label": _("Item Code"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Item",
|
"options": "Item",
|
||||||
"width": 110,
|
"width": 110,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "source_warehouse",
|
"fieldname": "source_warehouse",
|
||||||
"label": "Source Warehouse",
|
"label": _("Source Warehouse"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Warehouse",
|
"options": "Warehouse",
|
||||||
"width": 110,
|
"width": 110,
|
||||||
},
|
},
|
||||||
{"fieldname": "qty", "label": "Qty to Build", "fieldtype": "Data", "options": "", "width": 110},
|
{
|
||||||
{"fieldname": "status", "label": "Status", "fieldtype": "Data", "options": "", "width": 100},
|
"fieldname": "qty",
|
||||||
|
"label": _("Qty to Build"),
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"options": "",
|
||||||
|
"width": 110,
|
||||||
|
},
|
||||||
|
{"fieldname": "status", "label": _("Status"), "fieldtype": "Data", "options": "", "width": 100},
|
||||||
{
|
{
|
||||||
"fieldname": "req_items",
|
"fieldname": "req_items",
|
||||||
"label": "# Req'd Items",
|
"label": _("# Req'd Items"),
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"options": "",
|
"options": "",
|
||||||
"width": 105,
|
"width": 105,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "instock",
|
"fieldname": "instock",
|
||||||
"label": "# In Stock",
|
"label": _("# In Stock"),
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"options": "",
|
"options": "",
|
||||||
"width": 105,
|
"width": 105,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "buildable_qty",
|
"fieldname": "buildable_qty",
|
||||||
"label": "Buildable Qty",
|
"label": _("Buildable Qty"),
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"options": "",
|
"options": "",
|
||||||
"width": 100,
|
"width": 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "ready_to_build",
|
"fieldname": "ready_to_build",
|
||||||
"label": "Build All?",
|
"label": _("Build All?"),
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"options": "",
|
"options": "",
|
||||||
"width": 90,
|
"width": 90,
|
||||||
|
@ -4,6 +4,7 @@ erpnext.patches.v11_0.rename_production_order_to_work_order
|
|||||||
erpnext.patches.v13_0.add_bin_unique_constraint
|
erpnext.patches.v13_0.add_bin_unique_constraint
|
||||||
erpnext.patches.v11_0.refactor_naming_series
|
erpnext.patches.v11_0.refactor_naming_series
|
||||||
erpnext.patches.v11_0.refactor_autoname_naming
|
erpnext.patches.v11_0.refactor_autoname_naming
|
||||||
|
erpnext.patches.v14_0.change_is_subcontracted_fieldtype
|
||||||
execute:frappe.reload_doc("accounts", "doctype", "POS Payment Method") #2020-05-28
|
execute:frappe.reload_doc("accounts", "doctype", "POS Payment Method") #2020-05-28
|
||||||
execute:frappe.reload_doc("HR", "doctype", "HR Settings") #2020-01-16 #2020-07-24
|
execute:frappe.reload_doc("HR", "doctype", "HR Settings") #2020-01-16 #2020-07-24
|
||||||
erpnext.patches.v4_2.update_requested_and_ordered_qty #2021-03-31
|
erpnext.patches.v4_2.update_requested_and_ordered_qty #2021-03-31
|
||||||
|
26
erpnext/patches/v14_0/change_is_subcontracted_fieldtype.py
Normal file
26
erpnext/patches/v14_0/change_is_subcontracted_fieldtype.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
for doctype in ["Purchase Order", "Purchase Receipt", "Purchase Invoice", "Supplier Quotation"]:
|
||||||
|
frappe.db.sql(
|
||||||
|
"""
|
||||||
|
UPDATE `tab{doctype}`
|
||||||
|
SET is_subcontracted = 0
|
||||||
|
where is_subcontracted in ('', NULL, 'No')""".format(
|
||||||
|
doctype=doctype
|
||||||
|
)
|
||||||
|
)
|
||||||
|
frappe.db.sql(
|
||||||
|
"""
|
||||||
|
UPDATE `tab{doctype}`
|
||||||
|
SET is_subcontracted = 1
|
||||||
|
where is_subcontracted = 'Yes'""".format(
|
||||||
|
doctype=doctype
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.reload_doc(frappe.get_meta(doctype).module, "doctype", frappe.scrub(doctype))
|
@ -44,8 +44,7 @@ class EmployeeBenefitClaim(Document):
|
|||||||
if max_benefits < claimed_amount:
|
if max_benefits < claimed_amount:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"Maximum benefit of employee {0} exceeds {1} by the sum {2} of previous claimed\
|
"Maximum benefit of employee {0} exceeds {1} by the sum {2} of previous claimed amount"
|
||||||
amount"
|
|
||||||
).format(self.employee, max_benefits, claimed_amount - max_benefits)
|
).format(self.employee, max_benefits, claimed_amount - max_benefits)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -84,8 +83,7 @@ class EmployeeBenefitClaim(Document):
|
|||||||
if max_benefits < pro_rata_amount + claimed_amount:
|
if max_benefits < pro_rata_amount + claimed_amount:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"Maximum benefit of employee {0} exceeds {1} by the sum {2} of benefit application pro-rata component\
|
"Maximum benefit of employee {0} exceeds {1} by the sum {2} of benefit application pro-rata component amount and previous claimed amount"
|
||||||
amount and previous claimed amount"
|
|
||||||
).format(
|
).format(
|
||||||
self.employee, max_benefits, pro_rata_amount + claimed_amount - max_benefits
|
self.employee, max_benefits, pro_rata_amount + claimed_amount - max_benefits
|
||||||
)
|
)
|
||||||
|
@ -34,7 +34,7 @@ frappe.ui.form.on('Gratuity Rule Slab', {
|
|||||||
to_year(frm, cdt, cdn) {
|
to_year(frm, cdt, cdn) {
|
||||||
let row = locals[cdt][cdn];
|
let row = locals[cdt][cdn];
|
||||||
if (row.to_year <= row.from_year && row.to_year === 0) {
|
if (row.to_year <= row.from_year && row.to_year === 0) {
|
||||||
frappe.throw(__("To(Year) year can not be less than From(year) "));
|
frappe.throw(__("To(Year) year can not be less than From(year)"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -112,7 +112,7 @@ frappe.ui.form.on('Payroll Entry', {
|
|||||||
},
|
},
|
||||||
callback: function (r) {
|
callback: function (r) {
|
||||||
if (r.message && !r.message.submitted) {
|
if (r.message && !r.message.submitted) {
|
||||||
frm.add_custom_button("Make Bank Entry", function () {
|
frm.add_custom_button(__("Make Bank Entry"), function () {
|
||||||
make_bank_entry(frm);
|
make_bank_entry(frm);
|
||||||
}).addClass("btn-primary");
|
}).addClass("btn-primary");
|
||||||
}
|
}
|
||||||
|
@ -142,21 +142,21 @@ def get_report_summary(gross_pay, total_deductions, net_pay, currency):
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"value": gross_pay,
|
"value": gross_pay,
|
||||||
"label": "Total Gross Pay",
|
"label": _("Total Gross Pay"),
|
||||||
"indicator": "Green",
|
"indicator": "Green",
|
||||||
"datatype": "Currency",
|
"datatype": "Currency",
|
||||||
"currency": currency,
|
"currency": currency,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"value": total_deductions,
|
"value": total_deductions,
|
||||||
"label": "Total Deduction",
|
"label": _("Total Deduction"),
|
||||||
"datatype": "Currency",
|
"datatype": "Currency",
|
||||||
"indicator": "Red",
|
"indicator": "Red",
|
||||||
"currency": currency,
|
"currency": currency,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"value": net_pay,
|
"value": net_pay,
|
||||||
"label": "Total Net Pay",
|
"label": _("Total Net Pay"),
|
||||||
"datatype": "Currency",
|
"datatype": "Currency",
|
||||||
"indicator": "Blue",
|
"indicator": "Blue",
|
||||||
"currency": currency,
|
"currency": currency,
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe import _
|
||||||
from frappe.utils import date_diff, nowdate
|
from frappe.utils import date_diff, nowdate
|
||||||
|
|
||||||
|
|
||||||
@ -83,19 +84,24 @@ def get_chart_data(data):
|
|||||||
|
|
||||||
def get_columns():
|
def get_columns():
|
||||||
columns = [
|
columns = [
|
||||||
{"fieldname": "name", "fieldtype": "Link", "label": "Task", "options": "Task", "width": 150},
|
{"fieldname": "name", "fieldtype": "Link", "label": _("Task"), "options": "Task", "width": 150},
|
||||||
{"fieldname": "subject", "fieldtype": "Data", "label": "Subject", "width": 200},
|
{"fieldname": "subject", "fieldtype": "Data", "label": _("Subject"), "width": 200},
|
||||||
{"fieldname": "status", "fieldtype": "Data", "label": "Status", "width": 100},
|
{"fieldname": "status", "fieldtype": "Data", "label": _("Status"), "width": 100},
|
||||||
{"fieldname": "priority", "fieldtype": "Data", "label": "Priority", "width": 80},
|
{"fieldname": "priority", "fieldtype": "Data", "label": _("Priority"), "width": 80},
|
||||||
{"fieldname": "progress", "fieldtype": "Data", "label": "Progress (%)", "width": 120},
|
{"fieldname": "progress", "fieldtype": "Data", "label": _("Progress (%)"), "width": 120},
|
||||||
{
|
{
|
||||||
"fieldname": "exp_start_date",
|
"fieldname": "exp_start_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Expected Start Date",
|
"label": _("Expected Start Date"),
|
||||||
"width": 150,
|
"width": 150,
|
||||||
},
|
},
|
||||||
{"fieldname": "exp_end_date", "fieldtype": "Date", "label": "Expected End Date", "width": 150},
|
{
|
||||||
{"fieldname": "completed_on", "fieldtype": "Date", "label": "Actual End Date", "width": 130},
|
"fieldname": "exp_end_date",
|
||||||
{"fieldname": "delay", "fieldtype": "Data", "label": "Delay (In Days)", "width": 120},
|
"fieldtype": "Date",
|
||||||
|
"label": _("Expected End Date"),
|
||||||
|
"width": 150,
|
||||||
|
},
|
||||||
|
{"fieldname": "completed_on", "fieldtype": "Date", "label": _("Actual End Date"), "width": 130},
|
||||||
|
{"fieldname": "delay", "fieldtype": "Data", "label": _("Delay (In Days)"), "width": 120},
|
||||||
]
|
]
|
||||||
return columns
|
return columns
|
||||||
|
@ -81,7 +81,7 @@ erpnext.buying.BuyingController = class BuyingController extends erpnext.Transac
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.frm.set_query("item_code", "items", function() {
|
this.frm.set_query("item_code", "items", function() {
|
||||||
if (me.frm.doc.is_subcontracted == "Yes") {
|
if (me.frm.doc.is_subcontracted) {
|
||||||
return{
|
return{
|
||||||
query: "erpnext.controllers.queries.item_query",
|
query: "erpnext.controllers.queries.item_query",
|
||||||
filters:{ 'supplier': me.frm.doc.supplier, 'is_sub_contracted_item': 1 }
|
filters:{ 'supplier': me.frm.doc.supplier, 'is_sub_contracted_item': 1 }
|
||||||
|
@ -239,7 +239,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
() => set_value('currency', currency),
|
() => set_value('currency', currency),
|
||||||
() => set_value('price_list_currency', currency),
|
() => set_value('price_list_currency', currency),
|
||||||
() => set_value('status', 'Draft'),
|
() => set_value('status', 'Draft'),
|
||||||
() => set_value('is_subcontracted', 'No'),
|
() => set_value('is_subcontracted', 0),
|
||||||
() => {
|
() => {
|
||||||
if(this.frm.doc.company && !this.frm.doc.amended_from) {
|
if(this.frm.doc.company && !this.frm.doc.amended_from) {
|
||||||
this.frm.trigger("company");
|
this.frm.trigger("company");
|
||||||
|
@ -483,7 +483,7 @@ erpnext.utils.update_child_items = function(opts) {
|
|||||||
if (frm.doc.doctype == 'Sales Order') {
|
if (frm.doc.doctype == 'Sales Order') {
|
||||||
filters = {"is_sales_item": 1};
|
filters = {"is_sales_item": 1};
|
||||||
} else if (frm.doc.doctype == 'Purchase Order') {
|
} else if (frm.doc.doctype == 'Purchase Order') {
|
||||||
if (frm.doc.is_subcontracted == "Yes") {
|
if (frm.doc.is_subcontracted) {
|
||||||
filters = {"is_sub_contracted_item": 1};
|
filters = {"is_sub_contracted_item": 1};
|
||||||
} else {
|
} else {
|
||||||
filters = {"is_purchase_item": 1};
|
filters = {"is_purchase_item": 1};
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
|
|
||||||
frappe.ui.form.on('DATEV Settings', {
|
frappe.ui.form.on('DATEV Settings', {
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
frm.add_custom_button('Show Report', () => frappe.set_route('query-report', 'DATEV'), "fa fa-table");
|
frm.add_custom_button(__('Show Report'), () => frappe.set_route('query-report', 'DATEV'), "fa fa-table");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -105,6 +105,30 @@ erpnext.setup_einvoice_actions = (doctype) => {
|
|||||||
},
|
},
|
||||||
primary_action_label: __('Submit')
|
primary_action_label: __('Submit')
|
||||||
});
|
});
|
||||||
|
d.fields_dict.transporter.df.onchange = function () {
|
||||||
|
const transporter = d.fields_dict.transporter.value;
|
||||||
|
if (transporter) {
|
||||||
|
frappe.db.get_value('Supplier', transporter, ['gst_transporter_id', 'supplier_name'])
|
||||||
|
.then(({ message }) => {
|
||||||
|
d.set_value('gst_transporter_id', message.gst_transporter_id);
|
||||||
|
d.set_value('transporter_name', message.supplier_name);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
d.set_value('gst_transporter_id', '');
|
||||||
|
d.set_value('transporter_name', '');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
d.fields_dict.driver.df.onchange = function () {
|
||||||
|
const driver = d.fields_dict.driver.value;
|
||||||
|
if (driver) {
|
||||||
|
frappe.db.get_value('Driver', driver, ['full_name'])
|
||||||
|
.then(({ message }) => {
|
||||||
|
d.set_value('driver_name', message.full_name);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
d.set_value('driver_name', '');
|
||||||
|
}
|
||||||
|
};
|
||||||
d.show();
|
d.show();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -153,7 +177,6 @@ const get_ewaybill_fields = (frm) => {
|
|||||||
'fieldname': 'gst_transporter_id',
|
'fieldname': 'gst_transporter_id',
|
||||||
'label': 'GST Transporter ID',
|
'label': 'GST Transporter ID',
|
||||||
'fieldtype': 'Data',
|
'fieldtype': 'Data',
|
||||||
'fetch_from': 'transporter.gst_transporter_id',
|
|
||||||
'default': frm.doc.gst_transporter_id
|
'default': frm.doc.gst_transporter_id
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -189,9 +212,9 @@ const get_ewaybill_fields = (frm) => {
|
|||||||
'fieldname': 'transporter_name',
|
'fieldname': 'transporter_name',
|
||||||
'label': 'Transporter Name',
|
'label': 'Transporter Name',
|
||||||
'fieldtype': 'Data',
|
'fieldtype': 'Data',
|
||||||
'fetch_from': 'transporter.name',
|
|
||||||
'read_only': 1,
|
'read_only': 1,
|
||||||
'default': frm.doc.transporter_name
|
'default': frm.doc.transporter_name,
|
||||||
|
'depends_on': 'transporter'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'fieldname': 'mode_of_transport',
|
'fieldname': 'mode_of_transport',
|
||||||
@ -206,7 +229,8 @@ const get_ewaybill_fields = (frm) => {
|
|||||||
'fieldtype': 'Data',
|
'fieldtype': 'Data',
|
||||||
'fetch_from': 'driver.full_name',
|
'fetch_from': 'driver.full_name',
|
||||||
'read_only': 1,
|
'read_only': 1,
|
||||||
'default': frm.doc.driver_name
|
'default': frm.doc.driver_name,
|
||||||
|
'depends_on': 'driver'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'fieldname': 'lr_date',
|
'fieldname': 'lr_date',
|
||||||
|
@ -387,7 +387,7 @@ def update_other_charges(
|
|||||||
|
|
||||||
def get_payment_details(invoice):
|
def get_payment_details(invoice):
|
||||||
payee_name = invoice.company
|
payee_name = invoice.company
|
||||||
mode_of_payment = ", ".join([d.mode_of_payment for d in invoice.payments])
|
mode_of_payment = ""
|
||||||
paid_amount = invoice.base_paid_amount
|
paid_amount = invoice.base_paid_amount
|
||||||
outstanding_amount = invoice.outstanding_amount
|
outstanding_amount = invoice.outstanding_amount
|
||||||
|
|
||||||
|
@ -727,7 +727,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
|
|||||||
args: {
|
args: {
|
||||||
reference_doctype: me.frm.doctype,
|
reference_doctype: me.frm.doctype,
|
||||||
reference_name: me.frm.docname,
|
reference_name: me.frm.docname,
|
||||||
content: __('Reason for hold: ')+data.reason_for_hold,
|
content: __('Reason for hold:') + ' ' + data.reason_for_hold,
|
||||||
comment_email: frappe.session.user,
|
comment_email: frappe.session.user,
|
||||||
comment_by: frappe.session.user_fullname
|
comment_by: frappe.session.user_fullname
|
||||||
},
|
},
|
||||||
|
@ -243,7 +243,7 @@ erpnext.PointOfSale.ItemSelector = class {
|
|||||||
value: "+1",
|
value: "+1",
|
||||||
item: { item_code, batch_no, serial_no, uom, rate }
|
item: { item_code, batch_no, serial_no, uom, rate }
|
||||||
});
|
});
|
||||||
me.set_search_value('');
|
me.search_field.set_focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.search_field.$input.on('input', (e) => {
|
this.search_field.$input.on('input', (e) => {
|
||||||
@ -328,6 +328,7 @@ erpnext.PointOfSale.ItemSelector = class {
|
|||||||
|
|
||||||
add_filtered_item_to_cart() {
|
add_filtered_item_to_cart() {
|
||||||
this.$items_container.find(".item-wrapper").click();
|
this.$items_container.find(".item-wrapper").click();
|
||||||
|
this.set_search_value('');
|
||||||
}
|
}
|
||||||
|
|
||||||
resize_selector(minimize) {
|
resize_selector(minimize) {
|
||||||
|
@ -102,7 +102,7 @@ def get_data_by_time(filters, common_columns):
|
|||||||
def get_data_by_territory(filters, common_columns):
|
def get_data_by_territory(filters, common_columns):
|
||||||
columns = [
|
columns = [
|
||||||
{
|
{
|
||||||
"label": "Territory",
|
"label": _("Territory"),
|
||||||
"fieldname": "territory",
|
"fieldname": "territory",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Territory",
|
"options": "Territory",
|
||||||
|
@ -65,7 +65,7 @@ def get_columns(customer_naming_type):
|
|||||||
_("Credit Limit") + ":Currency:120",
|
_("Credit Limit") + ":Currency:120",
|
||||||
_("Outstanding Amt") + ":Currency:100",
|
_("Outstanding Amt") + ":Currency:100",
|
||||||
_("Credit Balance") + ":Currency:120",
|
_("Credit Balance") + ":Currency:120",
|
||||||
_("Bypass credit check at Sales Order ") + ":Check:80",
|
_("Bypass credit check at Sales Order") + ":Check:80",
|
||||||
_("Is Frozen") + ":Check:80",
|
_("Is Frozen") + ":Check:80",
|
||||||
_("Disabled") + ":Check:80",
|
_("Disabled") + ":Check:80",
|
||||||
]
|
]
|
||||||
|
@ -235,7 +235,7 @@ def get_chart_data(data):
|
|||||||
return {
|
return {
|
||||||
"data": {
|
"data": {
|
||||||
"labels": labels[:30], # show max of 30 items in chart
|
"labels": labels[:30], # show max of 30 items in chart
|
||||||
"datasets": [{"name": _(" Total Sales Amount"), "values": datapoints[:30]}],
|
"datasets": [{"name": _("Total Sales Amount"), "values": datapoints[:30]}],
|
||||||
},
|
},
|
||||||
"type": "bar",
|
"type": "bar",
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ def get_chart_data(data, conditions, filters):
|
|||||||
"data": {
|
"data": {
|
||||||
"labels": labels,
|
"labels": labels,
|
||||||
"datasets": [
|
"datasets": [
|
||||||
{"name": _("{0}").format(filters.get("period")) + _(" Quoted Amount"), "values": datapoints}
|
{"name": _(filters.get("period")) + " " + _("Quoted Amount"), "values": datapoints}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"type": "line",
|
"type": "line",
|
||||||
|
@ -47,9 +47,7 @@ def get_chart_data(data, conditions, filters):
|
|||||||
return {
|
return {
|
||||||
"data": {
|
"data": {
|
||||||
"labels": labels,
|
"labels": labels,
|
||||||
"datasets": [
|
"datasets": [{"name": _(filters.get("period")) + " " + _("Sales Value"), "values": datapoints}],
|
||||||
{"name": _("{0}").format(filters.get("period")) + _(" Sales Value"), "values": datapoints}
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
"type": "line",
|
"type": "line",
|
||||||
"lineOptions": {"regionFill": 1},
|
"lineOptions": {"regionFill": 1},
|
||||||
|
@ -54,7 +54,7 @@ class Bin(Document):
|
|||||||
(supplied_item.rm_item_code == self.item_code)
|
(supplied_item.rm_item_code == self.item_code)
|
||||||
& (po.name == supplied_item.parent)
|
& (po.name == supplied_item.parent)
|
||||||
& (po.docstatus == 1)
|
& (po.docstatus == 1)
|
||||||
& (po.is_subcontracted == "Yes")
|
& (po.is_subcontracted)
|
||||||
& (po.status != "Closed")
|
& (po.status != "Closed")
|
||||||
& (po.per_received < 100)
|
& (po.per_received < 100)
|
||||||
& (supplied_item.reserve_warehouse == self.warehouse)
|
& (supplied_item.reserve_warehouse == self.warehouse)
|
||||||
@ -79,7 +79,7 @@ class Bin(Document):
|
|||||||
& (se.name == se_item.parent)
|
& (se.name == se_item.parent)
|
||||||
& (po.name == se.purchase_order)
|
& (po.name == se.purchase_order)
|
||||||
& (po.docstatus == 1)
|
& (po.docstatus == 1)
|
||||||
& (po.is_subcontracted == "Yes")
|
& (po.is_subcontracted == 1)
|
||||||
& (po.status != "Closed")
|
& (po.status != "Closed")
|
||||||
& (po.per_received < 100)
|
& (po.per_received < 100)
|
||||||
)
|
)
|
||||||
|
@ -55,10 +55,15 @@ frappe.ui.form.on("Item", {
|
|||||||
|
|
||||||
if (frm.doc.has_variants) {
|
if (frm.doc.has_variants) {
|
||||||
frm.set_intro(__("This Item is a Template and cannot be used in transactions. Item attributes will be copied over into the variants unless 'No Copy' is set"), true);
|
frm.set_intro(__("This Item is a Template and cannot be used in transactions. Item attributes will be copied over into the variants unless 'No Copy' is set"), true);
|
||||||
|
|
||||||
frm.add_custom_button(__("Show Variants"), function() {
|
frm.add_custom_button(__("Show Variants"), function() {
|
||||||
frappe.set_route("List", "Item", {"variant_of": frm.doc.name});
|
frappe.set_route("List", "Item", {"variant_of": frm.doc.name});
|
||||||
}, __("View"));
|
}, __("View"));
|
||||||
|
|
||||||
|
frm.add_custom_button(__("Item Variant Settings"), function() {
|
||||||
|
frappe.set_route("Form", "Item Variant Settings");
|
||||||
|
}, __("View"));
|
||||||
|
|
||||||
frm.add_custom_button(__("Variant Details Report"), function() {
|
frm.add_custom_button(__("Variant Details Report"), function() {
|
||||||
frappe.set_route("query-report", "Item Variant Details", {"item": frm.doc.name});
|
frappe.set_route("query-report", "Item Variant Details", {"item": frm.doc.name});
|
||||||
}, __("View"));
|
}, __("View"));
|
||||||
@ -110,6 +115,13 @@ frappe.ui.form.on("Item", {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, __('Actions'));
|
}, __('Actions'));
|
||||||
|
} else {
|
||||||
|
frm.add_custom_button(__("Website Item"), function() {
|
||||||
|
frappe.db.get_value("Website Item", {item_code: frm.doc.name}, "name", (d) => {
|
||||||
|
if (!d.name) frappe.throw(__("Website Item not found"));
|
||||||
|
frappe.set_route("Form", "Website Item", d.name);
|
||||||
|
});
|
||||||
|
}, __("View"));
|
||||||
}
|
}
|
||||||
|
|
||||||
erpnext.item.edit_prices_button(frm);
|
erpnext.item.edit_prices_button(frm);
|
||||||
@ -131,12 +143,6 @@ frappe.ui.form.on("Item", {
|
|||||||
frappe.set_route('Form', 'Item', new_item.name);
|
frappe.set_route('Form', 'Item', new_item.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
if(frm.doc.has_variants) {
|
|
||||||
frm.add_custom_button(__("Item Variant Settings"), function() {
|
|
||||||
frappe.set_route("Form", "Item Variant Settings");
|
|
||||||
}, __("View"));
|
|
||||||
}
|
|
||||||
|
|
||||||
const stock_exists = (frm.doc.__onload
|
const stock_exists = (frm.doc.__onload
|
||||||
&& frm.doc.__onload.stock_exists) ? 1 : 0;
|
&& frm.doc.__onload.stock_exists) ? 1 : 0;
|
||||||
|
|
||||||
|
@ -464,7 +464,7 @@ class Item(Document):
|
|||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
_("It can take upto few hours for accurate stock values to be visible after merging items."),
|
_("It can take upto few hours for accurate stock values to be visible after merging items."),
|
||||||
indicator="orange",
|
indicator="orange",
|
||||||
title="Note",
|
title=_("Note"),
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.published_in_website:
|
if self.published_in_website:
|
||||||
|
@ -32,5 +32,6 @@ def get_data():
|
|||||||
{"label": _("Manufacture"), "items": ["Production Plan", "Work Order", "Item Manufacturer"]},
|
{"label": _("Manufacture"), "items": ["Production Plan", "Work Order", "Item Manufacturer"]},
|
||||||
{"label": _("Traceability"), "items": ["Serial No", "Batch"]},
|
{"label": _("Traceability"), "items": ["Serial No", "Batch"]},
|
||||||
{"label": _("Move"), "items": ["Stock Entry"]},
|
{"label": _("Move"), "items": ["Stock Entry"]},
|
||||||
|
{"label": _("E-commerce"), "items": ["Website Item"]},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ class TestItemAlternative(FrappeTestCase):
|
|||||||
supplier_warehouse = "Test Supplier Warehouse - _TC"
|
supplier_warehouse = "Test Supplier Warehouse - _TC"
|
||||||
po = create_purchase_order(
|
po = create_purchase_order(
|
||||||
item="Test Finished Goods - A",
|
item="Test Finished Goods - A",
|
||||||
is_subcontracted="Yes",
|
is_subcontracted=1,
|
||||||
qty=5,
|
qty=5,
|
||||||
rate=3000,
|
rate=3000,
|
||||||
supplier_warehouse=supplier_warehouse,
|
supplier_warehouse=supplier_warehouse,
|
||||||
|
@ -209,16 +209,14 @@ class MaterialRequest(BuyingController):
|
|||||||
if d.ordered_qty and d.ordered_qty > allowed_qty:
|
if d.ordered_qty and d.ordered_qty > allowed_qty:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"The total Issue / Transfer quantity {0} in Material Request {1} \
|
"The total Issue / Transfer quantity {0} in Material Request {1} cannot be greater than allowed requested quantity {2} for Item {3}"
|
||||||
cannot be greater than allowed requested quantity {2} for Item {3}"
|
|
||||||
).format(d.ordered_qty, d.parent, allowed_qty, d.item_code)
|
).format(d.ordered_qty, d.parent, allowed_qty, d.item_code)
|
||||||
)
|
)
|
||||||
|
|
||||||
elif d.ordered_qty and d.ordered_qty > d.stock_qty:
|
elif d.ordered_qty and d.ordered_qty > d.stock_qty:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"The total Issue / Transfer quantity {0} in Material Request {1} \
|
"The total Issue / Transfer quantity {0} in Material Request {1} cannot be greater than requested quantity {2} for Item {3}"
|
||||||
cannot be greater than requested quantity {2} for Item {3}"
|
|
||||||
).format(d.ordered_qty, d.parent, d.qty, d.item_code)
|
).format(d.ordered_qty, d.parent, d.qty, d.item_code)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -33,7 +33,9 @@ class PickList(Document):
|
|||||||
location.sales_order
|
location.sales_order
|
||||||
and frappe.db.get_value("Sales Order", location.sales_order, "per_picked") == 100
|
and frappe.db.get_value("Sales Order", location.sales_order, "per_picked") == 100
|
||||||
):
|
):
|
||||||
frappe.throw("Row " + str(location.idx) + " has been picked already!")
|
frappe.throw(
|
||||||
|
_("Row #{}: item {} has been picked already.").format(location.idx, location.item_code)
|
||||||
|
)
|
||||||
|
|
||||||
def before_submit(self):
|
def before_submit(self):
|
||||||
for item in self.locations:
|
for item in self.locations:
|
||||||
@ -82,10 +84,9 @@ class PickList(Document):
|
|||||||
100 + flt(frappe.db.get_single_value("Stock Settings", "over_delivery_receipt_allowance"))
|
100 + flt(frappe.db.get_single_value("Stock Settings", "over_delivery_receipt_allowance"))
|
||||||
):
|
):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
"You are picking more than required quantity for "
|
_(
|
||||||
+ item_code
|
"You are picking more than required quantity for {}. Check if there is any other pick list created for {}"
|
||||||
+ ". Check if there is any other pick list created for "
|
).format(item_code, so_doc.name)
|
||||||
+ so_doc.name
|
|
||||||
)
|
)
|
||||||
|
|
||||||
frappe.db.set_value("Sales Order Item", so_item, "picked_qty", already_picked + picked_qty)
|
frappe.db.set_value("Sales Order Item", so_item, "picked_qty", already_picked + picked_qty)
|
||||||
|
@ -200,7 +200,7 @@ erpnext.stock.PurchaseReceiptController = class PurchaseReceiptController extend
|
|||||||
cur_frm.add_custom_button(__('Reopen'), this.reopen_purchase_receipt, __("Status"))
|
cur_frm.add_custom_button(__('Reopen'), this.reopen_purchase_receipt, __("Status"))
|
||||||
}
|
}
|
||||||
|
|
||||||
this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted==="Yes");
|
this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted);
|
||||||
}
|
}
|
||||||
|
|
||||||
make_purchase_invoice() {
|
make_purchase_invoice() {
|
||||||
@ -298,10 +298,10 @@ cur_frm.fields_dict['items'].grid.get_field('bom').get_query = function(doc, cdt
|
|||||||
frappe.provide("erpnext.buying");
|
frappe.provide("erpnext.buying");
|
||||||
|
|
||||||
frappe.ui.form.on("Purchase Receipt", "is_subcontracted", function(frm) {
|
frappe.ui.form.on("Purchase Receipt", "is_subcontracted", function(frm) {
|
||||||
if (frm.doc.is_subcontracted === "Yes") {
|
if (frm.doc.is_subcontracted) {
|
||||||
erpnext.buying.get_default_bom(frm);
|
erpnext.buying.get_default_bom(frm);
|
||||||
}
|
}
|
||||||
frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted==="Yes");
|
frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted);
|
||||||
});
|
});
|
||||||
|
|
||||||
frappe.ui.form.on('Purchase Receipt Item', {
|
frappe.ui.form.on('Purchase Receipt Item', {
|
||||||
|
@ -437,17 +437,16 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "No",
|
"default": "0",
|
||||||
"fieldname": "is_subcontracted",
|
"fieldname": "is_subcontracted",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Check",
|
||||||
"label": "Raw Materials Consumed",
|
"label": "Is Subcontracted",
|
||||||
"oldfieldname": "is_subcontracted",
|
"oldfieldname": "is_subcontracted",
|
||||||
"oldfieldtype": "Select",
|
"oldfieldtype": "Select",
|
||||||
"options": "No\nYes",
|
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.is_subcontracted==\"Yes\"",
|
"depends_on": "eval:doc.is_subcontracted",
|
||||||
"fieldname": "supplier_warehouse",
|
"fieldname": "supplier_warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Supplier Warehouse",
|
"label": "Supplier Warehouse",
|
||||||
|
@ -327,7 +327,7 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
target="_Test Warehouse 1 - _TC",
|
target="_Test Warehouse 1 - _TC",
|
||||||
basic_rate=100,
|
basic_rate=100,
|
||||||
)
|
)
|
||||||
pr = make_purchase_receipt(item_code="_Test FG Item", qty=10, rate=500, is_subcontracted="Yes")
|
pr = make_purchase_receipt(item_code="_Test FG Item", qty=10, rate=500, is_subcontracted=1)
|
||||||
self.assertEqual(len(pr.get("supplied_items")), 2)
|
self.assertEqual(len(pr.get("supplied_items")), 2)
|
||||||
|
|
||||||
rm_supp_cost = sum(d.amount for d in pr.get("supplied_items"))
|
rm_supp_cost = sum(d.amount for d in pr.get("supplied_items"))
|
||||||
@ -362,7 +362,7 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
item_code="_Test FG Item",
|
item_code="_Test FG Item",
|
||||||
qty=10,
|
qty=10,
|
||||||
rate=0,
|
rate=0,
|
||||||
is_subcontracted="Yes",
|
is_subcontracted=1,
|
||||||
company="_Test Company with perpetual inventory",
|
company="_Test Company with perpetual inventory",
|
||||||
warehouse="Stores - TCP1",
|
warehouse="Stores - TCP1",
|
||||||
supplier_warehouse="Work In Progress - TCP1",
|
supplier_warehouse="Work In Progress - TCP1",
|
||||||
@ -401,7 +401,7 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
item_code=item_code,
|
item_code=item_code,
|
||||||
qty=1,
|
qty=1,
|
||||||
include_exploded_items=0,
|
include_exploded_items=0,
|
||||||
is_subcontracted="Yes",
|
is_subcontracted=1,
|
||||||
supplier_warehouse="_Test Warehouse 1 - _TC",
|
supplier_warehouse="_Test Warehouse 1 - _TC",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -647,6 +647,45 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
return_pr.cancel()
|
return_pr.cancel()
|
||||||
pr.cancel()
|
pr.cancel()
|
||||||
|
|
||||||
|
def test_purchase_receipt_for_rejected_gle_without_accepted_warehouse(self):
|
||||||
|
from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse
|
||||||
|
|
||||||
|
rejected_warehouse = "_Test Rejected Warehouse - TCP1"
|
||||||
|
if not frappe.db.exists("Warehouse", rejected_warehouse):
|
||||||
|
get_warehouse(
|
||||||
|
company="_Test Company with perpetual inventory",
|
||||||
|
abbr=" - TCP1",
|
||||||
|
warehouse_name="_Test Rejected Warehouse",
|
||||||
|
).name
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(
|
||||||
|
company="_Test Company with perpetual inventory",
|
||||||
|
warehouse="Stores - TCP1",
|
||||||
|
received_qty=2,
|
||||||
|
rejected_qty=2,
|
||||||
|
rejected_warehouse=rejected_warehouse,
|
||||||
|
do_not_save=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
pr.items[0].qty = 0.0
|
||||||
|
pr.items[0].warehouse = ""
|
||||||
|
pr.submit()
|
||||||
|
|
||||||
|
actual_qty = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{
|
||||||
|
"voucher_type": "Purchase Receipt",
|
||||||
|
"voucher_no": pr.name,
|
||||||
|
"warehouse": pr.items[0].rejected_warehouse,
|
||||||
|
"is_cancelled": 0,
|
||||||
|
},
|
||||||
|
"actual_qty",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(actual_qty, 2)
|
||||||
|
self.assertFalse(pr.items[0].warehouse)
|
||||||
|
pr.cancel()
|
||||||
|
|
||||||
def test_purchase_return_for_serialized_items(self):
|
def test_purchase_return_for_serialized_items(self):
|
||||||
def _check_serial_no_values(serial_no, field_values):
|
def _check_serial_no_values(serial_no, field_values):
|
||||||
serial_no = frappe.get_doc("Serial No", serial_no)
|
serial_no = frappe.get_doc("Serial No", serial_no)
|
||||||
@ -1122,7 +1161,7 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
po = create_purchase_order(
|
po = create_purchase_order(
|
||||||
item_code=item_code,
|
item_code=item_code,
|
||||||
qty=order_qty,
|
qty=order_qty,
|
||||||
is_subcontracted="Yes",
|
is_subcontracted=1,
|
||||||
supplier_warehouse="_Test Warehouse 1 - _TC",
|
supplier_warehouse="_Test Warehouse 1 - _TC",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1465,7 +1504,7 @@ def make_purchase_receipt(**args):
|
|||||||
pr.set_posting_time = 1
|
pr.set_posting_time = 1
|
||||||
pr.company = args.company or "_Test Company"
|
pr.company = args.company or "_Test Company"
|
||||||
pr.supplier = args.supplier or "_Test Supplier"
|
pr.supplier = args.supplier or "_Test Supplier"
|
||||||
pr.is_subcontracted = args.is_subcontracted or "No"
|
pr.is_subcontracted = args.is_subcontracted or 0
|
||||||
pr.supplier_warehouse = args.supplier_warehouse or "_Test Warehouse 1 - _TC"
|
pr.supplier_warehouse = args.supplier_warehouse or "_Test Warehouse 1 - _TC"
|
||||||
pr.currency = args.currency or "INR"
|
pr.currency = args.currency or "INR"
|
||||||
pr.is_return = args.is_return
|
pr.is_return = args.is_return
|
||||||
|
@ -92,7 +92,7 @@
|
|||||||
"currency": "INR",
|
"currency": "INR",
|
||||||
"doctype": "Purchase Receipt",
|
"doctype": "Purchase Receipt",
|
||||||
"base_grand_total": 5000.0,
|
"base_grand_total": 5000.0,
|
||||||
"is_subcontracted": "Yes",
|
"is_subcontracted": 1,
|
||||||
"base_net_total": 5000.0,
|
"base_net_total": 5000.0,
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
|
@ -648,7 +648,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"depends_on": "eval:parent.is_subcontracted == 'Yes'",
|
"depends_on": "eval:parent.is_subcontracted",
|
||||||
"fieldname": "include_exploded_items",
|
"fieldname": "include_exploded_items",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Include Exploded Items",
|
"label": "Include Exploded Items",
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user