Merge branch 'develop' of https://github.com/frappe/erpnext into buying_selling_pricing_rule

This commit is contained in:
Deepesh Garg 2022-12-08 18:05:05 +05:30
commit 6db52930d8
47 changed files with 497 additions and 294 deletions

View File

@ -24,15 +24,14 @@ fi
if [ "$DB" == "mariadb" ];then
mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL character_set_server = 'utf8mb4'"
mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'"
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE DATABASE test_frappe"
mysql --host 127.0.0.1 --port 3306 -u root -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE DATABASE test_frappe"
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
mysql --host 127.0.0.1 --port 3306 -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'"
mysql --host 127.0.0.1 --port 3306 -u root -e "FLUSH PRIVILEGES"
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "FLUSH PRIVILEGES"
fi
if [ "$DB" == "postgres" ];then

View File

@ -9,8 +9,8 @@
"mail_password": "test",
"admin_password": "admin",
"root_login": "root",
"root_password": "travis",
"root_password": "root",
"host_name": "http://test_site:8000",
"install_apps": ["erpnext"],
"install_apps": ["payments", "erpnext"],
"throttle_user_limit": 100
}

View File

@ -25,7 +25,7 @@ jobs:
mysql:
image: mariadb:10.3
env:
MYSQL_ALLOW_EMPTY_PASSWORD: YES
MARIADB_ROOT_PASSWORD: 'root'
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3

View File

@ -45,9 +45,9 @@ jobs:
services:
mysql:
image: mariadb:10.3
image: mariadb:10.6
env:
MYSQL_ALLOW_EMPTY_PASSWORD: YES
MARIADB_ROOT_PASSWORD: 'root'
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3

View File

@ -9,10 +9,6 @@ from frappe.model.document import Document
from frappe.utils import add_days, add_years, cstr, getdate
class FiscalYearIncorrectDate(frappe.ValidationError):
pass
class FiscalYear(Document):
@frappe.whitelist()
def set_as_default(self):
@ -53,23 +49,18 @@ class FiscalYear(Document):
)
def validate_dates(self):
self.validate_from_to_dates("year_start_date", "year_end_date")
if self.is_short_year:
# Fiscal Year can be shorter than one year, in some jurisdictions
# under certain circumstances. For example, in the USA and Germany.
return
if getdate(self.year_start_date) > getdate(self.year_end_date):
frappe.throw(
_("Fiscal Year Start Date should be one year earlier than Fiscal Year End Date"),
FiscalYearIncorrectDate,
)
date = getdate(self.year_start_date) + relativedelta(years=1) - relativedelta(days=1)
if getdate(self.year_end_date) != date:
frappe.throw(
_("Fiscal Year End Date should be one year after Fiscal Year Start Date"),
FiscalYearIncorrectDate,
frappe.exceptions.InvalidDates,
)
def on_update(self):
@ -169,5 +160,6 @@ def auto_create_fiscal_year():
def get_from_and_to_date(fiscal_year):
fields = ["year_start_date as from_date", "year_end_date as to_date"]
return frappe.get_cached_value("Fiscal Year", fiscal_year, fields, as_dict=1)
fields = ["year_start_date", "year_end_date"]
cached_results = frappe.get_cached_value("Fiscal Year", fiscal_year, fields, as_dict=1)
return dict(from_date=cached_results.year_start_date, to_date=cached_results.year_end_date)

View File

@ -7,8 +7,6 @@ import unittest
import frappe
from frappe.utils import now_datetime
from erpnext.accounts.doctype.fiscal_year.fiscal_year import FiscalYearIncorrectDate
test_ignore = ["Company"]
@ -26,7 +24,7 @@ class TestFiscalYear(unittest.TestCase):
}
)
self.assertRaises(FiscalYearIncorrectDate, fy.insert)
self.assertRaises(frappe.exceptions.InvalidDates, fy.insert)
def test_record_generator():
@ -35,8 +33,8 @@ def test_record_generator():
"doctype": "Fiscal Year",
"year": "_Test Short Fiscal Year 2011",
"is_short_year": 1,
"year_end_date": "2011-04-01",
"year_start_date": "2011-12-31",
"year_start_date": "2011-04-01",
"year_end_date": "2011-12-31",
}
]

View File

@ -3,6 +3,7 @@
frappe.ui.form.on('Payment Gateway Account', {
refresh(frm) {
erpnext.utils.check_payments_app();
if(!frm.doc.__islocal) {
frm.set_df_property('payment_gateway', 'read_only', 1);
}

View File

@ -9,7 +9,6 @@ from frappe import _
from frappe.model.document import Document
from frappe.utils import flt, get_url, nowdate
from frappe.utils.background_jobs import enqueue
from payments.utils import get_payment_gateway_controller
from erpnext.accounts.doctype.payment_entry.payment_entry import (
get_company_defaults,
@ -19,6 +18,14 @@ from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_pla
from erpnext.accounts.party import get_party_account, get_party_bank_account
from erpnext.accounts.utils import get_account_currency
from erpnext.erpnext_integrations.stripe_integration import create_stripe_subscription
from erpnext.utilities import payment_app_import_guard
def _get_payment_gateway_controller(*args, **kwargs):
with payment_app_import_guard():
from payments.utils import get_payment_gateway_controller
return get_payment_gateway_controller(*args, **kwargs)
class PaymentRequest(Document):
@ -107,7 +114,7 @@ class PaymentRequest(Document):
self.request_phone_payment()
def request_phone_payment(self):
controller = get_payment_gateway_controller(self.payment_gateway)
controller = _get_payment_gateway_controller(self.payment_gateway)
request_amount = self.get_request_amount()
payment_record = dict(
@ -156,7 +163,7 @@ class PaymentRequest(Document):
def payment_gateway_validation(self):
try:
controller = get_payment_gateway_controller(self.payment_gateway)
controller = _get_payment_gateway_controller(self.payment_gateway)
if hasattr(controller, "on_payment_request_submission"):
return controller.on_payment_request_submission(self)
else:
@ -189,7 +196,7 @@ class PaymentRequest(Document):
)
data.update({"company": frappe.defaults.get_defaults().company})
controller = get_payment_gateway_controller(self.payment_gateway)
controller = _get_payment_gateway_controller(self.payment_gateway)
controller.validate_transaction_currency(self.currency)
if hasattr(controller, "validate_minimum_transaction_amount"):

View File

@ -10,7 +10,7 @@ import re
import frappe
from frappe import _, throw
from frappe.model.document import Document
from frappe.utils import cint, flt, getdate
from frappe.utils import cint, flt
apply_on_dict = {"Item Code": "items", "Item Group": "item_groups", "Brand": "brands"}
@ -184,8 +184,7 @@ class PricingRule(Document):
if self.is_cumulative and not (self.valid_from and self.valid_upto):
frappe.throw(_("Valid from and valid upto fields are mandatory for the cumulative"))
if self.valid_from and self.valid_upto and getdate(self.valid_from) > getdate(self.valid_upto):
frappe.throw(_("Valid from date must be less than valid upto date"))
self.validate_from_to_dates("valid_from", "valid_upto")
def validate_condition(self):
if (

View File

@ -231,7 +231,9 @@ class PurchaseInvoice(BuyingController):
)
if (
cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate")) and not self.is_return
cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate"))
and not self.is_return
and not self.is_internal_supplier
):
self.validate_rate_with_reference_doc(
[

View File

@ -921,6 +921,7 @@
"fieldtype": "Table",
"hide_days": 1,
"hide_seconds": 1,
"label": "Sales Taxes and Charges",
"oldfieldname": "other_charges",
"oldfieldtype": "Table",
"options": "Sales Taxes and Charges"
@ -2133,7 +2134,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2022-11-17 17:17:10.883487",
"modified": "2022-12-05 16:18:14.532114",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@ -5,5 +5,9 @@ frappe.ui.form.on('Subscription Plan', {
price_determination: function(frm) {
frm.toggle_reqd("cost", frm.doc.price_determination === 'Fixed rate');
frm.toggle_reqd("price_list", frm.doc.price_determination === 'Based on price list');
}
},
subscription_plan: function (frm) {
erpnext.utils.check_payments_app();
},
});

View File

@ -32,7 +32,7 @@ class TaxRule(Document):
def validate(self):
self.validate_tax_template()
self.validate_date()
self.validate_from_to_dates("from_date", "to_date")
self.validate_filters()
self.validate_use_for_shopping_cart()
@ -51,10 +51,6 @@ class TaxRule(Document):
if not (self.sales_tax_template or self.purchase_tax_template):
frappe.throw(_("Tax Template is mandatory."))
def validate_date(self):
if self.from_date and self.to_date and self.from_date > self.to_date:
frappe.throw(_("From Date cannot be greater than To Date"))
def validate_filters(self):
filters = {
"tax_type": self.tax_type,

View File

@ -28,7 +28,7 @@ def get_currency(filters):
filters["presentation_currency"] if filters.get("presentation_currency") else company_currency
)
report_date = filters.get("to_date")
report_date = filters.get("to_date") or filters.get("period_end_date")
if not report_date:
fiscal_year_to_date = get_from_and_to_date(filters.get("to_fiscal_year"))["to_date"]

View File

@ -322,17 +322,18 @@ class BuyingController(SubcontractingController):
)
if self.is_internal_transfer():
if rate != d.rate:
d.rate = rate
frappe.msgprint(
_(
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
).format(d.idx),
alert=1,
)
d.discount_percentage = 0.0
d.discount_amount = 0.0
d.margin_rate_or_amount = 0.0
if self.doctype == "Purchase Receipt" or self.get("update_stock"):
if rate != d.rate:
d.rate = rate
frappe.msgprint(
_(
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
).format(d.idx),
alert=1,
)
d.discount_percentage = 0.0
d.discount_amount = 0.0
d.margin_rate_or_amount = 0.0
def validate_for_subcontracting(self):
if self.is_subcontracted and self.get("is_old_subcontracting_flow"):

View File

@ -442,30 +442,31 @@ class SellingController(StockController):
# For internal transfers use incoming rate as the valuation rate
if self.is_internal_transfer():
if d.doctype == "Packed Item":
incoming_rate = flt(
flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
d.precision("incoming_rate"),
)
if d.incoming_rate != incoming_rate:
d.incoming_rate = incoming_rate
else:
rate = flt(
flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
d.precision("rate"),
)
if d.rate != rate:
d.rate = rate
frappe.msgprint(
_(
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
).format(d.idx),
alert=1,
if self.doctype == "Delivery Note" or self.get("update_stock"):
if d.doctype == "Packed Item":
incoming_rate = flt(
flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
d.precision("incoming_rate"),
)
if d.incoming_rate != incoming_rate:
d.incoming_rate = incoming_rate
else:
rate = flt(
flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
d.precision("rate"),
)
if d.rate != rate:
d.rate = rate
frappe.msgprint(
_(
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
).format(d.idx),
alert=1,
)
d.discount_percentage = 0.0
d.discount_amount = 0.0
d.margin_rate_or_amount = 0.0
d.discount_percentage = 0.0
d.discount_amount = 0.0
d.margin_rate_or_amount = 0.0
elif self.get("return_against"):
# Get incoming rate of return entry from reference document

View File

@ -349,7 +349,7 @@ class StatusUpdater(Document):
def warn_about_bypassing_with_role(self, item, qty_or_amount, role):
action = _("Over Receipt/Delivery") if qty_or_amount == "qty" else _("Overbilling")
msg = _("{} of {} {} ignored for item {} because you have {} role.").format(
msg = _("{0} of {1} {2} ignored for item {3} because you have {4} role.").format(
action,
_(item["target_ref_field"].title()),
frappe.bold(item["reduce_by"]),

View File

@ -453,6 +453,7 @@ def get_lead_with_phone_number(number):
"Lead",
or_filters={
"phone": ["like", "%{}".format(number)],
"whatsapp_no": ["like", "%{}".format(number)],
"mobile_no": ["like", "%{}".format(number)],
},
limit=1,

View File

@ -48,5 +48,11 @@ frappe.ui.form.on("E Commerce Settings", {
frm.set_value('default_customer_group', '');
frm.set_value('quotation_series', '');
}
},
enable_checkout: function(frm) {
if (frm.doc.enable_checkout) {
erpnext.utils.check_payments_app();
}
}
});

View File

@ -2,4 +2,7 @@
// For license information, please see license.txt
frappe.ui.form.on('GoCardless Settings', {
refresh: function(frm) {
erpnext.utils.check_payments_app();
}
});

View File

@ -173,7 +173,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-02-12 14:18:47.209114",
"modified": "2022-02-12 14:18:47.209114",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "GoCardless Settings",
@ -201,7 +201,6 @@
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,

View File

@ -10,7 +10,8 @@ from frappe import _
from frappe.integrations.utils import create_request_log
from frappe.model.document import Document
from frappe.utils import call_hook_method, cint, flt, get_url
from payments.utils import create_payment_gateway
from erpnext.utilities import payment_app_import_guard
class GoCardlessSettings(Document):
@ -30,6 +31,9 @@ class GoCardlessSettings(Document):
frappe.throw(e)
def on_update(self):
with payment_app_import_guard():
from payments.utils import create_payment_gateway
create_payment_gateway(
"GoCardless-" + self.gateway_name, settings="GoCardLess Settings", controller=self.gateway_name
)

View File

@ -7,6 +7,8 @@ frappe.ui.form.on('Mpesa Settings', {
},
refresh: function(frm) {
erpnext.utils.check_payments_app();
frappe.realtime.on("refresh_mpesa_dashboard", function(){
frm.reload_doc();
frm.events.setup_account_balance_html(frm);

View File

@ -9,13 +9,13 @@ from frappe import _
from frappe.integrations.utils import create_request_log
from frappe.model.document import Document
from frappe.utils import call_hook_method, fmt_money, get_request_site_address
from payments.utils import create_payment_gateway
from erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_connector import MpesaConnector
from erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_custom_fields import (
create_custom_pos_fields,
)
from erpnext.erpnext_integrations.utils import create_mode_of_payment
from erpnext.utilities import payment_app_import_guard
class MpesaSettings(Document):
@ -30,6 +30,9 @@ class MpesaSettings(Document):
)
def on_update(self):
with payment_app_import_guard():
from payments.utils import create_payment_gateway
create_custom_pos_fields()
create_payment_gateway(
"Mpesa-" + self.payment_gateway_name,

View File

@ -2,12 +2,16 @@
# For license information, please see license.txt
import frappe
import stripe
from frappe import _
from frappe.integrations.utils import create_request_log
from erpnext.utilities import payment_app_import_guard
def create_stripe_subscription(gateway_controller, data):
with payment_app_import_guard():
import stripe
stripe_settings = frappe.get_doc("Stripe Settings", gateway_controller)
stripe_settings.data = frappe._dict(data)
@ -35,6 +39,9 @@ def create_stripe_subscription(gateway_controller, data):
def create_subscription_on_stripe(stripe_settings):
with payment_app_import_guard():
import stripe
items = []
for payment_plan in stripe_settings.payment_plans:
plan = frappe.db.get_value("Subscription Plan", payment_plan.plan, "product_price_id")

View File

@ -8,7 +8,6 @@ app_email = "info@erpnext.com"
app_license = "GNU General Public License (v3)"
source_link = "https://github.com/frappe/erpnext"
app_logo_url = "/assets/erpnext/images/erpnext-logo.svg"
required_apps = ["payments"]
develop_version = "14.x.x-develop"

View File

@ -635,6 +635,10 @@ class TestWorkOrder(FrappeTestCase):
bom.submit()
bom_name = bom.name
ste1 = test_stock_entry.make_stock_entry(
item_code=rm1, target="_Test Warehouse - _TC", qty=32, basic_rate=5000.0
)
work_order = make_wo_order_test_record(
item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1
)
@ -659,11 +663,29 @@ class TestWorkOrder(FrappeTestCase):
work_order.insert()
work_order.submit()
self.assertEqual(work_order.has_batch_no, 1)
ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 30))
batches = frappe.get_all("Batch", filters={"reference_name": work_order.name})
self.assertEqual(len(batches), 3)
batches = [batch.name for batch in batches]
ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 10))
for row in ste1.get("items"):
if row.is_finished_item:
self.assertEqual(row.item_code, fg_item)
self.assertEqual(row.qty, 10)
self.assertTrue(row.batch_no in batches)
batches.remove(row.batch_no)
ste1.submit()
remaining_batches = []
ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 20))
for row in ste1.get("items"):
if row.is_finished_item:
self.assertEqual(row.item_code, fg_item)
self.assertEqual(row.qty, 10)
remaining_batches.append(row.batch_no)
self.assertEqual(sorted(remaining_batches), sorted(batches))
frappe.db.set_value("Manufacturing Settings", None, "make_serial_no_batch_from_work_order", 0)

View File

@ -49,7 +49,7 @@ class ProductionPlanReport(object):
parent.bom_no,
parent.fg_warehouse.as_("warehouse"),
)
.where(parent.status.notin(["Completed", "Stopped"]))
.where(parent.status.notin(["Completed", "Stopped", "Closed"]))
)
if order_by == "Planned Start Date":
@ -79,10 +79,11 @@ class ProductionPlanReport(object):
query = query.where(child.parent.isin(self.filters.docnames))
if doctype == "Sales Order":
query = query.select(
child.delivery_date,
parent.base_grand_total,
).where((child.stock_qty > child.produced_qty) & (parent.per_delivered < 100.0))
query = query.select(child.delivery_date, parent.base_grand_total,).where(
(child.stock_qty > child.produced_qty)
& (parent.per_delivered < 100.0)
& (parent.status.notin(["Completed", "Closed"]))
)
if order_by == "Delivery Date":
query = query.orderby(child.delivery_date, order=Order.asc)
@ -91,7 +92,9 @@ class ProductionPlanReport(object):
elif doctype == "Material Request":
query = query.select(child.schedule_date,).where(
(parent.per_ordered < 100) & (parent.material_request_type == "Manufacture")
(parent.per_ordered < 100)
& (parent.material_request_type == "Manufacture")
& (parent.status != "Stopped")
)
if order_by == "Required Date":

View File

@ -1,5 +1,8 @@
import frappe
from erpnext.setup.install import setup_currency_exchange
def execute():
frappe.reload_doc("accounts", "doctype", "currency_exchange_settings")
setup_currency_exchange()

View File

@ -42,6 +42,8 @@ class Project(Document):
self.send_welcome_email()
self.update_costing()
self.update_percent_complete()
self.validate_from_to_dates("expected_start_date", "expected_end_date")
self.validate_from_to_dates("actual_start_date", "actual_end_date")
def copy_from_template(self):
"""

View File

@ -9,6 +9,7 @@ from frappe import _, throw
from frappe.desk.form.assign_to import clear, close_all_assignments
from frappe.model.mapper import get_mapped_doc
from frappe.utils import add_days, cstr, date_diff, flt, get_link_to_form, getdate, today
from frappe.utils.data import format_date
from frappe.utils.nestedset import NestedSet
@ -16,10 +17,6 @@ class CircularReferenceError(frappe.ValidationError):
pass
class EndDateCannotBeGreaterThanProjectEndDateError(frappe.ValidationError):
pass
class Task(NestedSet):
nsm_parent_field = "parent_task"
@ -34,8 +31,6 @@ class Task(NestedSet):
def validate(self):
self.validate_dates()
self.validate_parent_expected_end_date()
self.validate_parent_project_dates()
self.validate_progress()
self.validate_status()
self.update_depends_on()
@ -43,51 +38,42 @@ class Task(NestedSet):
self.validate_completed_on()
def validate_dates(self):
if (
self.exp_start_date
and self.exp_end_date
and getdate(self.exp_start_date) > getdate(self.exp_end_date)
):
frappe.throw(
_("{0} can not be greater than {1}").format(
frappe.bold("Expected Start Date"), frappe.bold("Expected End Date")
)
)
if (
self.act_start_date
and self.act_end_date
and getdate(self.act_start_date) > getdate(self.act_end_date)
):
frappe.throw(
_("{0} can not be greater than {1}").format(
frappe.bold("Actual Start Date"), frappe.bold("Actual End Date")
)
)
self.validate_from_to_dates("exp_start_date", "exp_end_date")
self.validate_from_to_dates("act_start_date", "act_end_date")
self.validate_parent_expected_end_date()
self.validate_parent_project_dates()
def validate_parent_expected_end_date(self):
if self.parent_task:
parent_exp_end_date = frappe.db.get_value("Task", self.parent_task, "exp_end_date")
if parent_exp_end_date and getdate(self.get("exp_end_date")) > getdate(parent_exp_end_date):
frappe.throw(
_(
"Expected End Date should be less than or equal to parent task's Expected End Date {0}."
).format(getdate(parent_exp_end_date))
)
if not self.parent_task or not self.exp_end_date:
return
parent_exp_end_date = frappe.db.get_value("Task", self.parent_task, "exp_end_date")
if not parent_exp_end_date:
return
if getdate(self.exp_end_date) > getdate(parent_exp_end_date):
frappe.throw(
_(
"Expected End Date should be less than or equal to parent task's Expected End Date {0}."
).format(format_date(parent_exp_end_date)),
frappe.exceptions.InvalidDates,
)
def validate_parent_project_dates(self):
if not self.project or frappe.flags.in_test:
return
expected_end_date = frappe.db.get_value("Project", self.project, "expected_end_date")
if expected_end_date:
validate_project_dates(
getdate(expected_end_date), self, "exp_start_date", "exp_end_date", "Expected"
)
validate_project_dates(
getdate(expected_end_date), self, "act_start_date", "act_end_date", "Actual"
)
if project_end_date := frappe.db.get_value("Project", self.project, "expected_end_date"):
project_end_date = getdate(project_end_date)
for fieldname in ("exp_start_date", "exp_end_date", "act_start_date", "act_end_date"):
task_date = self.get(fieldname)
if task_date and date_diff(project_end_date, getdate(task_date)) < 0:
frappe.throw(
_("Task's {0} cannot be after Project's Expected End Date.").format(
_(self.meta.get_label(fieldname))
),
frappe.exceptions.InvalidDates,
)
def validate_status(self):
if self.is_template and self.status != "Template":
@ -398,15 +384,3 @@ def add_multiple_tasks(data, parent):
def on_doctype_update():
frappe.db.add_index("Task", ["lft", "rgt"])
def validate_project_dates(project_end_date, task, task_start, task_end, actual_or_expected_date):
if task.get(task_start) and date_diff(project_end_date, getdate(task.get(task_start))) < 0:
frappe.throw(
_("Task's {0} Start Date cannot be after Project's End Date.").format(actual_or_expected_date)
)
if task.get(task_end) and date_diff(project_end_date, getdate(task.get(task_end))) < 0:
frappe.throw(
_("Task's {0} End Date cannot be after Project's End Date.").format(actual_or_expected_date)
)

View File

@ -333,8 +333,18 @@ $.extend(erpnext.utils, {
}
frappe.ui.form.make_quick_entry(doctype, null, null, new_doc);
});
}
},
// check if payments app is installed on site, if not warn user.
check_payments_app: () => {
if (frappe.boot.versions && !frappe.boot.versions.payments) {
const marketplace_link = '<a href="https://frappecloud.com/marketplace/apps/payments">Marketplace</a>'
const github_link = '<a href="https://github.com/frappe/payments/">GitHub</a>'
const msg = __("payments app is not installed. Please install it from {0} or {1}", [marketplace_link, github_link])
frappe.msgprint(msg);
}
},
});
erpnext.utils.select_alternate_items = function(opts) {

View File

@ -6,7 +6,7 @@ import json
import frappe
import frappe.defaults
from frappe import _, msgprint
from frappe import _, msgprint, qb
from frappe.contacts.address_and_contact import (
delete_contact_and_address,
load_address_and_contact,
@ -732,12 +732,15 @@ def make_address(args, is_primary_address=1):
@frappe.validate_and_sanitize_search_inputs
def get_customer_primary_contact(doctype, txt, searchfield, start, page_len, filters):
customer = filters.get("customer")
return frappe.db.sql(
"""
select `tabContact`.name from `tabContact`, `tabDynamic Link`
where `tabContact`.name = `tabDynamic Link`.parent and `tabDynamic Link`.link_name = %(customer)s
and `tabDynamic Link`.link_doctype = 'Customer'
and `tabContact`.name like %(txt)s
""",
{"customer": customer, "txt": "%%%s%%" % txt},
con = qb.DocType("Contact")
dlink = qb.DocType("Dynamic Link")
return (
qb.from_(con)
.join(dlink)
.on(con.name == dlink.parent)
.select(con.name, con.full_name, con.email_id)
.where((dlink.link_name == customer) & (con.name.like(f"%{txt}%")))
.run()
)

View File

@ -145,33 +145,10 @@ class Employee(NestedSet):
if self.date_of_birth and getdate(self.date_of_birth) > getdate(today()):
throw(_("Date of Birth cannot be greater than today."))
if (
self.date_of_birth
and self.date_of_joining
and getdate(self.date_of_birth) >= getdate(self.date_of_joining)
):
throw(_("Date of Joining must be greater than Date of Birth"))
elif (
self.date_of_retirement
and self.date_of_joining
and (getdate(self.date_of_retirement) <= getdate(self.date_of_joining))
):
throw(_("Date Of Retirement must be greater than Date of Joining"))
elif (
self.relieving_date
and self.date_of_joining
and (getdate(self.relieving_date) < getdate(self.date_of_joining))
):
throw(_("Relieving Date must be greater than or equal to Date of Joining"))
elif (
self.contract_end_date
and self.date_of_joining
and (getdate(self.contract_end_date) <= getdate(self.date_of_joining))
):
throw(_("Contract End Date must be greater than Date of Joining"))
self.validate_from_to_dates("date_of_birth", "date_of_joining")
self.validate_from_to_dates("date_of_joining", "date_of_retirement")
self.validate_from_to_dates("date_of_joining", "relieving_date")
self.validate_from_to_dates("date_of_joining", "contract_end_date")
def validate_email(self):
if self.company_email:

View File

@ -152,7 +152,7 @@ def get_parent_item_groups(item_group_name, from_item=False):
if from_item and frappe.request.environ.get("HTTP_REFERER"):
# base page after 'Home' will vary on Item page
last_page = frappe.request.environ["HTTP_REFERER"].split("/")[-1]
last_page = frappe.request.environ["HTTP_REFERER"].split("/")[-1].split("?")[0]
if last_page and last_page in ("shop-by-category", "all-products"):
base_nav_page_title = " ".join(last_page.split("-")).title()
base_nav_page = {"name": _(base_nav_page_title), "route": "/" + last_page}

View File

@ -48,7 +48,7 @@ def make_packing_list(doc):
update_packed_item_from_cancelled_doc(item_row, bundle_item, pi_row, doc)
if set_price_from_children: # create/update bundle item wise price dict
update_product_bundle_rate(parent_items_price, pi_row)
update_product_bundle_rate(parent_items_price, pi_row, item_row)
if parent_items_price:
set_product_bundle_rate_amount(doc, parent_items_price) # set price in bundle item
@ -247,7 +247,7 @@ def get_cancelled_doc_packed_item_details(old_packed_items):
return prev_doc_packed_items_map
def update_product_bundle_rate(parent_items_price, pi_row):
def update_product_bundle_rate(parent_items_price, pi_row, item_row):
"""
Update the price dict of Product Bundles based on the rates of the Items in the bundle.
@ -259,7 +259,7 @@ def update_product_bundle_rate(parent_items_price, pi_row):
if not rate:
parent_items_price[key] = 0.0
parent_items_price[key] += flt(pi_row.rate)
parent_items_price[key] += flt((pi_row.rate * pi_row.qty) / item_row.stock_qty)
def set_product_bundle_rate_amount(doc, parent_items_price):

View File

@ -126,8 +126,8 @@ class TestPackedItem(FrappeTestCase):
so.packed_items[1].rate = 200
so.save()
self.assertEqual(so.items[0].rate, 350)
self.assertEqual(so.items[0].amount, 700)
self.assertEqual(so.items[0].rate, 700)
self.assertEqual(so.items[0].amount, 1400)
def test_newly_mapped_doc_packed_items(self):
"Test impact on packed items in newly mapped DN from SO."

View File

@ -173,7 +173,9 @@ class PurchaseReceipt(BuyingController):
)
if (
cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate")) and not self.is_return
cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate"))
and not self.is_return
and not self.is_internal_supplier
):
self.validate_rate_with_reference_doc(
[["Purchase Order", "purchase_order", "purchase_order_item"]]

View File

@ -1545,6 +1545,7 @@ class StockEntry(StockController):
"reference_name": self.pro_doc.name,
"reference_doctype": self.pro_doc.doctype,
"qty_to_produce": (">", 0),
"batch_qty": ("=", 0),
}
fields = ["qty_to_produce as qty", "produced_qty", "name"]
@ -2238,14 +2239,14 @@ class StockEntry(StockController):
d.qty -= process_loss_dict[d.item_code][1]
def set_serial_no_batch_for_finished_good(self):
args = {}
serial_nos = ""
if self.pro_doc.serial_no:
self.get_serial_nos_for_fg(args)
serial_nos = self.get_serial_nos_for_fg()
for row in self.items:
if row.is_finished_item and row.item_code == self.pro_doc.production_item:
if args.get("serial_no"):
row.serial_no = "\n".join(args["serial_no"][0 : cint(row.qty)])
if serial_nos:
row.serial_no = "\n".join(serial_nos[0 : cint(row.qty)])
def get_serial_nos_for_fg(self, args):
fields = [
@ -2258,14 +2259,14 @@ class StockEntry(StockController):
filters = [
["Stock Entry", "work_order", "=", self.work_order],
["Stock Entry", "purpose", "=", "Manufacture"],
["Stock Entry", "docstatus", "=", 1],
["Stock Entry", "docstatus", "<", 2],
["Stock Entry Detail", "item_code", "=", self.pro_doc.production_item],
]
stock_entries = frappe.get_all("Stock Entry", fields=fields, filters=filters)
if self.pro_doc.serial_no:
args["serial_no"] = self.get_available_serial_nos(stock_entries)
return self.get_available_serial_nos(stock_entries)
def get_available_serial_nos(self, stock_entries):
used_serial_nos = []

View File

@ -0,0 +1,20 @@
// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Warehouse Wise Stock Balance"] = {
"filters": [
{
"fieldname":"company",
"label": __("Company"),
"fieldtype": "Link",
"options": "Company",
"reqd": 1,
"default": frappe.defaults.get_user_default("Company")
}
],
"initial_depth": 3,
"tree": true,
"parent_field": "parent_warehouse",
"name_field": "warehouse"
};

View File

@ -0,0 +1,30 @@
{
"add_total_row": 0,
"columns": [],
"creation": "2022-12-06 14:15:31.924345",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 0,
"is_standard": "Yes",
"json": "{}",
"modified": "2022-12-06 14:16:55.969214",
"modified_by": "Administrator",
"module": "Stock",
"name": "Warehouse Wise Stock Balance",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Stock Ledger Entry",
"report_name": "Warehouse Wise Stock Balance",
"report_type": "Script Report",
"roles": [
{
"role": "Stock User"
},
{
"role": "Accounts Manager"
}
]
}

View File

@ -0,0 +1,103 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from typing import Any, Dict, List, Optional, TypedDict
import frappe
from frappe import _
from frappe.query_builder.functions import Sum
class StockBalanceFilter(TypedDict):
company: Optional[str]
warehouse: Optional[str]
SLEntry = Dict[str, Any]
def execute(filters=None):
columns, data = [], []
columns = get_columns()
data = get_data(filters)
return columns, data
def get_warehouse_wise_balance(filters: StockBalanceFilter) -> List[SLEntry]:
sle = frappe.qb.DocType("Stock Ledger Entry")
query = (
frappe.qb.from_(sle)
.select(sle.warehouse, Sum(sle.stock_value_difference).as_("stock_balance"))
.where((sle.docstatus < 2) & (sle.is_cancelled == 0))
.groupby(sle.warehouse)
)
if filters.get("company"):
query = query.where(sle.company == filters.get("company"))
data = query.run(as_list=True)
return frappe._dict(data) if data else frappe._dict()
def get_warehouses(report_filters: StockBalanceFilter):
return frappe.get_all(
"Warehouse",
fields=["name", "parent_warehouse", "is_group"],
filters={"company": report_filters.company},
order_by="lft",
)
def get_data(filters: StockBalanceFilter):
warehouse_balance = get_warehouse_wise_balance(filters)
warehouses = get_warehouses(filters)
for warehouse in warehouses:
warehouse.stock_balance = warehouse_balance.get(warehouse.name, 0) or 0.0
update_indent(warehouses)
set_balance_in_parent(warehouses)
return warehouses
def update_indent(warehouses):
for warehouse in warehouses:
def add_indent(warehouse, indent):
warehouse.indent = indent
for child in warehouses:
if child.parent_warehouse == warehouse.name:
add_indent(child, indent + 1)
if warehouse.is_group:
add_indent(warehouse, warehouse.indent or 0)
def set_balance_in_parent(warehouses):
# sort warehouses by indent in descending order
warehouses = sorted(warehouses, key=lambda x: x.get("indent", 0), reverse=1)
for warehouse in warehouses:
def update_balance(warehouse, balance):
for parent in warehouses:
if warehouse.parent_warehouse == parent.name:
parent.stock_balance += balance
update_balance(warehouse, warehouse.stock_balance)
def get_columns():
return [
{
"label": _("Warehouse"),
"fieldname": "name",
"fieldtype": "Link",
"options": "Warehouse",
"width": 200,
},
{"label": _("Stock Balance"), "fieldname": "stock_balance", "fieldtype": "Float", "width": 150},
]

View File

@ -5,7 +5,7 @@
"label": "Warehouse wise Stock Value"
}
],
"content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Stock\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Warehouse wise Stock Value\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Quick Access</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Material Request\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Receipt\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Delivery Note\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Ledger\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Balance\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Masters & Reports</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Items and Pricing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock Transactions\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Serial No and Batch\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]",
"content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Stock\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Warehouse wise Stock Value\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Quick Access</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Material Request\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Receipt\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Delivery Note\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Ledger\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Balance\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Masters &amp; Reports</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Items and Pricing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock Transactions\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Serial No and Batch\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]",
"creation": "2020-03-02 15:43:10.096528",
"docstatus": 0,
"doctype": "Workspace",
@ -207,80 +207,6 @@
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Stock Reports",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Stock Ledger",
"link_count": 0,
"link_to": "Stock Ledger",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Stock Balance",
"link_count": 0,
"link_to": "Stock Balance",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Stock Projected Qty",
"link_count": 0,
"link_to": "Stock Projected Qty",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 0,
"label": "Stock Summary",
"link_count": 0,
"link_to": "stock-balance",
"link_type": "Page",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Stock Ageing",
"link_count": 0,
"link_to": "Stock Ageing",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Item Price Stock",
"link_count": 0,
"link_to": "Item Price Stock",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
@ -705,15 +631,100 @@
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Stock Reports",
"link_count": 7,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Stock Ledger",
"link_count": 0,
"link_to": "Stock Ledger",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Stock Balance",
"link_count": 0,
"link_to": "Stock Balance",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Stock Projected Qty",
"link_count": 0,
"link_to": "Stock Projected Qty",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 0,
"label": "Stock Summary",
"link_count": 0,
"link_to": "stock-balance",
"link_type": "Page",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Stock Ageing",
"link_count": 0,
"link_to": "Stock Ageing",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Item Price Stock",
"link_count": 0,
"link_to": "Item Price Stock",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Warehouse Wise Stock Balance",
"link_count": 0,
"link_to": "Warehouse Wise Stock Balance",
"link_type": "Report",
"onboard": 0,
"type": "Link"
}
],
"modified": "2022-01-13 17:47:38.339931",
"modified": "2022-12-06 17:03:56.397272",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock",
"owner": "Administrator",
"parent_page": "",
"public": 1,
"quick_lists": [],
"restrict_to_domain": "",
"roles": [],
"sequence_id": 24.0,

View File

@ -9914,3 +9914,4 @@ Cost and Freight,Kosten und Fracht,
Delivered at Place,Geliefert benannter Ort,
Delivered at Place Unloaded,Geliefert benannter Ort entladen,
Delivered Duty Paid,Geliefert verzollt,
{0} of {1} {2} ignored for item {3} because you have {4} role,"{0} von Artikel {3} mit {1} {2} wurde ignoriert, weil Sie die Rolle {4} haben."

Can't render this file because it is too large.

View File

@ -453,11 +453,11 @@ Cancel Subscription,取消订阅,
Cancel the journal entry {0} first,首先取消日记条目{0},
Canceled,取消,
"Cannot Submit, Employees left to mark attendance",无法提交,不能为已离职员工登记考勤,
Cannot be a fixed asset item as Stock Ledger is created.,不能成为股票分类账创建的固定资产项目。,
Cannot be a fixed asset item as Stock Ledger is created.,不能成为库存分类账创建的固定资产项目。,
Cannot cancel because submitted Stock Entry {0} exists,不能取消,因为提交的仓储记录{0}已经存在,
Cannot cancel transaction for Completed Work Order.,无法取消已完成工单的交易。,
Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3},无法取消{0} {1},因为序列号{2}不属于仓库{3},
Cannot change Attributes after stock transaction. Make a new Item and transfer stock to the new Item,股票交易后不能更改属性。创建一个新项目并将库存转移到新项目,
Cannot change Attributes after stock transaction. Make a new Item and transfer stock to the new Item,库存交易后不能更改属性。创建一个新项目并将库存转移到新项目,
Cannot change Fiscal Year Start Date and Fiscal Year End Date once the Fiscal Year is saved.,财年保存后便不能更改财年开始日期和结束日期,
Cannot change Service Stop Date for item in row {0},无法更改行{0}中项目的服务停止日期,
Cannot change Variant properties after stock transaction. You will have to make a new Item to do this.,存货业务发生后不能更改变体物料的属性。需要新建新物料。,
@ -2309,7 +2309,7 @@ Receivable Account,应收账款,
Received,收到,
Received On,收到的,
Received Quantity,收到的数量,
Received Stock Entries,收到的股票条目,
Received Stock Entries,收到的库存条目,
Receiver List is empty. Please create Receiver List,接收人列表为空。请创建接收人列表,
Recipients,收件人,
Reconcile,核消(对帐),
@ -2783,7 +2783,7 @@ State,州,
State/UT Tax,州/ UT税,
Statement of Account,对账单,
Status must be one of {0},状态必须是{0}中的一个,
Stock,股票,
Stock,库存,
Stock Adjustment,库存调整,
Stock Analytics,库存分析,
Stock Assets,库存资产,
@ -2963,7 +2963,7 @@ The folio numbers are not matching,作品集编号不匹配,
The holiday on {0} is not between From Date and To Date,在{0}这个节日之间没有从日期和结束日期,
The name of the institute for which you are setting up this system.,对于要为其建立这个系统的该机构的名称。,
The name of your company for which you are setting up this system.,贵公司的名称,
The number of shares and the share numbers are inconsistent,股份数量和股票数量不一致,
The number of shares and the share numbers are inconsistent,股份数量和库存数量不一致,
The payment gateway account in plan {0} is different from the payment gateway account in this payment request,计划{0}中的支付网关帐户与此付款请求中的支付网关帐户不同,
The selected BOMs are not for the same item,所选物料清单不能用于同一个物料,
The selected item cannot have Batch,所选物料不能有批次,
@ -3514,7 +3514,7 @@ or,或,
Ageing Range 4,老化范围4,
Allocated amount cannot be greater than unadjusted amount,分配的金额不能大于未调整的金额,
Allocated amount cannot be negative,分配数量不能为负数,
"Difference Account must be a Asset/Liability type account, since this Stock Entry is an Opening Entry",差异账户必须是资产/负债类型账户,因为此股票分录是开仓分录,
"Difference Account must be a Asset/Liability type account, since this Stock Entry is an Opening Entry",差异账户必须是资产/负债类型账户,因为此库存分录是开仓分录,
Error in some rows,某些行出错,
Import Successful,导入成功,
Please save first,请先保存,
@ -3531,7 +3531,7 @@ Duplicate entry against the item code {0} and manufacturer {1},项目代码{0}
Invalid GSTIN! The input you've entered doesn't match the GSTIN format for UIN Holders or Non-Resident OIDAR Service Providers,GSTIN无效您输入的输入与UIN持有人或非居民OIDAR服务提供商的GSTIN格式不符,
Invoice Grand Total,发票总计,
Last carbon check date cannot be a future date,最后的碳检查日期不能是未来的日期,
Make Stock Entry,进入股票,
Make Stock Entry,进入库存,
Quality Feedback,质量反馈,
Quality Feedback Template,质量反馈模板,
Rules for applying different promotional schemes.,适用不同促销计划的规则。,
@ -3626,7 +3626,7 @@ BOM 2,BOM 2,
BOM Comparison Tool,BOM比较工具,
BOM recursion: {0} cannot be child of {1},BOM递归{0}不能是{1}的子代,
BOM recursion: {0} cannot be parent or child of {1},BOM递归{0}不能是{1}的父级或子级,
Back to Home,回到,
Back to Home,回到主页,
Back to Messages,回到消息,
Bank Data mapper doesn't exist,银行数据映射器不存在,
Bank Details,银行明细,
@ -3786,7 +3786,7 @@ Help,帮助,
Help Article,帮助文章,
"Helps you keep tracks of Contracts based on Supplier, Customer and Employee",帮助您根据供应商,客户和员工记录合同,
Helps you manage appointments with your leads,帮助您管理潜在客户的约会,
Home,,
Home,主页,
IBAN is not valid,IBAN无效,
Import Data from CSV / Excel files.,从CSV / Excel文件导入数据。,
In Progress,进行中,
@ -4064,8 +4064,8 @@ Start Time,开始时间,
Status,状态,
Status must be Cancelled or Completed,状态必须已取消或已完成,
Stock Balance Report,库存余额报告,
Stock Entry has been already created against this Pick List,已经根据此选择列表创建了股票输入,
Stock Ledger ID,股票分类帐编号,
Stock Entry has been already created against this Pick List,已经根据此选择列表创建了库存输入,
Stock Ledger ID,库存分类帐编号,
Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses.,库存值({0})和帐户余额({1})与帐户{2}及其链接的仓库不同步。,
Stores - {0},商店-{0},
Student with email {0} does not exist,电子邮件{0}的学生不存在,
@ -6127,7 +6127,7 @@ Procedure Template,程序模板,
Procedure Prescription,程序处方,
Service Unit,服务单位,
Consumables,耗材,
Consume Stock,消费股票,
Consume Stock,消费库存,
Invoice Consumables Separately,发票耗材分开,
Consumption Invoiced,消费发票,
Consumable Total Amount,耗材总量,
@ -8285,8 +8285,8 @@ Out of AMC,出资产管理公司,
Warranty Period (Days),保修期限(天数),
Serial No Details,序列号信息,
MAT-STE-.YYYY.-,MAT-STE-.YYYY.-,
Stock Entry Type,股票进入类型,
Stock Entry (Outward GIT),股票进入外向GIT,
Stock Entry Type,库存进入类型,
Stock Entry (Outward GIT),库存进入外向GIT,
Material Consumption for Manufacture,生产所需的材料消耗,
Repack,包装,
Send to Subcontractor,发送给分包商,
@ -8318,8 +8318,8 @@ Serial No / Batch,序列号/批次,
BOM No. for a Finished Good Item,成品物料的物料清单编号,
Material Request used to make this Stock Entry,创建此手工库存移动的材料申请,
Subcontracted Item,外包物料,
Against Stock Entry,反对股票进入,
Stock Entry Child,股票入境儿童,
Against Stock Entry,反对库存进入,
Stock Entry Child,库存入境儿童,
PO Supplied Item,PO提供的物品,
Reference Purchase Receipt,参考购买收据,
Stock Ledger Entry,库存分类帐分录,
@ -8571,7 +8571,7 @@ Serial No Service Contract Expiry,序列号/年度保养合同过期,
Serial No Status,序列号状态,
Serial No Warranty Expiry,序列号/保修到期,
Stock Ageing,库存账龄,
Stock and Account Value Comparison,股票和账户价值比较,
Stock and Account Value Comparison,库存和账户价值比较,
Stock Projected Qty,预期可用库存,
Student and Guardian Contact Details,学生和监护人联系方式,
Student Batch-Wise Attendance,学生按批考勤,
@ -9655,7 +9655,7 @@ Raise Material Request When Stock Reaches Re-order Level,库存达到再订购
Notify by Email on Creation of Automatic Material Request,通过电子邮件通知创建自动物料请求,
Allow Material Transfer from Delivery Note to Sales Invoice,允许物料从交货单转移到销售发票,
Allow Material Transfer from Purchase Receipt to Purchase Invoice,允许从收货到采购发票的物料转移,
Freeze Stocks Older Than (Days),冻结大于(天)的股票,
Freeze Stocks Older Than (Days),冻结大于(天)的库存,
Role Allowed to Edit Frozen Stock,允许角色编辑冻结库存,
The unallocated amount of Payment Entry {0} is greater than the Bank Transaction's unallocated amount,付款条目{0}的未分配金额大于银行交易的未分配金额,
Payment Received,已收到付款,
@ -9698,7 +9698,7 @@ Creating {} out of {} {},在{} {}中创建{},
Item {0} {1},项目{0} {1},
Last Stock Transaction for item {0} under warehouse {1} was on {2}.,仓库{1}下项目{0}的上次库存交易在{2}上。,
Stock Transactions for Item {0} under warehouse {1} cannot be posted before this time.,在此之前,不能过帐仓库{1}下物料{0}的库存交易。,
Posting future stock transactions are not allowed due to Immutable Ledger,由于总帐不可变,不允许过帐未来的股票交易,
Posting future stock transactions are not allowed due to Immutable Ledger,由于总帐不可变,不允许过帐未来的库存交易,
A BOM with name {0} already exists for item {1}.,项目{1}的名称为{0}的BOM已存在。,
{0}{1} Did you rename the item? Please contact Administrator / Tech support,{0} {1}您是否重命名了该项目?请联系管理员/技术支持,
At row #{0}: the sequence id {1} cannot be less than previous row sequence id {2},在第{0}行序列ID {1}不能小于上一行的序列ID {2},

Can't render this file because it is too large.

View File

@ -1,6 +1,9 @@
## temp utility
from contextlib import contextmanager
import frappe
from frappe import _
from frappe.utils import cstr
from erpnext.utilities.activation import get_level
@ -35,3 +38,16 @@ def get_site_info(site_info):
domain = frappe.get_cached_value("Company", cstr(company), "domain")
return {"company": company, "domain": domain, "activation": get_level()}
@contextmanager
def payment_app_import_guard():
marketplace_link = '<a href="https://frappecloud.com/marketplace/apps/payments">Marketplace</a>'
github_link = '<a href="https://github.com/frappe/payments/">GitHub</a>'
msg = _("payments app is not installed. Please install it from {} or {}").format(
marketplace_link, github_link
)
try:
yield
except ImportError:
frappe.throw(msg, title=_("Missing Payments App"))