Merge branch 'develop' into barredterra-patch-1

This commit is contained in:
Raffael Meyer
2020-08-12 16:14:55 +02:00
committed by GitHub
68 changed files with 1359 additions and 254 deletions

View File

@@ -31,8 +31,7 @@ frappe.ui.form.on('POS Profile', {
frm.set_query("print_format", function() { frm.set_query("print_format", function() {
return { return {
filters: [ filters: [
['Print Format', 'doc_type', '=', 'Sales Invoice'], ['Print Format', 'doc_type', '=', 'POS Invoice']
['Print Format', 'print_format_type', '=', 'Jinja'],
] ]
}; };
}); });
@@ -45,10 +44,6 @@ frappe.ui.form.on('POS Profile', {
}; };
}); });
frm.set_query("print_format", function() {
return { filters: { doc_type: "Sales Invoice", print_format_type: "JS"} };
});
frm.set_query('company_address', function(doc) { frm.set_query('company_address', function(doc) {
if(!doc.company) { if(!doc.company) {
frappe.throw(__('Please set Company')); frappe.throw(__('Please set Company'));

View File

@@ -0,0 +1,89 @@
<h1 class="text-center" style="page-break-before:always">{{ filters.party[0] }}</h1>
<h3 class="text-center">{{ _("Statement of Accounts") }}</h3>
<h5 class="text-center">
{{ frappe.format(filters.from_date, 'Date')}}
{{ _("to") }}
{{ frappe.format(filters.to_date, 'Date')}}
</h5>
<table class="table table-bordered">
<thead>
<tr>
<th style="width: 12%">{{ _("Date") }}</th>
<th style="width: 15%">{{ _("Ref") }}</th>
<th style="width: 25%">{{ _("Party") }}</th>
<th style="width: 15%">{{ _("Debit") }}</th>
<th style="width: 15%">{{ _("Credit") }}</th>
<th style="width: 18%">{{ _("Balance (Dr - Cr)") }}</th>
</tr>
</thead>
<tbody>
{% for row in data %}
<tr>
{% if(row.posting_date) %}
<td>{{ frappe.format(row.posting_date, 'Date') }}</td>
<td>{{ row.voucher_type }}
<br>{{ row.voucher_no }}</td>
<td>
{% if not (filters.party or filters.account) %}
{{ row.party or row.account }}
<br>
{% endif %}
{{ _("Against") }}: {{ row.against }}
<br>{{ _("Remarks") }}: {{ row.remarks }}
{% if row.bill_no %}
<br>{{ _("Supplier Invoice No") }}: {{ row.bill_no }}
{% endif %}
</td>
<td style="text-align: right">
{{ frappe.utils.fmt_money(row.debit, filters.presentation_currency) }}</td>
<td style="text-align: right">
{{ frappe.utils.fmt_money(row.credit, filters.presentation_currency) }}</td>
{% else %}
<td></td>
<td></td>
<td><b>{{ frappe.format(row.account, {fieldtype: "Link"}) or "&nbsp;" }}</b></td>
<td style="text-align: right">
{{ row.account and frappe.utils.fmt_money(row.debit, filters.presentation_currency) }}
</td>
<td style="text-align: right">
{{ row.account and frappe.utils.fmt_money(row.credit, filters.presentation_currency) }}
</td>
{% endif %}
<td style="text-align: right">
{{ frappe.utils.fmt_money(row.balance, filters.presentation_currency) }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<br><br>
{% if aging %}
<h3 class="text-center">{{ _("Ageing Report Based On ") }} {{ aging.ageing_based_on }}</h3>
<h5 class="text-center">
{{ _("Up to " ) }} {{ frappe.format(filters.to_date, 'Date')}}
</h5>
<br>
<table class="table table-bordered">
<thead>
<tr>
<th style="width: 12%">30 Days</th>
<th style="width: 15%">60 Days</th>
<th style="width: 25%">90 Days</th>
<th style="width: 15%">120 Days</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ aging.range1 }}</td>
<td>{{ aging.range2 }}</td>
<td>{{ aging.range3 }}</td>
<td>{{ aging.range4 }}</td>
</tr>
</tbody>
</table>
{% endif %}
<p class="text-right text-muted">Printed On {{ frappe.format(frappe.utils.get_datetime(), 'Datetime') }}</p>

View File

@@ -0,0 +1,132 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Process Statement Of Accounts', {
view_properties: function(frm) {
frappe.route_options = {doc_type: 'Customer'};
frappe.set_route("Form", "Customize Form");
},
refresh: function(frm){
if(!frm.doc.__islocal) {
frm.add_custom_button('Send Emails',function(){
frappe.call({
method: "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_emails",
args: {
"document_name": frm.doc.name,
},
callback: function(r) {
if(r && r.message) {
frappe.show_alert({message: __('Emails Queued'), indicator: 'blue'});
}
else{
frappe.msgprint('No Records for these settings.')
}
}
});
});
frm.add_custom_button('Download',function(){
var url = frappe.urllib.get_full_url(
'/api/method/erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.download_statements?'
+ 'document_name='+encodeURIComponent(frm.doc.name))
$.ajax({
url: url,
type: 'GET',
success: function(result) {
if(jQuery.isEmptyObject(result)){
frappe.msgprint('No Records for these settings.');
}
else{
window.location = url;
}
}
});
});
}
},
onload: function(frm) {
frm.set_query('currency', function(){
return {
filters: {
'enabled': 1
}
}
});
if(frm.doc.__islocal){
frm.set_value('from_date', frappe.datetime.add_months(frappe.datetime.get_today(), -1));
frm.set_value('to_date', frappe.datetime.get_today());
}
},
customer_collection: function(frm){
frm.set_value('collection_name', '');
if(frm.doc.customer_collection){
frm.get_field('collection_name').set_label(frm.doc.customer_collection);
}
},
frequency: function(frm){
if(frm.doc.frequency != ''){
frm.set_value('start_date', frappe.datetime.get_today());
}
else{
frm.set_value('start_date', '');
}
},
fetch_customers: function(frm){
if(frm.doc.collection_name){
frappe.call({
method: "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.fetch_customers",
args: {
'customer_collection': frm.doc.customer_collection,
'collection_name': frm.doc.collection_name,
'primary_mandatory': frm.doc.primary_mandatory
},
callback: function(r) {
if(!r.exc) {
if(r.message.length){
frm.clear_table('customers');
for (const customer of r.message){
var row = frm.add_child('customers');
row.customer = customer.name;
row.primary_email = customer.primary_email;
row.billing_email = customer.billing_email;
}
frm.refresh_field('customers');
}
else{
frappe.msgprint('No Customers found with selected options.');
}
}
}
});
}
else {
frappe.throw('Enter ' + frm.doc.customer_collection + ' name.');
}
}
});
frappe.ui.form.on('Process Statement Of Accounts Customer', {
customer: function(frm, cdt, cdn){
var row = locals[cdt][cdn];
if (!row.customer){
return;
}
frappe.call({
method: 'erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.get_customer_emails',
args: {
'customer_name': row.customer,
'primary_mandatory': frm.doc.primary_mandatory
},
callback: function(r){
if(!r.exe){
if(r.message.length){
frappe.model.set_value(cdt, cdn, "primary_email", r.message[0])
frappe.model.set_value(cdt, cdn, "billing_email", r.message[1])
}
else {
return
}
}
}
})
}
});

View File

@@ -0,0 +1,310 @@
{
"actions": [],
"allow_workflow": 1,
"autoname": "Prompt",
"creation": "2020-05-22 16:46:18.712954",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"section_break_11",
"from_date",
"company",
"account",
"group_by",
"cost_center",
"column_break_14",
"to_date",
"finance_book",
"currency",
"project",
"section_break_3",
"customer_collection",
"collection_name",
"fetch_customers",
"column_break_6",
"primary_mandatory",
"column_break_17",
"customers",
"preferences",
"orientation",
"section_break_14",
"include_ageing",
"ageing_based_on",
"section_break_1",
"enable_auto_email",
"section_break_18",
"frequency",
"filter_duration",
"column_break_21",
"start_date",
"section_break_33",
"subject",
"column_break_28",
"cc_to",
"section_break_30",
"body",
"help_text"
],
"fields": [
{
"fieldname": "frequency",
"fieldtype": "Select",
"label": "Frequency",
"options": "Weekly\nMonthly\nQuarterly"
},
{
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company",
"options": "Company",
"reqd": 1
},
{
"depends_on": "eval:doc.enable_auto_email == 0;",
"fieldname": "from_date",
"fieldtype": "Date",
"label": "From Date",
"mandatory_depends_on": "eval:doc.frequency == '';"
},
{
"depends_on": "eval:doc.enable_auto_email == 0;",
"fieldname": "to_date",
"fieldtype": "Date",
"label": "To Date",
"mandatory_depends_on": "eval:doc.frequency == '';"
},
{
"fieldname": "cost_center",
"fieldtype": "Table MultiSelect",
"label": "Cost Center",
"options": "PSOA Cost Center"
},
{
"fieldname": "project",
"fieldtype": "Table MultiSelect",
"label": "Project",
"options": "PSOA Project"
},
{
"fieldname": "section_break_3",
"fieldtype": "Section Break",
"label": "Customers"
},
{
"fieldname": "column_break_6",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_11",
"fieldtype": "Section Break",
"label": "General Ledger Filters"
},
{
"fieldname": "column_break_14",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_17",
"fieldtype": "Section Break",
"hide_border": 1
},
{
"fieldname": "customer_collection",
"fieldtype": "Select",
"label": "Select Customers By",
"options": "\nCustomer Group\nTerritory\nSales Partner\nSales Person"
},
{
"depends_on": "eval: doc.customer_collection !== ''",
"fieldname": "collection_name",
"fieldtype": "Dynamic Link",
"label": "Recipient",
"options": "customer_collection"
},
{
"fieldname": "section_break_1",
"fieldtype": "Section Break",
"label": "Email Settings"
},
{
"fieldname": "account",
"fieldtype": "Link",
"label": "Account",
"options": "Account"
},
{
"fieldname": "finance_book",
"fieldtype": "Link",
"label": "Finance Book",
"options": "Finance Book"
},
{
"fieldname": "preferences",
"fieldtype": "Section Break",
"label": "Print Preferences"
},
{
"fieldname": "orientation",
"fieldtype": "Select",
"label": "Orientation",
"options": "Landscape\nPortrait"
},
{
"default": "Today",
"fieldname": "start_date",
"fieldtype": "Date",
"label": "Start Date"
},
{
"default": "Group by Voucher (Consolidated)",
"fieldname": "group_by",
"fieldtype": "Select",
"label": "Group By",
"options": "\nGroup by Voucher\nGroup by Voucher (Consolidated)"
},
{
"fieldname": "currency",
"fieldtype": "Link",
"label": "Currency",
"options": "Currency"
},
{
"default": "0",
"fieldname": "include_ageing",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Include Ageing Summary"
},
{
"default": "Due Date",
"depends_on": "eval:doc.include_ageing === 1",
"fieldname": "ageing_based_on",
"fieldtype": "Select",
"label": "Ageing Based On",
"options": "Due Date\nPosting Date"
},
{
"default": "0",
"fieldname": "enable_auto_email",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Enable Auto Email"
},
{
"fieldname": "section_break_14",
"fieldtype": "Column Break",
"hide_border": 1
},
{
"depends_on": "eval: doc.enable_auto_email ==1",
"fieldname": "section_break_18",
"fieldtype": "Section Break",
"hide_border": 1
},
{
"fieldname": "column_break_21",
"fieldtype": "Column Break"
},
{
"depends_on": "eval: doc.customer_collection !== ''",
"fieldname": "fetch_customers",
"fieldtype": "Button",
"label": "Fetch Customers",
"options": "fetch_customers",
"print_hide": 1,
"report_hide": 1
},
{
"default": "1",
"fieldname": "primary_mandatory",
"fieldtype": "Check",
"label": "Send To Primary Contact"
},
{
"fieldname": "cc_to",
"fieldtype": "Link",
"label": "CC To",
"options": "User"
},
{
"default": "1",
"fieldname": "filter_duration",
"fieldtype": "Int",
"label": "Filter Duration (Months)"
},
{
"fieldname": "customers",
"fieldtype": "Table",
"label": "Customers",
"options": "Process Statement Of Accounts Customer",
"reqd": 1
},
{
"fieldname": "column_break_28",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_30",
"fieldtype": "Section Break",
"hide_border": 1
},
{
"fieldname": "section_break_33",
"fieldtype": "Section Break",
"hide_border": 1
},
{
"fieldname": "help_text",
"fieldtype": "HTML",
"label": "Help Text",
"options": "<br>\n<h4>Note</h4>\n<ul>\n<li>\nYou can use <a href=\"https://jinja.palletsprojects.com/en/2.11.x/\" target=\"_blank\">Jinja tags</a> in <b>Subject</b> and <b>Body</b> fields for dynamic values.\n</li><li>\n All fields in this doctype are available under the <b>doc</b> object and all fields for the customer to whom the mail will go to is available under the <b>customer</b> object.\n</li></ul>\n<h4> Examples</h4>\n<!-- {% raw %} -->\n<ul>\n <li><b>Subject</b>:<br><br><pre><code>Statement Of Accounts for {{ customer.name }}</code></pre><br></li>\n <li><b>Body</b>: <br><br>\n<pre><code>Hello {{ customer.name }},<br>PFA your Statement Of Accounts from {{ doc.from_date }} to {{ doc.to_date }}.</code> </pre></li>\n</ul>\n<!-- {% endraw %} -->"
},
{
"fieldname": "subject",
"fieldtype": "Data",
"label": "Subject"
},
{
"fieldname": "body",
"fieldtype": "Text Editor",
"label": "Body"
}
],
"links": [],
"modified": "2020-08-08 08:47:09.185728",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Process Statement Of Accounts",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,271 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from erpnext.accounts.report.general_ledger.general_ledger import execute as get_soa
from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import execute as get_ageing
from frappe.core.doctype.communication.email import make
from frappe.utils.print_format import report_to_pdf
from frappe.utils.pdf import get_pdf
from frappe.utils import today, add_days, add_months, getdate, format_date
from frappe.utils.jinja import validate_template
import copy
from datetime import timedelta
from frappe.www.printview import get_print_style
class ProcessStatementOfAccounts(Document):
def validate(self):
if not self.subject:
self.subject = 'Statement Of Accounts for {{ customer.name }}'
if not self.body:
self.body = 'Hello {{ customer.name }},<br>PFA your Statement Of Accounts from {{ doc.from_date }} to {{ doc.to_date }}.'
validate_template(self.subject)
validate_template(self.body)
if not self.customers:
frappe.throw(frappe._('Customers not selected.'))
if self.enable_auto_email:
self.to_date = self.start_date
self.from_date = add_months(self.to_date, -1 * self.filter_duration)
def get_report_pdf(doc, consolidated=True):
statement_dict = {}
aging = ''
base_template_path = "frappe/www/printview.html"
template_path = "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html"
for entry in doc.customers:
if doc.include_ageing:
ageing_filters = frappe._dict({
'company': doc.company,
'report_date': doc.to_date,
'ageing_based_on': doc.ageing_based_on,
'range1': 30,
'range2': 60,
'range3': 90,
'range4': 120,
'customer': entry.customer
})
col1, aging = get_ageing(ageing_filters)
aging[0]['ageing_based_on'] = doc.ageing_based_on
tax_id = frappe.get_doc('Customer', entry.customer).tax_id
filters= frappe._dict({
'from_date': doc.from_date,
'to_date': doc.to_date,
'company': doc.company,
'finance_book': doc.finance_book if doc.finance_book else None,
"account": doc.account if doc.account else None,
'party_type': 'Customer',
'party': [entry.customer],
'group_by': doc.group_by,
'currency': doc.currency,
'cost_center': [cc.cost_center_name for cc in doc.cost_center],
'project': [p.project_name for p in doc.project],
'show_opening_entries': 0,
'include_default_book_entries': 0,
'show_cancelled_entries': 1,
'tax_id': tax_id if tax_id else None
})
col, res = get_soa(filters)
for x in [0, -2, -1]:
res[x]['account'] = res[x]['account'].replace("'","")
if len(res) == 3:
continue
html = frappe.render_template(template_path, \
{"filters": filters, "data": res, "aging": aging[0] if doc.include_ageing else None})
html = frappe.render_template(base_template_path, {"body": html, \
"css": get_print_style(), "title": "Statement For " + entry.customer})
statement_dict[entry.customer] = html
if not bool(statement_dict):
return False
elif consolidated:
result = ''.join(list(statement_dict.values()))
return get_pdf(result, {'orientation': doc.orientation})
else:
for customer, statement_html in statement_dict.items():
statement_dict[customer]=get_pdf(statement_html, {'orientation': doc.orientation})
return statement_dict
def get_customers_based_on_territory_or_customer_group(customer_collection, collection_name):
fields_dict = {
'Customer Group': 'customer_group',
'Territory': 'territory',
}
collection = frappe.get_doc(customer_collection, collection_name)
selected = [customer.name for customer in frappe.get_list(customer_collection, filters=[
['lft', '>=', collection.lft],
['rgt', '<=', collection.rgt]
],
fields=['name'],
order_by='lft asc, rgt desc'
)]
return frappe.get_list('Customer', fields=['name', 'email_id'], \
filters=[[fields_dict[customer_collection], 'IN', selected]])
def get_customers_based_on_sales_person(sales_person):
lft, rgt = frappe.db.get_value("Sales Person",
sales_person, ["lft", "rgt"])
records = frappe.db.sql("""
select distinct parent, parenttype
from `tabSales Team` steam
where parenttype = 'Customer'
and exists(select name from `tabSales Person` where lft >= %s and rgt <= %s and name = steam.sales_person)
""", (lft, rgt), as_dict=1)
sales_person_records = frappe._dict()
for d in records:
sales_person_records.setdefault(d.parenttype, set()).add(d.parent)
customers = frappe.get_list('Customer', fields=['name', 'email_id'], \
filters=[['name', 'in', list(sales_person_records['Customer'])]])
return customers
def get_recipients_and_cc(customer, doc):
recipients = []
for clist in doc.customers:
if clist.customer == customer:
recipients.append(clist.billing_email)
if doc.primary_mandatory and clist.primary_email:
recipients.append(clist.primary_email)
cc = []
if doc.cc_to != '':
try:
cc=[frappe.get_value('User', doc.cc_to, 'email')]
except:
pass
return recipients, cc
def get_context(customer, doc):
template_doc = copy.deepcopy(doc)
del template_doc.customers
template_doc.from_date = format_date(template_doc.from_date)
template_doc.to_date = format_date(template_doc.to_date)
return {
'doc': template_doc,
'customer': frappe.get_doc('Customer', customer),
'frappe': frappe.utils
}
@frappe.whitelist()
def fetch_customers(customer_collection, collection_name, primary_mandatory):
customer_list = []
customers = []
if customer_collection == 'Sales Person':
customers = get_customers_based_on_sales_person(collection_name)
if not bool(customers):
frappe.throw('No Customers found with selected options.')
else:
if customer_collection == 'Sales Partner':
customers = frappe.get_list('Customer', fields=['name', 'email_id'], \
filters=[['default_sales_partner', '=', collection_name]])
else:
customers = get_customers_based_on_territory_or_customer_group(customer_collection, collection_name)
for customer in customers:
primary_email = customer.get('email_id') or ''
billing_email = get_customer_emails(customer.name, 1, billing_and_primary=False)
if billing_email == '' or (primary_email == '' and int(primary_mandatory)):
continue
customer_list.append({
'name': customer.name,
'primary_email': primary_email,
'billing_email': billing_email
})
return customer_list
@frappe.whitelist()
def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=True):
billing_email = frappe.db.sql("""
SELECT c.email_id FROM `tabContact` AS c JOIN `tabDynamic Link` AS l ON c.name=l.parent \
WHERE l.link_doctype='Customer' and l.link_name='""" + customer_name + """' and \
c.is_billing_contact=1 \
order by c.creation desc""")
if len(billing_email) == 0 or (billing_email[0][0] is None):
if billing_and_primary:
frappe.throw('No billing email found for customer: '+ customer_name)
else:
return ''
if billing_and_primary:
primary_email = frappe.get_value('Customer', customer_name, 'email_id')
if primary_email is None and int(primary_mandatory):
frappe.throw('No primary email found for customer: '+ customer_name)
return [primary_email or '', billing_email[0][0]]
else:
return billing_email[0][0] or ''
@frappe.whitelist()
def download_statements(document_name):
doc = frappe.get_doc('Process Statement Of Accounts', document_name)
report = get_report_pdf(doc)
if report:
frappe.local.response.filename = doc.name + '.pdf'
frappe.local.response.filecontent = report
frappe.local.response.type = "download"
@frappe.whitelist()
def send_emails(document_name, from_scheduler=False):
doc = frappe.get_doc('Process Statement Of Accounts', document_name)
report = get_report_pdf(doc, consolidated=False)
if report:
for customer, report_pdf in report.items():
attachments = [{
'fname': customer + '.pdf',
'fcontent': report_pdf
}]
recipients, cc = get_recipients_and_cc(customer, doc)
context = get_context(customer, doc)
subject = frappe.render_template(doc.subject, context)
message = frappe.render_template(doc.body, context)
frappe.enqueue(
queue='short',
method=frappe.sendmail,
recipients=recipients,
sender=frappe.session.user,
cc=cc,
subject=subject,
message=message,
now=True,
reference_doctype='Process Statement Of Accounts',
reference_name=document_name,
attachments=attachments
)
if doc.enable_auto_email and from_scheduler:
new_to_date = getdate(today())
if doc.frequency == 'Weekly':
new_to_date = add_days(new_to_date, 7)
else:
new_to_date = add_months(new_to_date, 1 if doc.frequency == 'Monthly' else 3)
new_from_date = add_months(new_to_date, -1 * doc.filter_duration)
doc.add_comment('Comment', 'Emails sent on: ' + frappe.utils.format_datetime(frappe.utils.now()))
doc.db_set('to_date', new_to_date, commit=True)
doc.db_set('from_date', new_from_date, commit=True)
return True
else:
return False
@frappe.whitelist()
def send_auto_email():
selected = frappe.get_list('Process Statement Of Accounts', filters={'to_date': format_date(today()), 'enable_auto_email': 1})
for entry in selected:
send_emails(entry.name, from_scheduler=True)
return True

View File

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestProcessStatementOfAccounts(unittest.TestCase):
pass

View File

@@ -0,0 +1,47 @@
{
"actions": [],
"allow_workflow": 1,
"creation": "2020-08-03 16:35:21.852178",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"customer",
"billing_email",
"primary_email"
],
"fields": [
{
"fieldname": "customer",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Customer",
"options": "Customer",
"reqd": 1
},
{
"fieldname": "primary_email",
"fieldtype": "Read Only",
"in_list_view": 1,
"label": "Primary Contact Email"
},
{
"fieldname": "billing_email",
"fieldtype": "Read Only",
"in_list_view": 1,
"label": "Billing Email"
}
],
"istable": 1,
"links": [],
"modified": "2020-08-03 22:55:38.875601",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Process Statement Of Accounts Customer",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class ProcessStatementOfAccountsCustomer(Document):
pass

View File

@@ -0,0 +1,30 @@
{
"actions": [],
"creation": "2020-08-03 16:56:45.744905",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"cost_center_name"
],
"fields": [
{
"fieldname": "cost_center_name",
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
}
],
"istable": 1,
"links": [],
"modified": "2020-08-03 16:56:45.744905",
"modified_by": "Administrator",
"module": "Accounts",
"name": "PSOA Cost Center",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class PSOACostCenter(Document):
pass

View File

@@ -0,0 +1,30 @@
{
"actions": [],
"creation": "2020-08-03 16:52:14.731978",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"project_name"
],
"fields": [
{
"fieldname": "project_name",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
}
],
"istable": 1,
"links": [],
"modified": "2020-08-03 16:53:39.219736",
"modified_by": "Administrator",
"module": "Accounts",
"name": "PSOA Project",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class PSOAProject(Document):
pass

View File

@@ -180,7 +180,7 @@
"no_copy": 1, "no_copy": 1,
"oldfieldname": "naming_series", "oldfieldname": "naming_series",
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "ACC-PINV-.YYYY.-", "options": "ACC-PINV-.YYYY.-\nACC-PINV-RET-.YYYY.-",
"print_hide": 1, "print_hide": 1,
"reqd": 1, "reqd": 1,
"set_only_once": 1 "set_only_once": 1
@@ -1334,7 +1334,7 @@
"idx": 204, "idx": 204,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-08-03 12:46:01.411074", "modified": "2020-08-03 23:20:04.466153",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice", "name": "Purchase Invoice",
@@ -1396,4 +1396,4 @@
"timeline_field": "supplier", "timeline_field": "supplier",
"title_field": "title", "title_field": "title",
"track_changes": 1 "track_changes": 1
} }

View File

@@ -1,7 +1,6 @@
{ {
"actions": [], "actions": [],
"allow_import": 1, "allow_import": 1,
"allow_workflow": 1,
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2013-05-24 19:29:05", "creation": "2013-05-24 19:29:05",
"doctype": "DocType", "doctype": "DocType",
@@ -217,7 +216,7 @@
"no_copy": 1, "no_copy": 1,
"oldfieldname": "naming_series", "oldfieldname": "naming_series",
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "ACC-SINV-.YYYY.-", "options": "ACC-SINV-.YYYY.-\nACC-SINV-RET-.YYYY.-",
"print_hide": 1, "print_hide": 1,
"reqd": 1, "reqd": 1,
"set_only_once": 1 "set_only_once": 1
@@ -1947,7 +1946,7 @@
"idx": 181, "idx": 181,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-07-18 05:07:16.725974", "modified": "2020-08-03 23:31:12.675040",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "name": "Sales Invoice",

View File

@@ -206,10 +206,19 @@ class TestSalesInvoice(unittest.TestCase):
"rate": 14, "rate": 14,
'included_in_print_rate': 1 'included_in_print_rate': 1
}) })
si.append("taxes", {
"charge_type": "On Item Quantity",
"account_head": "_Test Account Education Cess - _TC",
"cost_center": "_Test Cost Center - _TC",
"description": "CESS",
"rate": 5,
'included_in_print_rate': 1
})
si.insert() si.insert()
# with inclusive tax # with inclusive tax
self.assertEqual(si.net_total, 4385.96) self.assertEqual(si.items[0].net_amount, 3947.368421052631)
self.assertEqual(si.net_total, 3947.37)
self.assertEqual(si.grand_total, 5000) self.assertEqual(si.grand_total, 5000)
si.reload() si.reload()
@@ -222,8 +231,8 @@ class TestSalesInvoice(unittest.TestCase):
si.save() si.save()
# with inclusive tax and additional discount # with inclusive tax and additional discount
self.assertEqual(si.net_total, 4285.96) self.assertEqual(si.net_total, 3847.37)
self.assertEqual(si.grand_total, 4885.99) self.assertEqual(si.grand_total, 4886)
si.reload() si.reload()
@@ -235,7 +244,7 @@ class TestSalesInvoice(unittest.TestCase):
si.save() si.save()
# with inclusive tax and additional discount # with inclusive tax and additional discount
self.assertEqual(si.net_total, 4298.25) self.assertEqual(si.net_total, 3859.65)
self.assertEqual(si.grand_total, 4900.00) self.assertEqual(si.grand_total, 4900.00)
def test_sales_invoice_discount_amount(self): def test_sales_invoice_discount_amount(self):

View File

@@ -8,7 +8,7 @@ def get_data():
'fieldname': 'taxes_and_charges', 'fieldname': 'taxes_and_charges',
'non_standard_fieldnames': { 'non_standard_fieldnames': {
'Tax Rule': 'sales_tax_template', 'Tax Rule': 'sales_tax_template',
'Subscription': 'tax_template', 'Subscription': 'sales_tax_template',
'Restaurant': 'default_tax_template' 'Restaurant': 'default_tax_template'
}, },
'transactions': [ 'transactions': [

View File

@@ -611,7 +611,7 @@ def get_partywise_advanced_payment_amount(party_type, posting_date = None, futur
cond = "posting_date <= '{0}'".format(posting_date) cond = "posting_date <= '{0}'".format(posting_date)
if company: if company:
cond += "and company = '{0}'".format(company) cond += "and company = {0}".format(frappe.db.escape(company))
data = frappe.db.sql(""" SELECT party, sum({0}) as amount data = frappe.db.sql(""" SELECT party, sum({0}) as amount
FROM `tabGL Entry` FROM `tabGL Entry`

View File

@@ -11,7 +11,7 @@ from erpnext.assets.doctype.asset_maintenance.asset_maintenance import calculate
class AssetMaintenanceLog(Document): class AssetMaintenanceLog(Document):
def validate(self): def validate(self):
if getdate(self.due_date) < getdate(nowdate()): if getdate(self.due_date) < getdate(nowdate()) and self.maintenance_status not in ["Completed", "Cancelled"]:
self.maintenance_status = "Overdue" self.maintenance_status = "Overdue"
if self.maintenance_status == "Completed" and not self.completion_date: if self.maintenance_status == "Completed" and not self.completion_date:

View File

@@ -1,14 +1,15 @@
frappe.listview_settings['Asset Maintenance Log'] = { frappe.listview_settings['Asset Maintenance Log'] = {
add_fields: ["maintenance_status"], add_fields: ["maintenance_status"],
has_indicator_for_draft: 1,
get_indicator: function(doc) { get_indicator: function(doc) {
if(doc.maintenance_status=="Pending") { if (doc.maintenance_status=="Planned") {
return [__("Pending"), "orange"]; return [__(doc.maintenance_status), "orange", "status,=," + doc.maintenance_status];
} else if(doc.maintenance_status=="Completed") { } else if (doc.maintenance_status=="Completed") {
return [__("Completed"), "green"]; return [__(doc.maintenance_status), "green", "status,=," + doc.maintenance_status];
} else if(doc.maintenance_status=="Cancelled") { } else if (doc.maintenance_status=="Cancelled") {
return [__("Cancelled"), "red"]; return [__(doc.maintenance_status), "red", "status,=," + doc.maintenance_status];
} else if(doc.maintenance_status=="Overdue") { } else if (doc.maintenance_status=="Overdue") {
return [__("Overdue"), "red"]; return [__(doc.maintenance_status), "red", "status,=," + doc.maintenance_status];
} }
} }
}; };

View File

@@ -8,6 +8,7 @@ from frappe import _
from frappe.utils import flt, getdate, cint, date_diff, formatdate from frappe.utils import flt, getdate, cint, date_diff, formatdate
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_checks_for_pl_and_bs_accounts
class AssetValueAdjustment(Document): class AssetValueAdjustment(Document):
def validate(self): def validate(self):
@@ -53,17 +54,33 @@ class AssetValueAdjustment(Document):
je.company = self.company je.company = self.company
je.remark = "Depreciation Entry against {0} worth {1}".format(self.asset, self.difference_amount) je.remark = "Depreciation Entry against {0} worth {1}".format(self.asset, self.difference_amount)
je.append("accounts", { credit_entry = {
"account": accumulated_depreciation_account, "account": accumulated_depreciation_account,
"credit_in_account_currency": self.difference_amount, "credit_in_account_currency": self.difference_amount,
"cost_center": depreciation_cost_center or self.cost_center "cost_center": depreciation_cost_center or self.cost_center
}) }
je.append("accounts", { debit_entry = {
"account": depreciation_expense_account, "account": depreciation_expense_account,
"debit_in_account_currency": self.difference_amount, "debit_in_account_currency": self.difference_amount,
"cost_center": depreciation_cost_center or self.cost_center "cost_center": depreciation_cost_center or self.cost_center
}) }
accounting_dimensions = get_checks_for_pl_and_bs_accounts()
for dimension in accounting_dimensions:
if dimension.get('mandatory_for_bs'):
credit_entry.update({
dimension['fieldname']: self.get(dimension['fieldname']) or dimension.get('default_dimension')
})
if dimension.get('mandatory_for_pl'):
debit_entry.update({
dimension['fieldname']: self.get(dimension['fieldname']) or dimension.get('default_dimension')
})
je.append("accounts", credit_entry)
je.append("accounts", debit_entry)
je.flags.ignore_permissions = True je.flags.ignore_permissions = True
je.submit() je.submit()

View File

@@ -94,7 +94,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
if(this.frm.doc.status !== 'Closed' && flt(this.frm.doc.per_received) < 100 && flt(this.frm.doc.per_billed) < 100) { if(this.frm.doc.status !== 'Closed' && flt(this.frm.doc.per_received) < 100 && flt(this.frm.doc.per_billed) < 100) {
this.frm.add_custom_button(__('Update Items'), () => { this.frm.add_custom_button(__('Update Items'), () => {
erpnext.utils.update_child_items({ erpnext.utils.update_child_items({
frm: frm, frm: this.frm,
child_docname: "items", child_docname: "items",
child_doctype: "Purchase Order Detail", child_doctype: "Purchase Order Detail",
cannot_add_row: false, cannot_add_row: false,

View File

@@ -479,7 +479,11 @@ class AccountsController(TransactionBase):
if d.against_order: if d.against_order:
allocated_amount = flt(d.amount) allocated_amount = flt(d.amount)
else: else:
amount = self.rounded_total or self.grand_total if self.get('party_account_currency') == self.company_currency:
amount = self.get('base_rounded_total') or self.base_grand_total
else:
amount = self.get('rounded_total') or self.grand_total
allocated_amount = min(amount - advance_allocated, d.amount) allocated_amount = min(amount - advance_allocated, d.amount)
advance_allocated += flt(allocated_amount) advance_allocated += flt(allocated_amount)
@@ -802,10 +806,22 @@ class AccountsController(TransactionBase):
self.payment_terms_template = '' self.payment_terms_template = ''
return return
party_account_currency = self.get('party_account_currency')
if not party_account_currency:
party_type, party = self.get_party()
if party_type and party:
party_account_currency = get_party_account_currency(party_type, party, self.company)
posting_date = self.get("bill_date") or self.get("posting_date") or self.get("transaction_date") posting_date = self.get("bill_date") or self.get("posting_date") or self.get("transaction_date")
date = self.get("due_date") date = self.get("due_date")
due_date = date or posting_date due_date = date or posting_date
grand_total = self.get("rounded_total") or self.grand_total
if party_account_currency == self.company_currency:
grand_total = self.get("base_rounded_total") or self.base_grand_total
else:
grand_total = self.get("rounded_total") or self.grand_total
if self.doctype in ("Sales Invoice", "Purchase Invoice"): if self.doctype in ("Sales Invoice", "Purchase Invoice"):
grand_total = grand_total - flt(self.write_off_amount) grand_total = grand_total - flt(self.write_off_amount)
@@ -850,13 +866,25 @@ class AccountsController(TransactionBase):
def validate_payment_schedule_amount(self): def validate_payment_schedule_amount(self):
if self.doctype == 'Sales Invoice' and self.is_pos: return if self.doctype == 'Sales Invoice' and self.is_pos: return
party_account_currency = self.get('party_account_currency')
if not party_account_currency:
party_type, party = self.get_party()
if party_type and party:
party_account_currency = get_party_account_currency(party_type, party, self.company)
if self.get("payment_schedule"): if self.get("payment_schedule"):
total = 0 total = 0
for d in self.get("payment_schedule"): for d in self.get("payment_schedule"):
total += flt(d.payment_amount) total += flt(d.payment_amount)
total = flt(total, self.precision("grand_total"))
grand_total = flt(self.get("rounded_total") or self.grand_total, self.precision('grand_total')) if party_account_currency == self.company_currency:
total = flt(total, self.precision("base_grand_total"))
grand_total = flt(self.get("base_rounded_total") or self.base_grand_total, self.precision('base_grand_total'))
else:
total = flt(total, self.precision("grand_total"))
grand_total = flt(self.get("rounded_total") or self.grand_total, self.precision('grand_total'))
if self.get("total_advance"): if self.get("total_advance"):
grand_total -= self.get("total_advance") grand_total -= self.get("total_advance")
@@ -957,7 +985,7 @@ def validate_inclusive_tax(tax, doc):
# all rows about the reffered tax should be inclusive # all rows about the reffered tax should be inclusive
_on_previous_row_error("1 - %d" % (tax.row_id,)) _on_previous_row_error("1 - %d" % (tax.row_id,))
elif tax.get("category") == "Valuation": elif tax.get("category") == "Valuation":
frappe.throw(_("Valuation type charges can not marked as Inclusive")) frappe.throw(_("Valuation type charges can not be marked as Inclusive"))
def set_balance_in_account_currency(gl_dict, account_currency=None, conversion_rate=None, company_currency=None): def set_balance_in_account_currency(gl_dict, account_currency=None, conversion_rate=None, company_currency=None):

View File

@@ -613,9 +613,12 @@ def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
if not taxes: if not taxes:
return frappe.db.sql(""" SELECT name FROM `tabItem Tax Template` """) return frappe.db.sql(""" SELECT name FROM `tabItem Tax Template` """)
else: else:
valid_from = filters.get('valid_from')
valid_from = valid_from[1] if isinstance(valid_from, list) else valid_from
args = { args = {
'item_code': filters.get('item_code'), 'item_code': filters.get('item_code'),
'posting_date': filters.get('valid_from'), 'posting_date': valid_from,
'tax_category': filters.get('tax_category'), 'tax_category': filters.get('tax_category'),
'company': filters.get('company') 'company': filters.get('company')
} }

View File

@@ -161,8 +161,9 @@ class calculate_taxes_and_totals(object):
for item in self.doc.get("items"): for item in self.doc.get("items"):
item_tax_map = self._load_item_tax_rate(item.item_tax_rate) item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
cumulated_tax_fraction = 0 cumulated_tax_fraction = 0
total_inclusive_tax_amount_per_qty = 0
for i, tax in enumerate(self.doc.get("taxes")): for i, tax in enumerate(self.doc.get("taxes")):
tax.tax_fraction_for_current_item = self.get_current_tax_fraction(tax, item_tax_map) tax.tax_fraction_for_current_item, inclusive_tax_amount_per_qty = self.get_current_tax_fraction(tax, item_tax_map)
if i==0: if i==0:
tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item
@@ -172,9 +173,12 @@ class calculate_taxes_and_totals(object):
+ tax.tax_fraction_for_current_item + tax.tax_fraction_for_current_item
cumulated_tax_fraction += tax.tax_fraction_for_current_item cumulated_tax_fraction += tax.tax_fraction_for_current_item
total_inclusive_tax_amount_per_qty += inclusive_tax_amount_per_qty * flt(item.stock_qty)
if cumulated_tax_fraction and not self.discount_amount_applied and item.qty: if not self.discount_amount_applied and item.qty and (cumulated_tax_fraction or total_inclusive_tax_amount_per_qty):
item.net_amount = flt(item.amount / (1 + cumulated_tax_fraction)) amount = flt(item.amount) - total_inclusive_tax_amount_per_qty
item.net_amount = flt(amount / (1 + cumulated_tax_fraction))
item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate")) item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate"))
item.discount_percentage = flt(item.discount_percentage, item.discount_percentage = flt(item.discount_percentage,
item.precision("discount_percentage")) item.precision("discount_percentage"))
@@ -190,6 +194,7 @@ class calculate_taxes_and_totals(object):
from tax inclusive amount from tax inclusive amount
""" """
current_tax_fraction = 0 current_tax_fraction = 0
inclusive_tax_amount_per_qty = 0
if cint(tax.included_in_print_rate): if cint(tax.included_in_print_rate):
tax_rate = self._get_tax_rate(tax, item_tax_map) tax_rate = self._get_tax_rate(tax, item_tax_map)
@@ -204,10 +209,15 @@ class calculate_taxes_and_totals(object):
elif tax.charge_type == "On Previous Row Total": elif tax.charge_type == "On Previous Row Total":
current_tax_fraction = (tax_rate / 100.0) * \ current_tax_fraction = (tax_rate / 100.0) * \
self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item
elif tax.charge_type == "On Item Quantity":
inclusive_tax_amount_per_qty = flt(tax_rate)
if getattr(tax, "add_deduct_tax", None): if getattr(tax, "add_deduct_tax", None) and tax.add_deduct_tax == "Deduct":
current_tax_fraction *= -1.0 if (tax.add_deduct_tax == "Deduct") else 1.0 current_tax_fraction *= -1.0
return current_tax_fraction inclusive_tax_amount_per_qty *= -1.0
return current_tax_fraction, inclusive_tax_amount_per_qty
def _get_tax_rate(self, tax, item_tax_map): def _get_tax_rate(self, tax, item_tax_map):
if tax.account_head in item_tax_map: if tax.account_head in item_tax_map:
@@ -321,7 +331,7 @@ class calculate_taxes_and_totals(object):
current_tax_amount = (tax_rate / 100.0) * \ current_tax_amount = (tax_rate / 100.0) * \
self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_for_current_item self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_for_current_item
elif tax.charge_type == "On Item Quantity": elif tax.charge_type == "On Item Quantity":
current_tax_amount = tax_rate * item.stock_qty current_tax_amount = tax_rate * item.qty
self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount) self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
@@ -472,7 +482,7 @@ class calculate_taxes_and_totals(object):
actual_taxes_dict = {} actual_taxes_dict = {}
for tax in self.doc.get("taxes"): for tax in self.doc.get("taxes"):
if tax.charge_type == "Actual": if tax.charge_type in ["Actual", "On Item Quantity"]:
tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax.tax_amount, tax) tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax.tax_amount, tax)
actual_taxes_dict.setdefault(tax.idx, tax_amount) actual_taxes_dict.setdefault(tax.idx, tax_amount)
elif tax.row_id in actual_taxes_dict: elif tax.row_id in actual_taxes_dict:

View File

@@ -16,6 +16,7 @@
"opportunity_from", "opportunity_from",
"party_name", "party_name",
"customer_name", "customer_name",
"source",
"column_break0", "column_break0",
"title", "title",
"opportunity_type", "opportunity_type",
@@ -49,10 +50,9 @@
"contact_email", "contact_email",
"contact_mobile", "contact_mobile",
"more_info", "more_info",
"source", "company",
"campaign", "campaign",
"column_break1", "column_break1",
"company",
"transaction_date", "transaction_date",
"amended_from", "amended_from",
"lost_reasons" "lost_reasons"
@@ -344,7 +344,7 @@
"collapsible": 1, "collapsible": 1,
"fieldname": "more_info", "fieldname": "more_info",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Source", "label": "More Information",
"oldfieldtype": "Section Break", "oldfieldtype": "Section Break",
"options": "fa fa-file-text" "options": "fa fa-file-text"
}, },
@@ -424,7 +424,7 @@
"icon": "fa fa-info-sign", "icon": "fa fa-info-sign",
"idx": 195, "idx": 195,
"links": [], "links": [],
"modified": "2020-07-14 16:49:15.888503", "modified": "2020-08-11 17:34:35.066961",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "CRM", "module": "CRM",
"name": "Opportunity", "name": "Opportunity",

View File

@@ -119,11 +119,19 @@ class Opportunity(TransactionBase):
and q.status not in ('Lost', 'Closed')""", self.name) and q.status not in ('Lost', 'Closed')""", self.name)
def has_ordered_quotation(self): def has_ordered_quotation(self):
return frappe.db.sql(""" if not self.with_items:
select q.name return frappe.get_all('Quotation',
from `tabQuotation` q, `tabQuotation Item` qi {
where q.name = qi.parent and q.docstatus=1 and qi.prevdoc_docname =%s 'opportunity': self.name,
and q.status = 'Ordered'""", self.name) 'status': 'Ordered',
'docstatus': 1
}, 'name')
else:
return frappe.db.sql("""
select q.name
from `tabQuotation` q, `tabQuotation Item` qi
where q.name = qi.parent and q.docstatus=1 and qi.prevdoc_docname =%s
and q.status = 'Ordered'""", self.name)
def has_lost_quotation(self): def has_lost_quotation(self):
lost_quotation = frappe.db.sql(""" lost_quotation = frappe.db.sql("""
@@ -330,7 +338,7 @@ def make_opportunity_from_communication(communication, ignore_communication_link
opportunity = frappe.get_doc({ opportunity = frappe.get_doc({
"doctype": "Opportunity", "doctype": "Opportunity",
"opportunity_from": opportunity_from, "opportunity_from": opportunity_from,
"lead": lead "party_name": lead
}).insert(ignore_permissions=True) }).insert(ignore_permissions=True)
link_communication_to_document(doc, "Opportunity", opportunity.name, ignore_communication_links) link_communication_to_document(doc, "Opportunity", opportunity.name, ignore_communication_links)

View File

@@ -7,7 +7,7 @@ import frappe
import unittest import unittest
from frappe.utils import nowdate from frappe.utils import nowdate
from frappe.utils.make_random import get_random from frappe.utils.make_random import get_random
from erpnext.education.doctype.program.test_program import make_program_and_linked_courses
# test_records = frappe.get_test_records('Fees') # test_records = frappe.get_test_records('Fees')
@@ -15,6 +15,7 @@ class TestFees(unittest.TestCase):
def test_fees(self): def test_fees(self):
student = get_random("Student") student = get_random("Student")
program = make_program_and_linked_courses("_Test Program 1", ["_Test Course 1", "_Test Course 2"])
fee = frappe.new_doc("Fees") fee = frappe.new_doc("Fees")
fee.posting_date = nowdate() fee.posting_date = nowdate()
fee.due_date = nowdate() fee.due_date = nowdate()
@@ -23,6 +24,7 @@ class TestFees(unittest.TestCase):
fee.income_account = "Sales - _TC" fee.income_account = "Sales - _TC"
fee.cost_center = "_Test Cost Center - _TC" fee.cost_center = "_Test Cost Center - _TC"
fee.company = "_Test Company" fee.company = "_Test Company"
fee.program = program.name
fee.extend("components", [ fee.extend("components", [
{ {

View File

@@ -134,7 +134,7 @@ let transfer_patient_dialog = function(frm) {
{fieldtype: 'Link', label: 'Leave From', fieldname: 'leave_from', options: 'Healthcare Service Unit', reqd: 1, read_only:1}, {fieldtype: 'Link', label: 'Leave From', fieldname: 'leave_from', options: 'Healthcare Service Unit', reqd: 1, read_only:1},
{fieldtype: 'Link', label: 'Service Unit Type', fieldname: 'service_unit_type', options: 'Healthcare Service Unit Type'}, {fieldtype: 'Link', label: 'Service Unit Type', fieldname: 'service_unit_type', options: 'Healthcare Service Unit Type'},
{fieldtype: 'Link', label: 'Transfer To', fieldname: 'service_unit', options: 'Healthcare Service Unit', reqd: 1}, {fieldtype: 'Link', label: 'Transfer To', fieldname: 'service_unit', options: 'Healthcare Service Unit', reqd: 1},
{fieldtype: 'Datetime', label: 'Check In', fieldname: 'check_in', reqd: 1} {fieldtype: 'Datetime', label: 'Check In', fieldname: 'check_in', reqd: 1, default: frappe.datetime.now_datetime()}
], ],
primary_action_label: __('Transfer'), primary_action_label: __('Transfer'),
primary_action : function() { primary_action : function() {
@@ -147,7 +147,12 @@ let transfer_patient_dialog = function(frm) {
if(dialog.get_value('service_unit')){ if(dialog.get_value('service_unit')){
service_unit = dialog.get_value('service_unit'); service_unit = dialog.get_value('service_unit');
} }
if(!check_in){ if(check_in > frappe.datetime.now_datetime()){
frappe.msgprint({
title: __('Not Allowed'),
message: __('Check-in time cannot be greater than the current time'),
indicator: 'red'
});
return; return;
} }
frappe.call({ frappe.call({

View File

@@ -322,7 +322,8 @@ scheduler_events = {
"erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status", "erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status",
"erpnext.selling.doctype.quotation.quotation.set_expired_status", "erpnext.selling.doctype.quotation.quotation.set_expired_status",
"erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_appointment_status", "erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_appointment_status",
"erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status" "erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status",
"erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email"
], ],
"daily_long": [ "daily_long": [
"erpnext.setup.doctype.email_digest.email_digest.send", "erpnext.setup.doctype.email_digest.email_digest.send",

View File

@@ -78,7 +78,7 @@
"idx": 0, "idx": 0,
"is_standard": 1, "is_standard": 1,
"label": "HR", "label": "HR",
"modified": "2020-06-16 19:20:50.976045", "modified": "2020-08-11 17:04:38.655417",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "HR", "name": "HR",
@@ -88,7 +88,7 @@
"pin_to_top": 0, "pin_to_top": 0,
"shortcuts": [ "shortcuts": [
{ {
"color": "#9deca2", "color": "#cef6d1",
"format": "{} Active", "format": "{} Active",
"label": "Employee", "label": "Employee",
"link_to": "Employee", "link_to": "Employee",
@@ -96,18 +96,19 @@
"type": "DocType" "type": "DocType"
}, },
{ {
"label": "Attendance", "color": "#ffe8cd",
"link_to": "Attendance",
"stats_filter": "",
"type": "DocType"
},
{
"format": "{} Open", "format": "{} Open",
"label": "Leave Application", "label": "Leave Application",
"link_to": "Leave Application", "link_to": "Leave Application",
"stats_filter": "{\"status\":\"Open\"}", "stats_filter": "{\"status\":\"Open\"}",
"type": "DocType" "type": "DocType"
}, },
{
"label": "Attendance",
"link_to": "Attendance",
"stats_filter": "",
"type": "DocType"
},
{ {
"label": "Job Applicant", "label": "Job Applicant",
"link_to": "Job Applicant", "link_to": "Job Applicant",

View File

@@ -8,7 +8,7 @@ frappe.query_reports["Downtime Analysis"] = {
label: __("From Date"), label: __("From Date"),
fieldname:"from_date", fieldname:"from_date",
fieldtype: "Datetime", fieldtype: "Datetime",
default: frappe.datetime.add_months(frappe.datetime.now_datetime(), -1), default: frappe.datetime.convert_to_system_tz(frappe.datetime.add_months(frappe.datetime.now_datetime(), -1)),
reqd: 1 reqd: 1
}, },
{ {

View File

@@ -718,3 +718,5 @@ erpnext.patches.v13_0.delete_report_requested_items_to_order
erpnext.patches.v12_0.update_item_tax_template_company erpnext.patches.v12_0.update_item_tax_template_company
erpnext.patches.v13_0.move_branch_code_to_bank_account erpnext.patches.v13_0.move_branch_code_to_bank_account
erpnext.patches.v13_0.healthcare_lab_module_rename_doctypes erpnext.patches.v13_0.healthcare_lab_module_rename_doctypes
erpnext.patches.v13_0.stock_entry_enhancements
erpnext.patches.v12_0.update_state_code_for_daman_and_diu

View File

@@ -19,7 +19,7 @@ def create_stock_entry_types():
for purpose in ["Material Issue", "Material Receipt", "Material Transfer", for purpose in ["Material Issue", "Material Receipt", "Material Transfer",
"Material Transfer for Manufacture", "Material Consumption for Manufacture", "Manufacture", "Material Transfer for Manufacture", "Material Consumption for Manufacture", "Manufacture",
"Repack", "Send to Subcontractor", "Send to Warehouse", "Receive at Warehouse"]: "Repack", "Send to Subcontractor"]:
ste_type = frappe.get_doc({ ste_type = frappe.get_doc({
'doctype': 'Stock Entry Type', 'doctype': 'Stock Entry Type',

View File

@@ -0,0 +1,22 @@
import frappe
from erpnext.regional.india import states
def execute():
company = frappe.get_all('Company', filters = {'country': 'India'})
if not company:
return
# Update options in gst_state custom field
gst_state = frappe.get_doc('Custom Field', 'Address-gst_state')
gst_state.options = '\n'.join(states)
gst_state.save()
# Update gst_state and state code in existing address
frappe.db.sql("""
UPDATE `tabAddress`
SET
gst_state = 'Dadra and Nagar Haveli and Daman and Diu',
gst_state_number = 26
WHERE gst_state = 'Daman and Diu'
""")

View File

@@ -0,0 +1,27 @@
# Copyright(c) 2020, Frappe Technologies Pvt.Ltd.and Contributors
# License: GNU General Public License v3.See license.txt
from __future__ import unicode_literals
import frappe
def execute():
frappe.reload_doc("stock", "doctype", "stock_entry")
if frappe.db.has_column("Stock Entry", "add_to_transit"):
frappe.db.sql("""
UPDATE `tabStock Entry` SET
stock_entry_type = 'Material Transfer',
purpose = 'Material Transfer',
add_to_transit = 1 WHERE stock_entry_type = 'Send to Warehouse'
""")
frappe.db.sql("""UPDATE `tabStock Entry` SET
stock_entry_type = 'Material Transfer',
purpose = 'Material Transfer'
WHERE stock_entry_type = 'Receive at Warehouse'
""")
frappe.reload_doc("stock", "doctype", "warehouse_type")
if not frappe.db.exists('Warehouse Type', 'Transit'):
doc = frappe.new_doc('Warehouse Type')
doc.name = 'Transit'
doc.insert()

View File

@@ -8,7 +8,7 @@
{ {
"hidden": 0, "hidden": 0,
"label": "Taxation", "label": "Taxation",
"links": "[\n {\n \"label\": \"Payroll Period\",\n \"name\": \"Payroll Period\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Income Tax Slab\",\n \"name\": \"Income Tax Slab\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Declaration\",\n \"name\": \"Employee Tax Exemption Declaration\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Proof Submission\",\n \"name\": \"Employee Tax Exemption Proof Submission\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Category\",\n \"name\": \"Employee Tax Exemption Category\",\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Sub Category\",\n \"name\": \"Employee Tax Exemption Sub Category\",\n \"type\": \"doctype\"\n \n }\n]" "links": "[\n {\n \"label\": \"Payroll Period\",\n \"name\": \"Payroll Period\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Income Tax Slab\",\n \"name\": \"Income Tax Slab\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Other Income\",\n \"name\": \"Employee Other Income\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Declaration\",\n \"name\": \"Employee Tax Exemption Declaration\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Proof Submission\",\n \"name\": \"Employee Tax Exemption Proof Submission\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Category\",\n \"name\": \"Employee Tax Exemption Category\",\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Sub Category\",\n \"name\": \"Employee Tax Exemption Sub Category\",\n \"type\": \"doctype\"\n \n }\n]"
}, },
{ {
"hidden": 0, "hidden": 0,
@@ -38,7 +38,7 @@
"idx": 0, "idx": 0,
"is_standard": 1, "is_standard": 1,
"label": "Payroll", "label": "Payroll",
"modified": "2020-06-19 12:23:06.034046", "modified": "2020-08-10 19:38:45.976209",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Payroll", "name": "Payroll",

View File

@@ -90,7 +90,7 @@ class PayrollEntry(Document):
cond = '' cond = ''
for f in ['company', 'branch', 'department', 'designation']: for f in ['company', 'branch', 'department', 'designation']:
if self.get(f): if self.get(f):
cond += " and t1." + f + " = '" + self.get(f).replace("'", "\'") + "'" cond += " and t1." + f + " = " + frappe.db.escape(self.get(f))
return cond return cond

View File

@@ -13,7 +13,7 @@ frappe.ui.form.on("Communication", {
frappe.confirm(__(confirm_msg, [__("Issue")]), () => { frappe.confirm(__(confirm_msg, [__("Issue")]), () => {
frm.trigger('make_issue_from_communication'); frm.trigger('make_issue_from_communication');
}) })
}, "Make"); }, "Create");
} }
if(!in_list(["Lead", "Opportunity"], frm.doc.reference_doctype)) { if(!in_list(["Lead", "Opportunity"], frm.doc.reference_doctype)) {

View File

@@ -163,9 +163,11 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
$.each(me.frm.doc["items"] || [], function(n, item) { $.each(me.frm.doc["items"] || [], function(n, item) {
var item_tax_map = me._load_item_tax_rate(item.item_tax_rate); var item_tax_map = me._load_item_tax_rate(item.item_tax_rate);
var cumulated_tax_fraction = 0.0; var cumulated_tax_fraction = 0.0;
var total_inclusive_tax_amount_per_qty = 0;
$.each(me.frm.doc["taxes"] || [], function(i, tax) { $.each(me.frm.doc["taxes"] || [], function(i, tax) {
tax.tax_fraction_for_current_item = me.get_current_tax_fraction(tax, item_tax_map); var current_tax_fraction = me.get_current_tax_fraction(tax, item_tax_map);
tax.tax_fraction_for_current_item = current_tax_fraction[0];
var inclusive_tax_amount_per_qty = current_tax_fraction[1];
if(i==0) { if(i==0) {
tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item; tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item;
@@ -176,10 +178,12 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
} }
cumulated_tax_fraction += tax.tax_fraction_for_current_item; cumulated_tax_fraction += tax.tax_fraction_for_current_item;
total_inclusive_tax_amount_per_qty += inclusive_tax_amount_per_qty * flt(item.qty);
}); });
if(cumulated_tax_fraction && !me.discount_amount_applied) { if(!me.discount_amount_applied && item.qty && (total_inclusive_tax_amount_per_qty || cumulated_tax_fraction)) {
item.net_amount = flt(item.amount / (1 + cumulated_tax_fraction)); var amount = flt(item.amount) - total_inclusive_tax_amount_per_qty;
item.net_amount = flt(amount / (1 + cumulated_tax_fraction));
item.net_rate = item.qty ? flt(item.net_amount / item.qty, precision("net_rate", item)) : 0; item.net_rate = item.qty ? flt(item.net_amount / item.qty, precision("net_rate", item)) : 0;
me.set_in_company_currency(item, ["net_rate", "net_amount"]); me.set_in_company_currency(item, ["net_rate", "net_amount"]);
@@ -191,6 +195,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
// Get tax fraction for calculating tax exclusive amount // Get tax fraction for calculating tax exclusive amount
// from tax inclusive amount // from tax inclusive amount
var current_tax_fraction = 0.0; var current_tax_fraction = 0.0;
var inclusive_tax_amount_per_qty = 0;
if(cint(tax.included_in_print_rate)) { if(cint(tax.included_in_print_rate)) {
var tax_rate = this._get_tax_rate(tax, item_tax_map); var tax_rate = this._get_tax_rate(tax, item_tax_map);
@@ -205,13 +210,16 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
} else if(tax.charge_type == "On Previous Row Total") { } else if(tax.charge_type == "On Previous Row Total") {
current_tax_fraction = (tax_rate / 100.0) * current_tax_fraction = (tax_rate / 100.0) *
this.frm.doc["taxes"][cint(tax.row_id) - 1].grand_total_fraction_for_current_item; this.frm.doc["taxes"][cint(tax.row_id) - 1].grand_total_fraction_for_current_item;
} else if (tax.charge_type == "On Item Quantity") {
inclusive_tax_amount_per_qty = flt(tax_rate);
} }
} }
if(tax.add_deduct_tax) { if(tax.add_deduct_tax && tax.add_deduct_tax == "Deduct") {
current_tax_fraction *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0; current_tax_fraction *= -1;
inclusive_tax_amount_per_qty *= -1;
} }
return current_tax_fraction; return [current_tax_fraction, inclusive_tax_amount_per_qty];
}, },
_get_tax_rate: function(tax, item_tax_map) { _get_tax_rate: function(tax, item_tax_map) {
@@ -360,8 +368,9 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
} else if(tax.charge_type == "On Previous Row Total") { } else if(tax.charge_type == "On Previous Row Total") {
current_tax_amount = (tax_rate / 100.0) * current_tax_amount = (tax_rate / 100.0) *
this.frm.doc["taxes"][cint(tax.row_id) - 1].grand_total_for_current_item; this.frm.doc["taxes"][cint(tax.row_id) - 1].grand_total_for_current_item;
} else if (tax.charge_type == "On Item Quantity") {
current_tax_amount = tax_rate * item.qty;
} }
this.set_item_wise_tax(item, tax, tax_rate, current_tax_amount); this.set_item_wise_tax(item, tax, tax_rate, current_tax_amount);
return current_tax_amount; return current_tax_amount;
@@ -573,7 +582,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
var actual_taxes_dict = {}; var actual_taxes_dict = {};
$.each(this.frm.doc["taxes"] || [], function(i, tax) { $.each(this.frm.doc["taxes"] || [], function(i, tax) {
if (tax.charge_type == "Actual") { if (in_list(["Actual", "On Item Quantity"], tax.charge_type)) {
var tax_amount = (tax.category == "Valuation") ? 0.0 : tax.tax_amount; var tax_amount = (tax.category == "Valuation") ? 0.0 : tax.tax_amount;
tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0; tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0;
actual_taxes_dict[tax.idx] = tax_amount; actual_taxes_dict[tax.idx] = tax_amount;
@@ -586,7 +595,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
$.each(actual_taxes_dict, function(key, value) { $.each(actual_taxes_dict, function(key, value) {
if (value) total_actual_tax += value; if (value) total_actual_tax += value;
}); });
return flt(this.frm.doc.grand_total - total_actual_tax, precision("grand_total")); return flt(this.frm.doc.grand_total - total_actual_tax, precision("grand_total"));
} }
}, },

View File

@@ -1821,7 +1821,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
}, },
set_query_for_item_tax_template: function(doc, cdt, cdn) { set_query_for_item_tax_template: function(doc, cdt, cdn) {
var item = frappe.get_doc(cdt, cdn); var item = frappe.get_doc(cdt, cdn);
if(!item.item_code) { if(!item.item_code) {
frappe.throw(__("Please enter Item Code to get item taxes")); frappe.throw(__("Please enter Item Code to get item taxes"));
@@ -1829,7 +1828,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
let filters = { let filters = {
'item_code': item.item_code, 'item_code': item.item_code,
'valid_from': doc.transaction_date || doc.bill_date || doc.posting_date, 'valid_from': ["<=", doc.transaction_date || doc.bill_date || doc.posting_date],
'item_group': item.item_group, 'item_group': item.item_group,
} }

View File

@@ -297,6 +297,10 @@
margin-top: 30px; margin-top: 30px;
} }
.item-group-slideshow {
margin-bottom: 1rem;
}
.product-image-img { .product-image-img {
border: 1px solid @light-border-color; border: 1px solid @light-border-color;
border-radius: 3px; border-radius: 3px;

View File

@@ -10,8 +10,7 @@ states = [
'Bihar', 'Bihar',
'Chandigarh', 'Chandigarh',
'Chhattisgarh', 'Chhattisgarh',
'Dadra and Nagar Haveli', 'Dadra and Nagar Haveli and Daman and Diu',
'Daman and Diu',
'Delhi', 'Delhi',
'Goa', 'Goa',
'Gujarat', 'Gujarat',
@@ -50,8 +49,7 @@ state_numbers = {
"Bihar": "10", "Bihar": "10",
"Chandigarh": "04", "Chandigarh": "04",
"Chhattisgarh": "22", "Chhattisgarh": "22",
"Dadra and Nagar Haveli": "26", "Dadra and Nagar Haveli and Daman and Diu": "26",
"Daman and Diu": "25",
"Delhi": "07", "Delhi": "07",
"Goa": "30", "Goa": "30",
"Gujarat": "24", "Gujarat": "24",

View File

@@ -134,15 +134,10 @@
"state_code": "DL", "state_code": "DL",
"state_name": "Delhi" "state_name": "Delhi"
}, },
{
"state_number": "25",
"state_code": "DD",
"state_name": "Daman and Diu"
},
{ {
"state_number": "26", "state_number": "26",
"state_code": "DN", "state_code": "DN",
"state_name": "Dadra and Nagar Haveli" "state_name": "Dadra and Nagar Haveli and Daman and Diu"
}, },
{ {
"state_number": "22", "state_number": "22",

View File

@@ -131,6 +131,9 @@ class Gstr1Report(object):
taxable_value += abs(net_amount) taxable_value += abs(net_amount)
elif tax_rate: elif tax_rate:
taxable_value += abs(net_amount) taxable_value += abs(net_amount)
elif not tax_rate and self.filters.get('type_of_business') == 'EXPORT' \
and invoice_details.get('export_type') == "Without Payment of Tax":
taxable_value += abs(net_amount)
row += [tax_rate or 0, taxable_value] row += [tax_rate or 0, taxable_value]

View File

@@ -12,7 +12,8 @@ def get_data():
'Payment Entry': 'party', 'Payment Entry': 'party',
'Quotation': 'party_name', 'Quotation': 'party_name',
'Opportunity': 'party_name', 'Opportunity': 'party_name',
'Bank Account': 'party' 'Bank Account': 'party',
'Subscription': 'party'
}, },
'dynamic_links': { 'dynamic_links': {
'party_name': ['Customer', 'quotation_to'] 'party_name': ['Customer', 'quotation_to']

View File

@@ -35,7 +35,8 @@ erpnext.PointOfSale.Controller = class {
create_opening_voucher() { create_opening_voucher() {
const table_fields = [ const table_fields = [
{ fieldname: "mode_of_payment", fieldtype: "Link", in_list_view: 1, label: "Mode of Payment", options: "Mode of Payment", reqd: 1 }, { fieldname: "mode_of_payment", fieldtype: "Link", in_list_view: 1, label: "Mode of Payment", options: "Mode of Payment", reqd: 1 },
{ fieldname: "opening_amount", fieldtype: "Currency", in_list_view: 1, label: "Opening Amount", options: "company:company_currency", reqd: 1 } { fieldname: "opening_amount", fieldtype: "Currency", default: 0, in_list_view: 1, label: "Opening Amount",
options: "company:company_currency", reqd: 1 }
]; ];
const dialog = new frappe.ui.Dialog({ const dialog = new frappe.ui.Dialog({
@@ -66,7 +67,7 @@ erpnext.PointOfSale.Controller = class {
frappe.db.get_doc("POS Closing Entry", pos_closing_entry.name).then(({ payment_reconciliation }) => { frappe.db.get_doc("POS Closing Entry", pos_closing_entry.name).then(({ payment_reconciliation }) => {
dialog.fields_dict.balance_details.df.data = []; dialog.fields_dict.balance_details.df.data = [];
payment_reconciliation.forEach(pay => { payment_reconciliation.forEach(pay => {
const { mode_of_payment, closing_amount } = pay; const { mode_of_payment } = pay;
dialog.fields_dict.balance_details.df.data.push({ dialog.fields_dict.balance_details.df.data.push({
mode_of_payment: mode_of_payment mode_of_payment: mode_of_payment
}); });
@@ -152,7 +153,7 @@ erpnext.PointOfSale.Controller = class {
}, },
() => this.make_new_invoice(), () => this.make_new_invoice(),
() => frappe.dom.unfreeze(), () => frappe.dom.unfreeze(),
() => this.page.set_title(__('Point of Sale Beta')), () => this.page.set_title(__('Point of Sale')),
]); ]);
} }

View File

@@ -34,6 +34,16 @@ frappe.ui.form.on("Company", {
frm.set_query("default_buying_terms", function() { frm.set_query("default_buying_terms", function() {
return { filters: { buying: 1 } }; return { filters: { buying: 1 } };
}); });
frm.set_query("default_in_transit_warehouse", function() {
return {
filters:{
'warehouse_type' : 'Transit',
'is_group': 0,
'company': frm.doc.company
}
};
});
}, },
company_name: function(frm) { company_name: function(frm) {

View File

@@ -25,6 +25,7 @@
"default_selling_terms", "default_selling_terms",
"default_buying_terms", "default_buying_terms",
"default_warehouse_for_sales_return", "default_warehouse_for_sales_return",
"default_in_transit_warehouse",
"column_break_10", "column_break_10",
"country", "country",
"create_chart_of_accounts_based_on", "create_chart_of_accounts_based_on",
@@ -242,7 +243,7 @@
{ {
"fieldname": "default_warehouse_for_sales_return", "fieldname": "default_warehouse_for_sales_return",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Default warehouse for Sales Return", "label": "Default Warehouse for Sales Return",
"options": "Warehouse" "options": "Warehouse"
}, },
{ {
@@ -733,6 +734,12 @@
"fieldname": "enable_perpetual_inventory_for_non_stock_items", "fieldname": "enable_perpetual_inventory_for_non_stock_items",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Enable Perpetual Inventory For Non Stock Items" "label": "Enable Perpetual Inventory For Non Stock Items"
},
{
"fieldname": "default_in_transit_warehouse",
"fieldtype": "Link",
"label": "Default In Transit Warehouse",
"options": "Warehouse"
} }
], ],
"icon": "fa fa-building", "icon": "fa fa-building",
@@ -740,7 +747,7 @@
"image_field": "company_logo", "image_field": "company_logo",
"is_tree": 1, "is_tree": 1,
"links": [], "links": [],
"modified": "2020-06-24 12:45:31.462195", "modified": "2020-08-06 00:38:08.311216",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Setup", "module": "Setup",
"name": "Company", "name": "Company",
@@ -801,4 +808,4 @@
"sort_field": "modified", "sort_field": "modified",
"sort_order": "ASC", "sort_order": "ASC",
"track_changes": 1 "track_changes": 1
} }

View File

@@ -140,7 +140,8 @@ class Company(NestedSet):
{"warehouse_name": _("All Warehouses"), "is_group": 1}, {"warehouse_name": _("All Warehouses"), "is_group": 1},
{"warehouse_name": _("Stores"), "is_group": 0}, {"warehouse_name": _("Stores"), "is_group": 0},
{"warehouse_name": _("Work In Progress"), "is_group": 0}, {"warehouse_name": _("Work In Progress"), "is_group": 0},
{"warehouse_name": _("Finished Goods"), "is_group": 0}]: {"warehouse_name": _("Finished Goods"), "is_group": 0},
{"warehouse_name": _("Goods In Transit"), "is_group": 0, "warehouse_type": "Transit"}]:
if not frappe.db.exists("Warehouse", "{0} - {1}".format(wh_detail["warehouse_name"], self.abbr)): if not frappe.db.exists("Warehouse", "{0} - {1}".format(wh_detail["warehouse_name"], self.abbr)):
warehouse = frappe.get_doc({ warehouse = frappe.get_doc({
@@ -149,7 +150,8 @@ class Company(NestedSet):
"is_group": wh_detail["is_group"], "is_group": wh_detail["is_group"],
"company": self.name, "company": self.name,
"parent_warehouse": "{0} - {1}".format(_("All Warehouses"), self.abbr) \ "parent_warehouse": "{0} - {1}".format(_("All Warehouses"), self.abbr) \
if not wh_detail["is_group"] else "" if not wh_detail["is_group"] else "",
"warehouse_type" : wh_detail["warehouse_type"] if "warehouse_type" in wh_detail else None
}) })
warehouse.flags.ignore_permissions = True warehouse.flags.ignore_permissions = True
warehouse.flags.ignore_mandatory = True warehouse.flags.ignore_mandatory = True

View File

@@ -95,8 +95,6 @@ def install(country=None):
{'doctype': 'Stock Entry Type', 'name': 'Send to Subcontractor', 'purpose': 'Send to Subcontractor'}, {'doctype': 'Stock Entry Type', 'name': 'Send to Subcontractor', 'purpose': 'Send to Subcontractor'},
{'doctype': 'Stock Entry Type', 'name': 'Material Transfer for Manufacture', 'purpose': 'Material Transfer for Manufacture'}, {'doctype': 'Stock Entry Type', 'name': 'Material Transfer for Manufacture', 'purpose': 'Material Transfer for Manufacture'},
{'doctype': 'Stock Entry Type', 'name': 'Material Consumption for Manufacture', 'purpose': 'Material Consumption for Manufacture'}, {'doctype': 'Stock Entry Type', 'name': 'Material Consumption for Manufacture', 'purpose': 'Material Consumption for Manufacture'},
{'doctype': 'Stock Entry Type', 'name': 'Send to Warehouse', 'purpose': 'Send to Warehouse'},
{'doctype': 'Stock Entry Type', 'name': 'Receive at Warehouse', 'purpose': 'Receive at Warehouse'},
# Designation # Designation
{'doctype': 'Designation', 'designation_name': _('CEO')}, {'doctype': 'Designation', 'designation_name': _('CEO')},
@@ -244,7 +242,10 @@ def install(country=None):
{"doctype": "Sales Stage", "stage_name": _("Identifying Decision Makers")}, {"doctype": "Sales Stage", "stage_name": _("Identifying Decision Makers")},
{"doctype": "Sales Stage", "stage_name": _("Perception Analysis")}, {"doctype": "Sales Stage", "stage_name": _("Perception Analysis")},
{"doctype": "Sales Stage", "stage_name": _("Proposal/Price Quote")}, {"doctype": "Sales Stage", "stage_name": _("Proposal/Price Quote")},
{"doctype": "Sales Stage", "stage_name": _("Negotiation/Review")} {"doctype": "Sales Stage", "stage_name": _("Negotiation/Review")},
# Warehouse Type
{'doctype': 'Warehouse Type', 'name': 'Transit'},
] ]
from erpnext.setup.setup_wizard.data.industry_type import get_industry_types from erpnext.setup.setup_wizard.data.industry_type import get_industry_types

View File

@@ -175,7 +175,7 @@
"no_copy": 1, "no_copy": 1,
"oldfieldname": "naming_series", "oldfieldname": "naming_series",
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "MAT-DN-.YYYY.-", "options": "MAT-DN-.YYYY.-\nMAT-DN-RET-.YYYY.-",
"print_hide": 1, "print_hide": 1,
"reqd": 1, "reqd": 1,
"set_only_once": 1 "set_only_once": 1
@@ -1255,7 +1255,7 @@
"idx": 146, "idx": 146,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-07-18 05:13:55.580420", "modified": "2020-08-03 23:18:47.739997",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Delivery Note", "name": "Delivery Note",

View File

@@ -123,6 +123,7 @@
"weightage", "weightage",
"slideshow", "slideshow",
"website_image", "website_image",
"website_image_alt",
"thumbnail", "thumbnail",
"cb72", "cb72",
"website_warehouse", "website_warehouse",
@@ -1054,15 +1055,21 @@
"fieldtype": "Data", "fieldtype": "Data",
"label": "Default Manufacturer Part No", "label": "Default Manufacturer Part No",
"read_only": 1 "read_only": 1
},
{
"fieldname": "website_image_alt",
"fieldtype": "Data",
"label": "Image Description"
} }
], ],
"has_web_view": 1, "has_web_view": 1,
"icon": "fa fa-tag", "icon": "fa fa-tag",
"idx": 2, "idx": 2,
"image_field": "image", "image_field": "image",
"index_web_pages_for_search": 1,
"links": [], "links": [],
"max_attachments": 1, "max_attachments": 1,
"modified": "2020-07-31 21:21:10.956453", "modified": "2020-08-07 14:24:58.384992",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Item", "name": "Item",
@@ -1124,4 +1131,4 @@
"sort_order": "DESC", "sort_order": "DESC",
"title_field": "item_name", "title_field": "item_name",
"track_changes": 1 "track_changes": 1
} }

View File

@@ -343,7 +343,7 @@ class Item(WebsiteGenerator):
if variant: if variant:
context.variant = frappe.get_doc("Item", variant) context.variant = frappe.get_doc("Item", variant)
for fieldname in ("website_image", "web_long_description", "description", for fieldname in ("website_image", "website_image_alt", "web_long_description", "description",
"website_specifications"): "website_specifications"):
if context.variant.get(fieldname): if context.variant.get(fieldname):
value = context.variant.get(fieldname) value = context.variant.get(fieldname)

View File

@@ -11,6 +11,7 @@
"naming_series", "naming_series",
"title", "title",
"material_request_type", "material_request_type",
"transfer_status",
"customer", "customer",
"column_break_2", "column_break_2",
"schedule_date", "schedule_date",
@@ -303,13 +304,22 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Set From Warehouse", "label": "Set From Warehouse",
"options": "Warehouse" "options": "Warehouse"
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.add_to_transit == 1",
"fieldname": "transfer_status",
"fieldtype": "Select",
"label": "Transfer Status",
"options": "\nNot Started\nIn Transit\nCompleted",
"read_only": 1
} }
], ],
"icon": "fa fa-ticket", "icon": "fa fa-ticket",
"idx": 70, "idx": 70,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-05-01 20:21:09.990867", "modified": "2020-08-10 13:27:54.891058",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Material Request", "name": "Material Request",

View File

@@ -1,8 +1,16 @@
frappe.listview_settings['Material Request'] = { frappe.listview_settings['Material Request'] = {
add_fields: ["material_request_type", "status", "per_ordered", "per_received"], add_fields: ["material_request_type", "status", "per_ordered", "per_received", "transfer_status"],
get_indicator: function(doc) { get_indicator: function(doc) {
if(doc.status=="Stopped") { if(doc.status=="Stopped") {
return [__("Stopped"), "red", "status,=,Stopped"]; return [__("Stopped"), "red", "status,=,Stopped"];
} else if(doc.transfer_status && doc.docstatus != 2) {
if (doc.transfer_status == "Not Started") {
return [__("Not Started"), "orange"];
} else if (doc.transfer_status == "In Transit") {
return [__("In Transit"), "yellow"];
} else if (doc.transfer_status == "Completed") {
return [__("Completed"), "green"];
}
} else if(doc.docstatus==1 && flt(doc.per_ordered, 2) == 0) { } else if(doc.docstatus==1 && flt(doc.per_ordered, 2) == 0) {
return [__("Pending"), "orange", "per_ordered,=,0"]; return [__("Pending"), "orange", "per_ordered,=,0"];
} else if(doc.docstatus==1 && flt(doc.per_ordered, 2) < 100) { } else if(doc.docstatus==1 && flt(doc.per_ordered, 2) < 100) {

View File

@@ -1,7 +1,6 @@
{ {
"actions": [], "actions": [],
"allow_import": 1, "allow_import": 1,
"allow_workflow": 1,
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2013-05-21 16:16:39", "creation": "2013-05-21 16:16:39",
"doctype": "DocType", "doctype": "DocType",
@@ -160,7 +159,7 @@
"no_copy": 1, "no_copy": 1,
"oldfieldname": "naming_series", "oldfieldname": "naming_series",
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "MAT-PRE-.YYYY.-", "options": "MAT-PRE-.YYYY.-\nMAT-PR-RET-.YYYY.-",
"print_hide": 1, "print_hide": 1,
"reqd": 1, "reqd": 1,
"set_only_once": 1 "set_only_once": 1
@@ -1110,7 +1109,7 @@
"idx": 261, "idx": 261,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-07-18 05:19:12.148115", "modified": "2020-08-03 23:20:26.381024",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Purchase Receipt", "name": "Purchase Receipt",

View File

@@ -19,7 +19,6 @@ frappe.ui.form.on('Stock Entry', {
filters: [ filters: [
['Stock Entry', 'docstatus', '=', 1], ['Stock Entry', 'docstatus', '=', 1],
['Stock Entry', 'per_transferred', '<','100'], ['Stock Entry', 'per_transferred', '<','100'],
['Stock Entry', 'purpose', '=', 'Send to Warehouse']
] ]
} }
}); });
@@ -171,9 +170,9 @@ frappe.ui.form.on('Stock Entry', {
} }
} }
if (frm.doc.docstatus === 1 && frm.doc.purpose == 'Send to Warehouse') { if (frm.doc.docstatus === 1) {
if (frm.doc.per_transferred < 100) { if (frm.doc.add_to_transit && frm.doc.purpose=='Material Transfer' && frm.doc.per_transferred < 100) {
frm.add_custom_button(__('Receive at Warehouse Entry'), function() { frm.add_custom_button('End Transit', function() {
frappe.model.open_mapped_doc({ frappe.model.open_mapped_doc({
method: "erpnext.stock.doctype.stock_entry.stock_entry.make_stock_in_entry", method: "erpnext.stock.doctype.stock_entry.stock_entry.make_stock_in_entry",
frm: frm frm: frm
@@ -266,6 +265,7 @@ frappe.ui.form.on('Stock Entry', {
stock_entry_type: function(frm){ stock_entry_type: function(frm){
frm.remove_custom_button('Bill of Materials', "Get items from"); frm.remove_custom_button('Bill of Materials', "Get items from");
frm.events.show_bom_custom_button(frm); frm.events.show_bom_custom_button(frm);
frm.trigger('add_to_transit');
}, },
purpose: function(frm) { purpose: function(frm) {
@@ -532,6 +532,26 @@ frappe.ui.form.on('Stock Entry', {
target_warehouse_address: function(frm) { target_warehouse_address: function(frm) {
erpnext.utils.get_address_display(frm, 'target_warehouse_address', 'target_address_display', false); erpnext.utils.get_address_display(frm, 'target_warehouse_address', 'target_address_display', false);
},
add_to_transit: function(frm) {
if(frm.doc.add_to_transit && frm.doc.purpose=='Material Transfer') {
frm.set_value('stock_entry_type', 'Material Transfer');
frm.fields_dict.to_warehouse.get_query = function() {
return {
filters:{
'warehouse_type' : 'Transit',
'is_group': 0,
'company': frm.doc.company
}
};
};
frappe.db.get_value('Company', frm.doc.company, 'default_in_transit_warehouse', (r) => {
if (r.default_in_transit_warehouse) {
frm.set_value('to_warehouse', r.default_in_transit_warehouse);
}
});
}
} }
}) })
@@ -754,6 +774,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
} }
erpnext.hide_company(); erpnext.hide_company();
erpnext.utils.add_item(this.frm); erpnext.utils.add_item(this.frm);
this.frm.trigger('add_to_transit');
}, },
scan_barcode: function() { scan_barcode: function() {
@@ -919,8 +940,6 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
doc.purpose!='Material Issue'); doc.purpose!='Material Issue');
this.frm.fields_dict["items"].grid.set_column_disp("additional_cost", doc.purpose!='Material Issue'); this.frm.fields_dict["items"].grid.set_column_disp("additional_cost", doc.purpose!='Material Issue');
this.frm.toggle_reqd("outgoing_stock_entry",
doc.purpose == 'Receive at Warehouse' ? 1: 0);
}, },
supplier: function(doc) { supplier: function(doc) {

View File

@@ -13,6 +13,7 @@
"stock_entry_type", "stock_entry_type",
"outgoing_stock_entry", "outgoing_stock_entry",
"purpose", "purpose",
"add_to_transit",
"work_order", "work_order",
"purchase_order", "purchase_order",
"delivery_note_no", "delivery_note_no",
@@ -116,11 +117,12 @@
"reqd": 1 "reqd": 1
}, },
{ {
"depends_on": "eval:doc.purpose == 'Receive at Warehouse'", "depends_on": "eval:doc.purpose == 'Material Transfer'",
"fieldname": "outgoing_stock_entry", "fieldname": "outgoing_stock_entry",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Stock Entry (Outward GIT)", "label": "Stock Entry (Outward GIT)",
"options": "Stock Entry" "options": "Stock Entry",
"read_only": 1
}, },
{ {
"bold": 1, "bold": 1,
@@ -132,7 +134,7 @@
"label": "Purpose", "label": "Purpose",
"oldfieldname": "purpose", "oldfieldname": "purpose",
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "Material Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor\nSend to Warehouse\nReceive at Warehouse", "options": "Material Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor",
"read_only": 1 "read_only": 1
}, },
{ {
@@ -630,13 +632,21 @@
{ {
"fieldname": "print_settings_col_break", "fieldname": "print_settings_col_break",
"fieldtype": "Column Break" "fieldtype": "Column Break"
},
{
"default": "0",
"depends_on": "eval: doc.purpose=='Material Transfer' && !doc.outgoing_stock_entry",
"fieldname": "add_to_transit",
"fieldtype": "Check",
"label": "Add to Transit",
"no_copy": 1
} }
], ],
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"idx": 1, "idx": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-04-23 12:56:52.881752", "modified": "2020-08-11 19:10:07.954981",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Entry", "name": "Stock Entry",

View File

@@ -96,6 +96,11 @@ class StockEntry(StockController):
self.update_quality_inspection() self.update_quality_inspection()
if self.work_order and self.purpose == "Manufacture": if self.work_order and self.purpose == "Manufacture":
self.update_so_in_serial_number() self.update_so_in_serial_number()
if self.purpose == 'Material Transfer' and self.add_to_transit:
self.set_material_request_transfer_status('In Transit')
if self.purpose == 'Material Transfer' and self.outgoing_stock_entry:
self.set_material_request_transfer_status('Completed')
def on_cancel(self): def on_cancel(self):
@@ -116,6 +121,11 @@ class StockEntry(StockController):
self.update_quality_inspection() self.update_quality_inspection()
self.delete_auto_created_batches() self.delete_auto_created_batches()
if self.purpose == 'Material Transfer' and self.add_to_transit:
self.set_material_request_transfer_status('Not Started')
if self.purpose == 'Material Transfer' and self.outgoing_stock_entry:
self.set_material_request_transfer_status('In Transit')
def set_job_card_data(self): def set_job_card_data(self):
if self.job_card and not self.work_order: if self.job_card and not self.work_order:
data = frappe.db.get_value('Job Card', data = frappe.db.get_value('Job Card',
@@ -133,7 +143,7 @@ class StockEntry(StockController):
def validate_purpose(self): def validate_purpose(self):
valid_purposes = ["Material Issue", "Material Receipt", "Material Transfer", valid_purposes = ["Material Issue", "Material Receipt", "Material Transfer",
"Material Transfer for Manufacture", "Manufacture", "Repack", "Send to Subcontractor", "Material Transfer for Manufacture", "Manufacture", "Repack", "Send to Subcontractor",
"Material Consumption for Manufacture", "Send to Warehouse", "Receive at Warehouse"] "Material Consumption for Manufacture"]
if self.purpose not in valid_purposes: if self.purpose not in valid_purposes:
frappe.throw(_("Purpose must be one of {0}").format(comma_or(valid_purposes))) frappe.throw(_("Purpose must be one of {0}").format(comma_or(valid_purposes)))
@@ -199,7 +209,8 @@ class StockEntry(StockController):
item.set(f, item_details.get(f)) item.set(f, item_details.get(f))
if not item.transfer_qty and item.qty: if not item.transfer_qty and item.qty:
item.transfer_qty = item.qty * item.conversion_factor item.transfer_qty = flt(flt(item.qty) * flt(item.conversion_factor),
self.precision("transfer_qty", item))
if (self.purpose in ("Material Transfer", "Material Transfer for Manufacture") if (self.purpose in ("Material Transfer", "Material Transfer for Manufacture")
and not item.serial_no and not item.serial_no
@@ -258,10 +269,10 @@ class StockEntry(StockController):
"""perform various (sometimes conditional) validations on warehouse""" """perform various (sometimes conditional) validations on warehouse"""
source_mandatory = ["Material Issue", "Material Transfer", "Send to Subcontractor", "Material Transfer for Manufacture", source_mandatory = ["Material Issue", "Material Transfer", "Send to Subcontractor", "Material Transfer for Manufacture",
"Material Consumption for Manufacture", "Send to Warehouse", "Receive at Warehouse"] "Material Consumption for Manufacture"]
target_mandatory = ["Material Receipt", "Material Transfer", "Send to Subcontractor", target_mandatory = ["Material Receipt", "Material Transfer", "Send to Subcontractor",
"Material Transfer for Manufacture", "Send to Warehouse", "Receive at Warehouse"] "Material Transfer for Manufacture"]
validate_for_manufacture = any([d.bom_no for d in self.get("items")]) validate_for_manufacture = any([d.bom_no for d in self.get("items")])
@@ -809,7 +820,7 @@ class StockEntry(StockController):
def set_items_for_stock_in(self): def set_items_for_stock_in(self):
self.items = [] self.items = []
if self.outgoing_stock_entry and self.purpose == 'Receive at Warehouse': if self.outgoing_stock_entry and self.purpose == 'Material Transfer':
doc = frappe.get_doc('Stock Entry', self.outgoing_stock_entry) doc = frappe.get_doc('Stock Entry', self.outgoing_stock_entry)
if doc.per_transferred == 100: if doc.per_transferred == 100:
@@ -1210,13 +1221,25 @@ class StockEntry(StockController):
def validate_with_material_request(self): def validate_with_material_request(self):
for item in self.get("items"): for item in self.get("items"):
if item.material_request: material_request = item.material_request or None
material_request_item = item.material_request_item or None
if self.purpose == 'Material Transfer' and self.outgoing_stock_entry:
parent_se = frappe.get_value("Stock Entry Detail", item.ste_detail, ['material_request','material_request_item'],as_dict=True)
if parent_se:
material_request = parent_se.material_request
material_request_item = parent_se.material_request_item
if material_request:
mreq_item = frappe.db.get_value("Material Request Item", mreq_item = frappe.db.get_value("Material Request Item",
{"name": item.material_request_item, "parent": item.material_request}, {"name": material_request_item, "parent": material_request},
["item_code", "warehouse", "idx"], as_dict=True) ["item_code", "warehouse", "idx"], as_dict=True)
if mreq_item.item_code != item.item_code or \ if mreq_item.item_code != item.item_code:
mreq_item.warehouse != (item.s_warehouse if self.purpose== "Material Issue" else item.t_warehouse): frappe.throw(_("Item for row {0} does not match Material Request").format(item.idx),
frappe.throw(_("Item or Warehouse for row {0} does not match Material Request").format(item.idx), frappe.MappingMismatchError)
elif self.purpose == "Material Transfer" and self.add_to_transit:
continue
elif mreq_item.warehouse != (item.s_warehouse if self.purpose == "Material Issue" else item.t_warehouse):
frappe.throw(_("Warehouse for row {0} does not match Material Request").format(item.idx),
frappe.MappingMismatchError) frappe.MappingMismatchError)
def validate_batch(self): def validate_batch(self):
@@ -1284,7 +1307,7 @@ class StockEntry(StockController):
to fullfill Sales Order {2}.").format(item.item_code, sr, sales_order)) to fullfill Sales Order {2}.").format(item.item_code, sr, sales_order))
def update_transferred_qty(self): def update_transferred_qty(self):
if self.purpose == 'Receive at Warehouse': if self.purpose == 'Material Transfer' and self.outgoing_stock_entry:
stock_entries = {} stock_entries = {}
stock_entries_child_list = [] stock_entries_child_list = []
for d in self.items: for d in self.items:
@@ -1342,6 +1365,20 @@ class StockEntry(StockController):
'reference_type': reference_type, 'reference_type': reference_type,
'reference_name': reference_name 'reference_name': reference_name
}) })
def set_material_request_transfer_status(self, status):
material_requests = []
if self.outgoing_stock_entry:
parent_se = frappe.get_value("Stock Entry", self.outgoing_stock_entry, 'add_to_transit')
for item in self.items:
material_request = item.material_request or None
if self.purpose == "Material Transfer" and material_request not in material_requests:
if self.outgoing_stock_entry and parent_se:
material_request = frappe.get_value("Stock Entry Detail", item.ste_detail, 'material_request')
if material_request and material_request not in material_requests:
material_requests.append(material_request)
frappe.db.set_value('Material Request', material_request, 'transfer_status', status)
@frappe.whitelist() @frappe.whitelist()
def move_sample_to_retention_warehouse(company, items): def move_sample_to_retention_warehouse(company, items):
@@ -1381,12 +1418,19 @@ def move_sample_to_retention_warehouse(company, items):
@frappe.whitelist() @frappe.whitelist()
def make_stock_in_entry(source_name, target_doc=None): def make_stock_in_entry(source_name, target_doc=None):
def set_missing_values(source, target): def set_missing_values(source, target):
target.purpose = 'Receive at Warehouse'
target.set_stock_entry_type() target.set_stock_entry_type()
def update_item(source_doc, target_doc, source_parent): def update_item(source_doc, target_doc, source_parent):
target_doc.t_warehouse = '' target_doc.t_warehouse = ''
if source_doc.material_request_item and source_doc.material_request :
add_to_transit = frappe.db.get_value('Stock Entry', source_name, 'add_to_transit')
if add_to_transit:
warehouse = frappe.get_value('Material Request Item', source_doc.material_request_item, 'warehouse')
target_doc.t_warehouse = warehouse
target_doc.s_warehouse = source_doc.t_warehouse target_doc.s_warehouse = source_doc.t_warehouse
target_doc.qty = source_doc.qty - source_doc.transferred_qty target_doc.qty = source_doc.qty - source_doc.transferred_qty

View File

@@ -737,34 +737,6 @@ class TestStockEntry(unittest.TestCase):
self.assertEqual(se.get("items")[0].allow_zero_valuation_rate, 1) self.assertEqual(se.get("items")[0].allow_zero_valuation_rate, 1)
self.assertEqual(se.get("items")[0].amount, 0) self.assertEqual(se.get("items")[0].amount, 0)
def test_goods_in_transit(self):
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
warehouse = "_Test Warehouse FG 1 - _TC"
if not frappe.db.exists('Warehouse', warehouse):
create_warehouse("_Test Warehouse FG 1")
outward_entry = make_stock_entry(item_code="_Test Item",
purpose="Send to Warehouse",
source="_Test Warehouse - _TC",
target="_Test Warehouse 1 - _TC", qty=50, basic_rate=100)
inward_entry1 = make_stock_in_entry(outward_entry.name)
inward_entry1.items[0].t_warehouse = warehouse
inward_entry1.items[0].qty = 25
inward_entry1.submit()
doc = frappe.get_doc('Stock Entry', outward_entry.name)
self.assertEqual(doc.per_transferred, 50)
inward_entry2 = make_stock_in_entry(outward_entry.name)
inward_entry2.items[0].t_warehouse = warehouse
inward_entry2.items[0].qty = 25
inward_entry2.submit()
doc = frappe.get_doc('Stock Entry', outward_entry.name)
self.assertEqual(doc.per_transferred, 100)
def test_gle_for_opening_stock_entry(self): def test_gle_for_opening_stock_entry(self):
mr = make_stock_entry(item_code="_Test Item", target="Stores - TCP1", company="_Test Company with perpetual inventory",qty=50, basic_rate=100, expense_account="Stock Adjustment - TCP1", is_opening="Yes", do_not_save=True) mr = make_stock_entry(item_code="_Test Item", target="Stores - TCP1", company="_Test Company with perpetual inventory",qty=50, basic_rate=100, expense_account="Stock Adjustment - TCP1", is_opening="Yes", do_not_save=True)

View File

@@ -1,156 +1,83 @@
{ {
"allow_copy": 0, "actions": [],
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "Prompt", "autoname": "Prompt",
"beta": 0,
"creation": "2019-03-13 16:23:46.636769", "creation": "2019-03-13 16:23:46.636769",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [
"purpose"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Material Issue", "default": "Material Issue",
"fetch_if_empty": 0,
"fieldname": "purpose", "fieldname": "purpose",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Purpose", "label": "Purpose",
"length": 0, "options": "\nMaterial Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor",
"no_copy": 0,
"options": "\nMaterial Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor\nSend to Warehouse\nReceive at Warehouse",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1, "reqd": 1,
"search_index": 0, "set_only_once": 1
"set_only_once": 1,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "links": [],
"hide_heading": 0, "modified": "2020-08-10 23:24:37.160817",
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-03-26 12:02:42.144377",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Entry Type", "name": "Stock Entry Type",
"name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "System Manager", "role": "System Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Manufacturing Manager", "role": "Manufacturing Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Stock Manager", "role": "Stock Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Stock User", "role": "Stock User",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
} }
], ],
"quick_entry": 1, "quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "ASC", "sort_order": "ASC",
"track_changes": 1, "track_changes": 1
"track_seen": 0,
"track_views": 0
} }

View File

@@ -23,7 +23,7 @@
}) })
</script> </script>
{% else %} {% else %}
{{ product_image(website_image or image or 'no-image.jpg') }} {{ product_image(website_image or image or 'no-image.jpg', alt=website_image_alt or item_name) }}
{% endif %} {% endif %}
<!-- Simple image preview --> <!-- Simple image preview -->

View File

@@ -4,7 +4,7 @@
{% block page_content %} {% block page_content %}
<div class="item-group-content" itemscope itemtype="http://schema.org/Product"> <div class="item-group-content" itemscope itemtype="http://schema.org/Product">
<div> <div class="item-group-slideshow">
{% if slideshow %}<!-- slideshow --> {% if slideshow %}<!-- slideshow -->
{% include "templates/includes/slideshow.html" %} {% include "templates/includes/slideshow.html" %}
{% endif %} {% endif %}

View File

@@ -7,9 +7,9 @@
</div> </div>
{% endmacro %} {% endmacro %}
{% macro product_image(website_image, css_class="") %} {% macro product_image(website_image, css_class="", alt="") %}
<div class="border text-center rounded h-100 {{ css_class }}" style="overflow: hidden;"> <div class="border text-center rounded h-100 {{ css_class }}" style="overflow: hidden;">
<img itemprop="image" class="website-image h-100 w-100" src="{{ frappe.utils.quoted(website_image or 'no-image.jpg') | abs_url }}"> <img itemprop="image" class="website-image h-100 w-100" alt="{{ alt }}" src="{{ frappe.utils.quoted(website_image or 'no-image.jpg') | abs_url }}">
</div> </div>
{% endmacro %} {% endmacro %}