Merge branch 'develop' into multiple-users-pos-profile

This commit is contained in:
Faris Ansari 2017-10-30 15:00:55 +05:30 committed by GitHub
commit 7095de03ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
356 changed files with 13671 additions and 7694 deletions

View File

@ -4,7 +4,7 @@ import inspect
import frappe
from erpnext.hooks import regional_overrides
__version__ = '9.0.3'
__version__ = '9.2.2'
def get_default_company(user=None):
'''Get default company for user'''

View File

@ -851,7 +851,7 @@
"4457-Taxes sur le chiffre d'affaires collect\u00e9es par l'entreprise": {
"44571-TVA collect\u00e9e": {
"account_type": "Tax",
"tax_rate": 20.0
"is_group": 1
},
"44578-Taxes assimil\u00e9es \u00e0 la TVA": {}
},

View File

@ -417,6 +417,46 @@
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 0,
"read": 1,
"report": 0,
"role": "Sales User",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 0,
"read": 1,
"report": 0,
"role": "Purchase User",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
}
],
"quick_entry": 1,
@ -426,4 +466,4 @@
"sort_order": "ASC",
"track_changes": 1,
"track_seen": 0
}
}

View File

@ -55,13 +55,13 @@ frappe.ui.form.on('Asset', {
});
}
frm.trigger("show_graph");
frm.trigger("setup_chart");
}
},
show_graph: function(frm) {
var x_intervals = ["x", frm.doc.purchase_date];
var asset_values = ["Asset Value", frm.doc.gross_purchase_amount];
setup_chart: function(frm) {
var x_intervals = [frm.doc.purchase_date];
var asset_values = [frm.doc.gross_purchase_amount];
var last_depreciation_date = frm.doc.purchase_date;
if(frm.doc.opening_accumulated_depreciation) {
@ -94,32 +94,21 @@ frappe.ui.form.on('Asset', {
last_depreciation_date = frm.doc.disposal_date;
}
frm.dashboard.setup_chart({
frm.dashboard.render_graph({
title: "Asset Value",
data: {
x: 'x',
columns: [x_intervals, asset_values],
regions: {
'Asset Value': [{'start': last_depreciation_date, 'style':'dashed'}]
}
labels: x_intervals,
datasets: [{
color: 'green',
values: asset_values,
formatted: asset_values.map(d => d.toFixed(2))
}]
},
legend: {
show: false
},
axis: {
x: {
type: 'timeseries',
tick: {
format: "%d-%m-%Y"
}
},
y: {
min: 0,
padding: {bottom: 10}
}
}
type: 'line'
});
},
item_code: function(frm) {
if(frm.doc.item_code) {
frappe.call({

View File

@ -13,6 +13,7 @@ class TestAsset(unittest.TestCase):
def setUp(self):
set_depreciation_settings_in_company()
create_asset()
frappe.db.sql("delete from `tabTax Rule`")
def test_purchase_asset(self):
asset = frappe.get_doc("Asset", "Macbook Pro 1")

View File

@ -12,8 +12,8 @@ from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amo
from erpnext.hr.doctype.employee_loan.employee_loan import update_disbursement_status
class JournalEntry(AccountsController):
def __init__(self, arg1, arg2=None):
super(JournalEntry, self).__init__(arg1, arg2)
def __init__(self, *args, **kwargs):
super(JournalEntry, self).__init__(*args, **kwargs)
def get_feed(self):
return self.voucher_type
@ -54,7 +54,7 @@ class JournalEntry(AccountsController):
def update_advance_paid(self):
advance_paid = frappe._dict()
for d in self.get("accounts"):
if d.is_advance:
if d.is_advance == "Yes":
if d.reference_type in ("Sales Order", "Purchase Order"):
advance_paid.setdefault(d.reference_type, []).append(d.reference_name)
@ -76,7 +76,7 @@ class JournalEntry(AccountsController):
def unlink_advance_entry_reference(self):
for d in self.get("accounts"):
if d.is_advance and d.reference_type in ("Sales Invoice", "Purchase Invoice"):
if d.is_advance == "Yes" and d.reference_type in ("Sales Invoice", "Purchase Invoice"):
doc = frappe.get_doc(d.reference_type, d.reference_name)
doc.delink_advance_entries(self.name)
d.reference_type = ''

View File

@ -405,11 +405,7 @@ frappe.ui.form.on('Payment Entry', {
}
// Make read only if Accounts Settings doesn't allow stale rates
frappe.model.get_value("Accounts Settings", null, "allow_stale",
function(d){
frm.set_df_property("source_exchange_rate", "read_only", cint(d.allow_stale) ? 0 : 1);
}
);
frm.set_df_property("source_exchange_rate", "read_only", erpnext.stale_rate_allowed());
},
target_exchange_rate: function(frm) {
@ -430,11 +426,7 @@ frappe.ui.form.on('Payment Entry', {
frm.set_paid_amount_based_on_received_amount = false;
// Make read only if Accounts Settings doesn't allow stale rates
frappe.model.get_value("Accounts Settings", null, "allow_stale",
function(d){
frm.set_df_property("target_exchange_rate", "read_only", cint(d.allow_stale) ? 0 : 1);
}
);
frm.set_df_property("target_exchange_rate", "read_only", erpnext.stale_rate_allowed());
},
paid_amount: function(frm) {
@ -660,8 +652,15 @@ frappe.ui.form.on('Payment Entry', {
var party_amount = frm.doc.payment_type=="Receive" ?
frm.doc.paid_amount : frm.doc.received_amount;
var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [],
function(d) { return flt(d.amount) }));
if(frm.doc.total_allocated_amount < party_amount) {
unallocated_amount = party_amount - frm.doc.total_allocated_amount;
if(frm.doc.payment_type == "Receive") {
unallocated_amount = party_amount - (frm.doc.total_allocated_amount - total_deductions);
} else {
unallocated_amount = party_amount - (frm.doc.total_allocated_amount + total_deductions);
}
}
}
frm.set_value("unallocated_amount", unallocated_amount);
@ -680,9 +679,6 @@ frappe.ui.form.on('Payment Entry', {
difference_amount = flt(frm.doc.base_paid_amount) - flt(frm.doc.base_received_amount);
}
var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [],
function(d) { return flt(d.amount) }));
frm.set_value("difference_amount", difference_amount - total_deductions);
frm.events.hide_unhide_fields(frm);

View File

@ -285,8 +285,13 @@ class PaymentEntry(AccountsController):
if self.party:
party_amount = self.paid_amount if self.payment_type=="Receive" else self.received_amount
total_deductions = sum([flt(d.amount) for d in self.get("deductions")])
if self.total_allocated_amount < party_amount:
self.unallocated_amount = party_amount - self.total_allocated_amount
if self.payment_type == "Receive":
self.unallocated_amount = party_amount - (self.total_allocated_amount - total_deductions)
else:
self.unallocated_amount = party_amount - (self.total_allocated_amount + total_deductions)
def set_difference_amount(self):
base_unallocated_amount = flt(self.unallocated_amount) * (flt(self.source_exchange_rate)

View File

@ -0,0 +1,60 @@
QUnit.module('Payment Entry');
QUnit.test("test payment entry", function(assert) {
assert.expect(7 );
let done = assert.async();
frappe.run_serially([
() => {
return frappe.tests.make('Purchase Invoice', [
{supplier: 'Test Supplier'},
{bill_no: 'in1234'},
{items: [
[
{'qty': 2},
{'item_code': 'Test Product 1'},
{'rate':1000},
]
]},
{update_stock:1},
{supplier_address: 'Test1-Billing'},
{contact_person: 'Contact 3-Test Supplier'},
{tc_name: 'Test Term 1'},
{terms: 'This is just a Test'}
]);
},
() => cur_frm.save(),
() => frappe.tests.click_button('Submit'),
() => frappe.tests.click_button('Yes'),
() => frappe.timeout(1),
() => frappe.click_button('Make'),
() => frappe.timeout(2),
() => frappe.click_link('Payment'),
() => frappe.timeout(3),
() => cur_frm.set_value('mode_of_payment','Cash'),
() => frappe.timeout(3),
() => {
assert.equal(frappe.get_route()[1], 'Payment Entry',
'made payment entry');
assert.equal(cur_frm.doc.party, 'Test Supplier',
'supplier set in payment entry');
assert.equal(cur_frm.doc.paid_amount, 2000,
'paid amount set in payment entry');
assert.equal(cur_frm.doc.references[0].allocated_amount, 2000,
'amount allocated against purchase invoice');
assert.equal(cur_frm.doc.references[0].bill_no, 'in1234',
'invoice number allocated against purchase invoice');
assert.equal(cur_frm.get_field('total_allocated_amount').value, 2000,
'correct amount allocated in Write Off');
assert.equal(cur_frm.get_field('unallocated_amount').value, 0,
'correct amount unallocated in Write Off');
},
() => cur_frm.save(),
() => frappe.tests.click_button('Submit'),
() => frappe.tests.click_button('Yes'),
() => frappe.timeout(3),
() => done()
]);
});

View File

@ -296,7 +296,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2017-09-04 17:37:01.192312",
"modified": "2017-10-16 17:37:01.192312",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry Reference",
@ -311,4 +311,4 @@
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}
}

View File

@ -37,10 +37,10 @@ frappe.ui.form.on('POS Profile', {
return { filters: { doc_type: "Sales Invoice", print_format_type: "Js"} };
});
frappe.db.get_value('POS Settings', {name: 'POS Settings'}, 'is_online', (r) => {
is_online = r && cint(r.is_online)
frm.toggle_display('offline_pos_section', !is_online);
frm.toggle_display('print_format_for_online', is_online);
frappe.db.get_value('POS Settings', {name: 'POS Settings'}, 'use_pos_in_offline_mode', (r) => {
is_offline = r && cint(r.use_pos_in_offline_mode)
frm.toggle_display('offline_pos_section', is_offline);
frm.toggle_display('print_format_for_online', !is_offline);
});
},

View File

@ -3,7 +3,17 @@
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class POSSettings(Document):
pass
def validate(self):
self.set_link_for_pos()
def set_link_for_pos(self):
link = 'pos' if self.use_pos_in_offline_mode else 'point-of-sale'
desktop_icon = frappe.db.get_value('Desktop Icon',
{'standard': 1, 'module_name': 'POS'}, 'name')
if desktop_icon:
frappe.db.set_value('Desktop Icon', desktop_icon, 'link', link)

View File

@ -348,6 +348,8 @@ def apply_internal_priority(pricing_rules, field_set, args):
return filtered_rules or pricing_rules
def set_transaction_type(args):
if args.transaction_type:
return
if args.doctype in ("Opportunity", "Quotation", "Sales Order", "Delivery Note", "Sales Invoice"):
args.transaction_type = "selling"
elif args.doctype in ("Material Request", "Supplier Quotation", "Purchase Order",
@ -356,4 +358,4 @@ def set_transaction_type(args):
elif args.customer:
args.transaction_type = "selling"
else:
args.transaction_type = "buying"
args.transaction_type = "buying"

View File

@ -3440,139 +3440,13 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "subscription",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Subscription",
"length": 0,
"no_copy": 1,
"options": "Subscription",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"collapsible_depends_on": "is_recurring",
"columns": 0,
"depends_on": "eval:doc.docstatus<2 && !doc.__islocal",
"fieldname": "recurring_invoice",
"fieldtype": "Section Break",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Recurring Invoice",
"length": 0,
"no_copy": 0,
"options": "fa fa-time",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.docstatus<2",
"description": "",
"fieldname": "is_recurring",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Is Recurring",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring==1",
"description": "Select the period when the invoice will be generated automatically",
"fieldname": "recurring_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Recurring Type",
"length": 0,
"no_copy": 1,
"options": "Monthly\nQuarterly\nHalf-yearly\nYearly",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring==1",
"depends_on": "",
"description": "Start date of current invoice's period",
"fieldname": "from_date",
"fieldtype": "Date",
@ -3603,7 +3477,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring==1",
"depends_on": "",
"description": "End date of current invoice's period",
"fieldname": "to_date",
"fieldtype": "Date",
@ -3628,138 +3502,13 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"fieldname": "submit_on_creation",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Submit on creation",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"description": "",
"fieldname": "notify_by_email",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Notify by email",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring==1",
"description": "The day of the month on which auto invoice will be generated e.g. 05, 28 etc",
"fieldname": "repeat_on_day_of_month",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Repeat on Day of Month",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring==1",
"description": "The date on which recurring invoice will be stop",
"fieldname": "end_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "End Date",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_82",
"fieldname": "column_break_114",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
@ -3771,101 +3520,8 @@
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "50%"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring==1",
"description": "The date on which next invoice will be generated. It is generated on submit.",
"fieldname": "next_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Next Date",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring==1",
"description": "The unique id for tracking all recurring invoices. It is generated on submit.",
"fieldname": "recurring_id",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Recurring Id",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring==1",
"description": "Enter Email Address separated by commas, invoice will be mailed automatically on particular date",
"fieldname": "notification_email_address",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Notification Email Address",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
@ -3881,8 +3537,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring==1",
"fieldname": "recurring_print_format",
"fieldname": "subscription",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
@ -3891,15 +3546,15 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Recurring Print Format",
"label": "Subscription",
"length": 0,
"no_copy": 0,
"options": "Print Format",
"no_copy": 1,
"options": "Subscription",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
@ -3920,7 +3575,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2017-09-19 11:22:47.074420",
"modified": "2017-10-24 12:51:51.199594",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@ -22,8 +22,8 @@ form_grid_templates = {
}
class PurchaseInvoice(BuyingController):
def __init__(self, arg1, arg2=None):
super(PurchaseInvoice, self).__init__(arg1, arg2)
def __init__(self, *args, **kwargs):
super(PurchaseInvoice, self).__init__(*args, **kwargs)
self.status_updater = [{
'source_dt': 'Purchase Invoice Item',
'target_dt': 'Purchase Order Item',

View File

@ -7,6 +7,7 @@ QUnit.test("test purchase invoice", function(assert) {
() => {
return frappe.tests.make('Purchase Invoice', [
{supplier: 'Test Supplier'},
{bill_no: 'in123'},
{items: [
[
{'qty': 5},
@ -36,7 +37,7 @@ QUnit.test("test purchase invoice", function(assert) {
},
() => frappe.tests.click_button('Submit'),
() => frappe.tests.click_button('Yes'),
() => frappe.timeout(0.3),
() => frappe.timeout(1),
() => done()
]);
});

View File

@ -473,6 +473,7 @@ class TestPurchaseInvoice(unittest.TestCase):
import test_records as jv_test_records
jv = frappe.copy_doc(jv_test_records[1])
jv.accounts[0].is_advance = 'Yes'
jv.insert()
jv.submit()

View File

@ -88,7 +88,7 @@ def update_pos_profile_data(doc, pos_profile, company_data):
doc.naming_series = pos_profile.get('naming_series') or 'SINV-'
doc.letter_head = pos_profile.get('letter_head') or company_data.default_letter_head
doc.ignore_pricing_rule = pos_profile.get('ignore_pricing_rule') or 0
doc.apply_discount_on = pos_profile.get('apply_discount_on') if pos_profile.get('apply_discount') else ''
doc.apply_discount_on = pos_profile.get('apply_discount_on') or 'Grand Total'
doc.customer_group = pos_profile.get('customer_group') or get_root('Customer Group')
doc.territory = pos_profile.get('territory') or get_root('Territory')
doc.terms = frappe.db.get_value('Terms and Conditions', pos_profile.get('tc_name'), 'terms') or doc.terms or ''
@ -417,6 +417,7 @@ def make_contact(args,customer):
'link_doctype': 'Customer',
'link_name': customer
})
doc.flags.ignore_mandatory = True
doc.save(ignore_permissions=True)
def make_address(args, customer):
@ -441,6 +442,7 @@ def make_address(args, customer):
address.is_primary_address = 1
address.is_shipping_address = 1
address.update(args)
address.flags.ignore_mandatory = True
address.save(ignore_permissions = True)
def make_email_queue(email_queue):
@ -484,17 +486,21 @@ def submit_invoice(si_doc, name, doc, name_list):
if frappe.message_log: frappe.message_log.pop()
frappe.db.rollback()
frappe.log_error(frappe.get_traceback())
name_list = save_invoice(e, si_doc, name, name_list)
name_list = save_invoice(doc, name, name_list)
return name_list
def save_invoice(e, si_doc, name, name_list):
def save_invoice(doc, name, name_list):
try:
if not frappe.db.exists('Sales Invoice', {'offline_pos_name': name}):
si_doc.docstatus = 0
si_doc.flags.ignore_mandatory = True
si_doc.due_date = si_doc.posting_date
si_doc.insert()
si = frappe.new_doc('Sales Invoice')
si.update(doc)
si.set_posting_time = 1
si.customer = get_customer_id(doc)
si.due_date = doc.get('posting_date')
si.flags.ignore_mandatory = True
si.insert(ignore_permissions=True)
frappe.db.commit()
name_list.append(name)
except Exception:
frappe.log_error(frappe.get_traceback())

View File

@ -339,7 +339,7 @@ $.extend(cur_frm.cscript, new erpnext.accounts.SalesInvoiceController({frm: cur_
// ------------
cur_frm.cscript.hide_fields = function(doc) {
var parent_fields = ['project', 'due_date', 'is_opening', 'source', 'total_advance', 'get_advances',
'advances', 'sales_partner', 'commission_rate', 'total_commission', 'advances', 'from_date', 'to_date'];
'advances', 'advances', 'from_date', 'to_date'];
if(cint(doc.is_pos) == 1) {
hide_field(parent_fields);
@ -520,6 +520,24 @@ frappe.ui.form.on('Sales Invoice', {
};
});
},
//When multiple companies are set up. in case company name is changed set default company address
company:function(frm){
if (frm.doc.company)
{
frappe.call({
method:"frappe.contacts.doctype.address.address.get_default_address",
args:{ doctype:'Company',name:frm.doc.company},
callback: function(r){
if (r.message){
frm.set_value("company_address",r.message)
}
else {
frm.set_value("company_address","")
}
}
})
}
},
project: function(frm){
frm.call({

View File

@ -4273,414 +4273,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "subscription",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Subscription",
"length": 0,
"no_copy": 1,
"options": "Subscription",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"collapsible_depends_on": "is_recurring",
"columns": 0,
"depends_on": "eval:doc.docstatus<2 && !doc.__islocal",
"fieldname": "recurring_invoice",
"fieldtype": "Section Break",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Recurring Invoice",
"length": 0,
"no_copy": 0,
"options": "fa fa-time",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break11",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Settings",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "50%"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.docstatus<2",
"description": "",
"fieldname": "is_recurring",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Is Recurring",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "is_recurring",
"description": "",
"fieldname": "recurring_id",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Reference Document",
"length": 0,
"no_copy": 1,
"options": "Sales Invoice",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"description": "",
"fieldname": "recurring_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Frequency",
"length": 0,
"no_copy": 1,
"options": "\nMonthly\nQuarterly\nHalf-yearly\nYearly",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"description": "",
"fieldname": "repeat_on_day_of_month",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Repeat on Day of Month",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"description": "",
"fieldname": "end_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "End Date",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"fieldname": "submit_on_creation",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Submit on creation",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"description": "",
"fieldname": "notify_by_email",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Notify by email",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.notify_by_email && doc.recurring_id === doc.name",
"description": "",
"fieldname": "notification_email_address",
"fieldtype": "Code",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Notification Email Address",
"length": 0,
"no_copy": 1,
"options": "Email",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.notify_by_email && doc.recurring_id === doc.name",
"fieldname": "recurring_print_format",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Recurring Print Format",
"length": 0,
"no_copy": 0,
"options": "Print Format",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break12",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "This Document",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "50%"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "is_recurring",
"depends_on": "",
"description": "",
"fieldname": "from_date",
"fieldtype": "Date",
@ -4711,7 +4304,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "is_recurring",
"depends_on": "",
"description": "",
"fieldname": "to_date",
"fieldtype": "Date",
@ -4742,10 +4335,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "is_recurring",
"description": "",
"fieldname": "next_date",
"fieldtype": "Date",
"fieldname": "column_break_140",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@ -4753,11 +4344,11 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Next Date",
"length": 0,
"no_copy": 1,
"no_copy": 0,
"permlevel": 0,
"print_hide": 1,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
@ -4767,6 +4358,37 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "subscription",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Subscription",
"length": 0,
"no_copy": 1,
"options": "Subscription",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@ -4811,7 +4433,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2017-09-19 11:23:08.675028",
"modified": "2017-10-24 12:46:48.331723",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@ -27,8 +27,8 @@ form_grid_templates = {
}
class SalesInvoice(SellingController):
def __init__(self, arg1, arg2=None):
super(SalesInvoice, self).__init__(arg1, arg2)
def __init__(self, *args, **kwargs):
super(SalesInvoice, self).__init__(*args, **kwargs)
self.status_updater = [{
'source_dt': 'Sales Invoice Item',
'target_field': 'billed_amt',
@ -70,6 +70,7 @@ class SalesInvoice(SellingController):
self.clear_unallocated_advances("Sales Invoice Advance", "advances")
self.add_remarks()
self.validate_write_off_account()
self.validate_duplicate_offline_pos_entry()
self.validate_account_for_change_amount()
self.validate_fixed_asset()
self.set_income_account_for_fixed_assets()
@ -462,6 +463,12 @@ class SalesInvoice(SellingController):
if flt(self.write_off_amount) and not self.write_off_account:
msgprint(_("Please enter Write Off Account"), raise_exception=1)
def validate_duplicate_offline_pos_entry(self):
if self.is_pos and self.offline_pos_name \
and frappe.db.get_value('Sales Invoice',
{'offline_pos_name': self.offline_pos_name, 'docstatus': 1}):
frappe.throw(_("Duplicate offline pos sales invoice {0}").format(self.offline_pos_name))
def validate_account_for_change_amount(self):
if flt(self.change_amount) and not self.account_for_change_amount:
msgprint(_("Please enter Account for Change Amount"), raise_exception=1)

View File

@ -3,8 +3,8 @@
from __future__ import unicode_literals
import frappe
import unittest, copy
from frappe.utils import nowdate, add_days, flt
import unittest, copy, time
from frappe.utils import nowdate, add_days, flt, cint
from frappe.model.dynamic_links import get_dynamic_link_map
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice
@ -665,6 +665,47 @@ class TestSalesInvoice(unittest.TestCase):
self.pos_gl_entry(si, pos, 330)
def test_make_pos_invoice_in_draft(self):
from erpnext.accounts.doctype.sales_invoice.pos import make_invoice
from erpnext.stock.doctype.item.test_item import make_item
set_perpetual_inventory()
allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock')
if allow_negative_stock:
frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 0)
make_pos_profile()
timestamp = cint(time.time())
item = make_item("_Test POS Item")
pos = copy.deepcopy(test_records[1])
pos['items'][0]['item_code'] = item.name
pos["is_pos"] = 1
pos["offline_pos_name"] = timestamp
pos["update_stock"] = 1
pos["payments"] = [{'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 300},
{'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 330}]
invoice_data = [{timestamp: pos}]
si = make_invoice(invoice_data).get('invoice')
self.assertEquals(si[0], timestamp)
sales_invoice = frappe.get_all('Sales Invoice', fields =["*"], filters = {'offline_pos_name': timestamp})
self.assertEquals(sales_invoice[0].docstatus, 0)
timestamp = cint(time.time())
pos["offline_pos_name"] = timestamp
invoice_data = [{timestamp: pos}]
si1 = make_invoice(invoice_data).get('invoice')
self.assertEquals(si1[0], timestamp)
sales_invoice1 = frappe.get_all('Sales Invoice', fields =["*"], filters = {'offline_pos_name': timestamp})
self.assertEquals(sales_invoice1[0].docstatus, 0)
if allow_negative_stock:
frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 1)
def pos_gl_entry(self, si, pos, cash_amount):
# check stock ledger entries
sle = frappe.db.sql("""select * from `tabStock Ledger Entry`
@ -1092,6 +1133,7 @@ class TestSalesInvoice(unittest.TestCase):
import test_records as jv_test_records
jv = frappe.copy_doc(jv_test_records[0])
jv.accounts[0].is_advance = 'Yes'
jv.insert()
jv.submit()

View File

@ -3,10 +3,17 @@
frappe.ui.form.on('Subscription', {
setup: function(frm) {
frm.fields_dict['reference_doctype'].get_query = function(doc) {
return {
query: "erpnext.accounts.doctype.subscription.subscription.subscription_doctype_query"
};
};
frm.fields_dict['reference_document'].get_query = function() {
return {
filters: {
"docstatus": 1
"docstatus": 1,
"subscription": ''
}
};
};

View File

@ -135,66 +135,6 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "disabled",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Disabled",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "submit_on_creation",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Submit on Creation",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@ -286,12 +226,12 @@
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "next_schedule_date",
"fieldtype": "Date",
"fieldname": "submit_on_creation",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@ -299,14 +239,44 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Next Schedule Date",
"label": "Submit on Creation",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "disabled",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Disabled",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
@ -320,7 +290,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "frequency_detail",
"fieldname": "section_break_10",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
@ -329,7 +299,6 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@ -381,7 +350,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_12",
"fieldname": "column_break_13",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
@ -437,10 +406,40 @@
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "next_schedule_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Next Schedule Date",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"columns": 0,
"fieldname": "notification",
"fieldtype": "Section Break",
"hidden": 0,
@ -495,6 +494,38 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval: doc.notify_by_email",
"description": "To add dynamic subject, use jinja tags like\n\n<div><pre><code>New {{ doc.doctype }} #{{ doc.name }}</code></pre></div>",
"fieldname": "subject",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Subject",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@ -593,6 +624,69 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
"depends_on": "eval:doc.notify_by_email",
"fieldname": "section_break_20",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Message",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Please find attached {{ doc.doctype }} #{{ doc.name }}",
"fieldname": "message",
"fieldtype": "Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Message",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"columns": 0,
"depends_on": "eval: !doc.__islocal",
"fieldname": "section_break_16",
"fieldtype": "Section Break",
"hidden": 0,
@ -690,7 +784,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-09-14 12:09:38.471458",
"modified": "2017-10-23 18:28:08.966403",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Subscription",
@ -700,7 +794,7 @@
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
@ -720,7 +814,7 @@
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
@ -740,7 +834,7 @@
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,

View File

@ -7,21 +7,28 @@ import frappe
import calendar
from frappe import _
from frappe.desk.form import assign_to
from frappe.utils.jinja import validate_template
from dateutil.relativedelta import relativedelta
from frappe.utils.user import get_system_managers
from frappe.utils import cstr, getdate, split_emails, add_days, today
from frappe.utils import cstr, getdate, split_emails, add_days, today, get_last_day, get_first_day
from frappe.model.document import Document
month_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, 'Yearly': 12}
class Subscription(Document):
def validate(self):
self.update_status()
self.validate_reference_doctype()
self.validate_dates()
self.validate_next_schedule_date()
self.validate_email_id()
validate_template(self.subject or "")
validate_template(self.message or "")
def before_submit(self):
self.set_next_schedule_date()
if not self.next_schedule_date:
self.next_schedule_date = get_next_schedule_date(self.start_date,
self.frequency, self.repeat_on_day)
def on_submit(self):
self.update_subscription_id()
@ -30,6 +37,18 @@ class Subscription(Document):
self.validate_dates()
self.set_next_schedule_date()
def before_cancel(self):
self.unlink_subscription_id()
self.next_schedule_date = None
def unlink_subscription_id(self):
frappe.db.sql("update `tab{0}` set subscription = null where subscription=%s"
.format(self.reference_doctype), self.name)
def validate_reference_doctype(self):
if not frappe.get_meta(self.reference_doctype).has_field('subscription'):
frappe.throw(_("Add custom field Subscription in the doctype {0}").format(self.reference_doctype))
def validate_dates(self):
if self.end_date and getdate(self.start_date) > getdate(self.end_date):
frappe.throw(_("End date must be greater than start date"))
@ -61,15 +80,11 @@ class Subscription(Document):
frappe.throw(_("'Recipients' not specified"))
def set_next_schedule_date(self):
self.next_schedule_date = get_next_schedule_date(self.start_date,
self.frequency, self.repeat_on_day)
if self.repeat_on_day:
self.next_schedule_date = get_next_date(self.next_schedule_date, 0, self.repeat_on_day)
def update_subscription_id(self):
doc = frappe.get_doc(self.reference_doctype, self.reference_document)
if not doc.meta.get_field('subscription'):
frappe.throw(_("Add custom field Subscription Id in the doctype {0}").format(self.reference_doctype))
doc.db_set('subscription', self.name)
frappe.db.set_value(self.reference_doctype, self.reference_document, "subscription", self.name)
def update_status(self, status=None):
self.status = {
@ -114,19 +129,19 @@ def create_documents(data, schedule_date):
doc = make_new_document(data, schedule_date)
if data.notify_by_email and data.recipients:
print_format = data.print_format or "Standard"
send_notification(doc, print_format, data.recipients)
send_notification(doc, data, print_format=print_format)
frappe.db.commit()
except Exception:
frappe.db.rollback()
frappe.db.begin()
frappe.log_error(frappe.get_traceback())
disabled_subscription(data)
disable_subscription(data)
frappe.db.commit()
if data.reference_document and not frappe.flags.in_test:
notify_error_to_user(data)
def disabled_subscription(data):
def disable_subscription(data):
subscription = frappe.get_doc('Subscription', data.name)
subscription.db_set('disabled', 1)
@ -160,28 +175,79 @@ def update_doc(new_document, reference_doc, args, schedule_date):
if new_document.meta.get_field('set_posting_time'):
new_document.set('set_posting_time', 1)
mcount = month_map.get(args.frequency)
if new_document.meta.get_field('subscription'):
new_document.set('subscription', args.name)
new_document.run_method("on_recurring", reference_doc=reference_doc, subscription_doc=args)
for fieldname in ['naming_series', 'ignore_pricing_rule', 'posting_time'
'select_print_heading', 'remarks', 'owner']:
if new_document.meta.get_field(fieldname):
new_document.set(fieldname, reference_doc.get(fieldname))
# copy item fields
if new_document.meta.get_field('items'):
for i, item in enumerate(new_document.items):
for fieldname in ("page_break",):
item.set(fieldname, reference_doc.items[i].get(fieldname))
for data in new_document.meta.fields:
if data.fieldtype == 'Date' and data.reqd:
new_document.set(data.fieldname, schedule_date)
set_subscription_period(args, mcount, new_document)
new_document.run_method("on_recurring", reference_doc=reference_doc, subscription_doc=args)
def set_subscription_period(args, mcount, new_document):
if mcount and new_document.meta.get_field('from_date') and new_document.meta.get_field('to_date'):
last_ref_doc = frappe.db.sql("""
select name, from_date, to_date
from `tab{0}`
where subscription=%s and docstatus < 2
order by creation desc
limit 1
""".format(args.reference_doctype), args.name, as_dict=1)
if not last_ref_doc:
return
from_date = get_next_date(last_ref_doc[0].from_date, mcount)
if (cstr(get_first_day(last_ref_doc[0].from_date)) == cstr(last_ref_doc[0].from_date)) and \
(cstr(get_last_day(last_ref_doc[0].to_date)) == cstr(last_ref_doc[0].to_date)):
to_date = get_last_day(get_next_date(last_ref_doc[0].to_date, mcount))
else:
to_date = get_next_date(last_ref_doc[0].to_date, mcount)
new_document.set('from_date', from_date)
new_document.set('to_date', to_date)
def get_next_date(dt, mcount, day=None):
dt = getdate(dt)
dt += relativedelta(months=mcount, day=day)
return dt
def send_notification(new_rv, print_format='Standard', recipients=None):
def send_notification(new_rv, subscription_doc, print_format='Standard'):
"""Notify concerned persons about recurring document generation"""
print_format = print_format
frappe.sendmail(recipients,
subject= _("New {0}: #{1}").format(new_rv.doctype, new_rv.name),
message = _("Please find attached {0} #{1}").format(new_rv.doctype, new_rv.name),
attachments = [frappe.attach_print(new_rv.doctype, new_rv.name, file_name=new_rv.name, print_format=print_format)])
if not subscription_doc.subject:
subject = _("New {0}: #{1}").format(new_rv.doctype, new_rv.name)
elif "{" in subscription_doc.subject:
subject = frappe.render_template(subscription_doc.subject, {'doc': new_rv})
if not subscription_doc.message:
message = _("Please find attached {0} #{1}").format(new_rv.doctype, new_rv.name)
elif "{" in subscription_doc.message:
message = frappe.render_template(subscription_doc.message, {'doc': new_rv})
attachments = [frappe.attach_print(new_rv.doctype, new_rv.name,
file_name=new_rv.name, print_format=print_format)]
frappe.sendmail(subscription_doc.recipients,
subject=subject, message=message, attachments=attachments)
def notify_errors(doc, doctype, party, owner, name):
recipients = get_system_managers(only_name=True)
@ -210,8 +276,11 @@ def assign_task_to_owner(name, msg, users):
@frappe.whitelist()
def make_subscription(doctype, docname):
doc = frappe.new_doc('Subscription')
reference_doc = frappe.get_doc(doctype, docname)
doc.reference_doctype = doctype
doc.reference_document = docname
doc.start_date = reference_doc.get('posting_date') or reference_doc.get('transaction_date')
return doc
@frappe.whitelist()
@ -225,4 +294,20 @@ def stop_resume_subscription(subscription, status):
doc.update_status(status)
doc.save()
return doc.status
return doc.status
def subscription_doctype_query(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""select parent from `tabDocField`
where fieldname = 'subscription'
and parent like %(txt)s
order by
if(locate(%(_txt)s, parent), locate(%(_txt)s, parent), 99999),
parent
limit %(start)s, %(page_len)s""".format(**{
'key': searchfield,
}), {
'txt': "%%%s%%" % txt,
'_txt': txt.replace("%", ""),
'start': start,
'page_len': page_len
})

View File

@ -30,7 +30,7 @@ class TestSubscription(unittest.TestCase):
new_quotation = frappe.get_doc('Quotation', new_quotation)
for fieldname in ['customer', 'company', 'order_type', 'total', 'grand_total']:
for fieldname in ['customer', 'company', 'order_type', 'total', 'net_total']:
self.assertEquals(quotation.get(fieldname), new_quotation.get(fieldname))
for fieldname in ['item_code', 'qty', 'rate', 'amount']:

View File

@ -11,7 +11,10 @@ test_records = frappe.get_test_records('Tax Rule')
class TestTaxRule(unittest.TestCase):
def setUp(self):
frappe.db.sql("delete from `tabTax Rule` where use_for_shopping_cart <> 1")
frappe.db.sql("delete from `tabTax Rule`")
def tearDown(self):
frappe.db.sql("delete from `tabTax Rule`")
def test_conflict(self):
tax_rule1 = make_tax_rule(customer= "_Test Customer",

View File

@ -84,6 +84,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
this.get_data_from_server(function () {
me.make_control();
me.create_new();
me.make();
});
},
@ -113,6 +114,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
});
this.page.add_menu_item(__("Sync Offline Invoices"), function () {
me.freeze_screen = true;
me.sync_sales_invoice()
});
@ -381,7 +383,6 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
},
setup: function () {
this.make();
this.set_primary_action();
this.party_field.$input.attr('disabled', false);
if(this.selected_row) {
@ -1340,6 +1341,12 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
this.wrapper.find('input.discount-percentage').on("change", function () {
me.frm.doc.additional_discount_percentage = flt($(this).val(), precision("additional_discount_percentage"));
if(me.frm.doc.additional_discount_percentage && me.frm.doc.discount_amount) {
// Reset discount amount
me.frm.doc.discount_amount = 0;
}
var total = me.frm.doc.grand_total
if (me.frm.doc.apply_discount_on == 'Net Total') {
@ -1347,15 +1354,15 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
}
me.frm.doc.discount_amount = flt(total * flt(me.frm.doc.additional_discount_percentage) / 100, precision("discount_amount"));
me.wrapper.find('input.discount-amount').val(me.frm.doc.discount_amount)
me.refresh();
me.wrapper.find('input.discount-amount').val(me.frm.doc.discount_amount)
});
this.wrapper.find('input.discount-amount').on("change", function () {
me.frm.doc.discount_amount = flt($(this).val(), precision("discount_amount"));
me.frm.doc.additional_discount_percentage = 0.0;
me.wrapper.find('input.discount-percentage').val(0);
me.refresh();
me.wrapper.find('input.discount-percentage').val(0);
});
},
@ -1516,6 +1523,8 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
var me = this;
this.wrapper.find(".net-total").text(format_currency(me.frm.doc.total, me.frm.doc.currency));
this.wrapper.find(".grand-total").text(format_currency(me.frm.doc.grand_total, me.frm.doc.currency));
this.wrapper.find('input.discount-percentage').val(this.frm.doc.additional_discount_percentage);
this.wrapper.find('input.discount-amount').val(this.frm.doc.discount_amount);
},
set_primary_action: function () {
@ -1684,6 +1693,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
set_interval_for_si_sync: function () {
var me = this;
setInterval(function () {
me.freeze_screen = false;
me.sync_sales_invoice()
}, 60000)
},
@ -1697,9 +1707,12 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
this.freeze = this.customer_doc.display
}
freeze_screen = this.freeze_screen || false;
if ((this.si_docs.length || this.email_queue_list || this.customers_list) && !this.freeze) {
frappe.call({
method: "erpnext.accounts.doctype.sales_invoice.pos.make_invoice",
freeze: freeze_screen,
args: {
doc_list: me.si_docs,
email_queue_list: me.email_queue_list,

View File

@ -68,16 +68,18 @@ def set_address_details(out, party, party_type, doctype=None, company=None):
billing_address_field = "customer_address" if party_type == "Lead" \
else party_type.lower() + "_address"
out[billing_address_field] = get_default_address(party_type, party.name)
out.update(get_fetch_values(doctype, billing_address_field, out[billing_address_field]))
if doctype:
out.update(get_fetch_values(doctype, billing_address_field, out[billing_address_field]))
# address display
out.address_display = get_address_display(out[billing_address_field])
# shipping address
if party_type in ["Customer", "Lead"]:
out.shipping_address_name = get_default_address(party_type, party.name, 'is_shipping_address')
out.shipping_address_name = get_party_shipping_address(party_type, party.name)
out.shipping_address = get_address_display(out["shipping_address_name"])
out.update(get_fetch_values(doctype, 'shipping_address_name', out.shipping_address_name))
if doctype:
out.update(get_fetch_values(doctype, 'shipping_address_name', out.shipping_address_name))
if doctype and doctype in ['Delivery Note', 'Sales Invoice']:
out.update(get_company_address(company))
@ -320,11 +322,15 @@ def set_taxes(party, party_type, posting_date, company, customer_group=None, sup
from erpnext.accounts.doctype.tax_rule.tax_rule import get_tax_template, get_party_details
args = {
party_type.lower(): party,
"customer_group": customer_group,
"supplier_type": supplier_type,
"company": company
}
if customer_group:
args['customer_group'] = customer_group
if supplier_type:
args['supplier_type'] = supplier_type
if billing_address or shipping_address:
args.update(get_party_details(party, party_type, {"billing_address": billing_address, \
"shipping_address": shipping_address }))
@ -412,3 +418,32 @@ def get_dashboard_info(party_type, party):
info["total_unpaid"] = -1 * info["total_unpaid"]
return info
def get_party_shipping_address(doctype, name):
"""
Returns an Address name (best guess) for the given doctype and name for which `address_type == 'Shipping'` is true.
and/or `is_shipping_address = 1`.
It returns an empty string if there is no matching record.
:param doctype: Party Doctype
:param name: Party name
:return: String
"""
out = frappe.db.sql(
'SELECT dl.parent '
'from `tabDynamic Link` dl join `tabAddress` ta on dl.parent=ta.name '
'where '
'dl.link_doctype=%s '
'and dl.link_name=%s '
'and dl.parenttype="Address" '
'and '
'(ta.address_type="Shipping" or ta.is_shipping_address=1) '
'order by ta.is_shipping_address desc, ta.address_type desc limit 1',
(doctype, name)
)
if out:
return out[0][0]
else:
return ''

View File

@ -44,7 +44,7 @@ class ReceivablePayableReport(object):
})
columns += [_("Age (Days)") + ":Int:80"]
self.ageing_col_idx_start = len(columns)
if not "range1" in self.filters:
@ -53,7 +53,7 @@ class ReceivablePayableReport(object):
self.filters["range2"] = "60"
if not "range3" in self.filters:
self.filters["range3"] = "90"
for label in ("0-{range1}".format(range1=self.filters["range1"]),
"{range1}-{range2}".format(range1=cint(self.filters["range1"])+ 1, range2=self.filters["range2"]),
"{range2}-{range3}".format(range2=cint(self.filters["range2"])+ 1, range3=self.filters["range3"]),
@ -74,14 +74,14 @@ class ReceivablePayableReport(object):
})
if args.get("party_type") == "Customer":
columns += [
_("Territory") + ":Link/Territory:80",
_("Territory") + ":Link/Territory:80",
_("Customer Group") + ":Link/Customer Group:120"
]
if args.get("party_type") == "Supplier":
columns += [_("Supplier Type") + ":Link/Supplier Type:80"]
columns.append(_("Remarks") + "::200")
return columns
def get_data(self, party_naming_by, args):
@ -97,13 +97,13 @@ class ReceivablePayableReport(object):
self.filters["company"] = frappe.db.get_single_value('Global Defaults', 'default_company')
company_currency = frappe.db.get_value("Company", self.filters.get("company"), "default_currency")
return_entries = self.get_return_entries(args.get("party_type"))
data = []
for gle in self.get_entries_till(self.filters.report_date, args.get("party_type")):
if self.is_receivable_or_payable(gle, dr_or_cr, future_vouchers):
outstanding_amount, credit_note_amount = self.get_outstanding_amount(gle,
outstanding_amount, credit_note_amount = self.get_outstanding_amount(gle,
self.filters.report_date, dr_or_cr, return_entries, currency_precision)
if abs(outstanding_amount) > 0.1/10**currency_precision:
row = [gle.posting_date, gle.party]
@ -179,15 +179,15 @@ class ReceivablePayableReport(object):
# entries adjusted with future vouchers
((gle.against_voucher_type, gle.against_voucher) in future_vouchers)
)
def get_return_entries(self, party_type):
doctype = "Sales Invoice" if party_type=="Customer" else "Purchase Invoice"
return [d.name for d in frappe.get_all(doctype, filters={"is_return": 1, "docstatus": 1})]
return [d.name for d in frappe.get_all(doctype, filters={"is_return": 1, "docstatus": 1})]
def get_outstanding_amount(self, gle, report_date, dr_or_cr, return_entries, currency_precision):
payment_amount, credit_note_amount = 0.0, 0.0
reverse_dr_or_cr = "credit" if dr_or_cr=="debit" else "debit"
for e in self.get_gl_entries_for(gle.party, gle.party_type, gle.voucher_type, gle.voucher_no):
if getdate(e.posting_date) <= report_date and e.name!=gle.name:
amount = flt(e.get(reverse_dr_or_cr)) - flt(e.get(dr_or_cr))
@ -195,11 +195,11 @@ class ReceivablePayableReport(object):
payment_amount += amount
else:
credit_note_amount += amount
outstanding_amount = flt((flt(gle.get(dr_or_cr)) - flt(gle.get(reverse_dr_or_cr)) \
- payment_amount - credit_note_amount), currency_precision)
credit_note_amount = flt(credit_note_amount, currency_precision)
return outstanding_amount, credit_note_amount
def get_party_name(self, party_type, party_name):
@ -207,7 +207,7 @@ class ReceivablePayableReport(object):
def get_territory(self, party_name):
return self.get_party_map("Customer").get(party_name, {}).get("territory") or ""
def get_customer_group(self, party_name):
return self.get_party_map("Customer").get(party_name, {}).get("customer_group") or ""
@ -220,7 +220,7 @@ class ReceivablePayableReport(object):
select_fields = "name, customer_name, territory, customer_group"
elif party_type == "Supplier":
select_fields = "name, supplier_name, supplier_type"
self.party_map = dict(((r.name, r) for r in frappe.db.sql("select {0} from `tab{1}`"
.format(select_fields, party_type), as_dict=True)))
@ -250,8 +250,8 @@ class ReceivablePayableReport(object):
else:
select_fields = "sum(debit) as debit, sum(credit) as credit"
self.gl_entries = frappe.db.sql("""select name, posting_date, account, party_type, party,
voucher_type, voucher_no, against_voucher_type, against_voucher,
self.gl_entries = frappe.db.sql("""select name, posting_date, account, party_type, party,
voucher_type, voucher_no, against_voucher_type, against_voucher,
account_currency, remarks, {0}
from `tabGL Entry`
where docstatus < 2 and party_type=%s and (party is not null and party != '') {1}
@ -277,13 +277,13 @@ class ReceivablePayableReport(object):
if party_type_field=="customer":
if self.filters.get("customer_group"):
lft, rgt = frappe.db.get_value("Customer Group",
lft, rgt = frappe.db.get_value("Customer Group",
self.filters.get("customer_group"), ["lft", "rgt"])
conditions.append("""party in (select name from tabCustomer
where exists(select name from `tabCustomer Group` where lft >= {0} and rgt <= {1}
conditions.append("""party in (select name from tabCustomer
where exists(select name from `tabCustomer Group` where lft >= {0} and rgt <= {1}
and name=tabCustomer.customer_group))""".format(lft, rgt))
if self.filters.get("credit_days_based_on"):
conditions.append("party in (select name from tabCustomer where credit_days_based_on=%s)")
values.append(self.filters.get("credit_days_based_on"))
@ -303,22 +303,22 @@ class ReceivablePayableReport(object):
return self.gl_entries_map.get(party, {})\
.get(against_voucher_type, {})\
.get(against_voucher, [])
def get_chart_data(self, columns, data):
ageing_columns = columns[self.ageing_col_idx_start : self.ageing_col_idx_start+4]
rows = []
for d in data:
rows.append(d[self.ageing_col_idx_start : self.ageing_col_idx_start+4])
if rows:
rows.insert(0, [[d.get("label")] for d in ageing_columns])
return {
"data": {
'rows': rows
'labels': rows
},
"chart_type": 'pie'
"type": 'percentage'
}
def execute(filters=None):

View File

@ -8,18 +8,18 @@ from frappe.utils import flt, cint
from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data)
def execute(filters=None):
period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
filters.periodicity, company=filters.company)
asset = get_data(filters.company, "Asset", "Debit", period_list,
asset = get_data(filters.company, "Asset", "Debit", period_list,
only_current_fiscal_year=False, filters=filters,
accumulated_values=filters.accumulated_values)
liability = get_data(filters.company, "Liability", "Credit", period_list,
liability = get_data(filters.company, "Liability", "Credit", period_list,
only_current_fiscal_year=False, filters=filters,
accumulated_values=filters.accumulated_values)
equity = get_data(filters.company, "Equity", "Credit", period_list,
equity = get_data(filters.company, "Equity", "Credit", period_list,
only_current_fiscal_year=False, filters=filters,
accumulated_values=filters.accumulated_values)
@ -43,17 +43,17 @@ def execute(filters=None):
unclosed[period.key] = opening_balance
if provisional_profit_loss:
provisional_profit_loss[period.key] = provisional_profit_loss[period.key] - opening_balance
unclosed["total"]=opening_balance
data.append(unclosed)
if provisional_profit_loss:
data.append(provisional_profit_loss)
if total_credit:
data.append(total_credit)
data.append(total_credit)
columns = get_columns(filters.periodicity, period_list, filters.accumulated_values, company=filters.company)
chart = get_chart_data(filters, columns, asset, liability, equity)
return columns, data, message, chart
@ -87,7 +87,7 @@ def get_provisional_profit_loss(asset, liability, equity, period_list, company):
total += flt(provisional_profit_loss[period.key])
provisional_profit_loss["total"] = total
total_row_total += flt(total_row[period.key])
total_row["total"] = total_row_total
@ -98,7 +98,7 @@ def get_provisional_profit_loss(asset, liability, equity, period_list, company):
"warn_if_negative": True,
"currency": currency
})
return provisional_profit_loss, total_row
def check_opening_balance(asset, liability, equity):
@ -111,17 +111,17 @@ def check_opening_balance(asset, liability, equity):
opening_balance -= flt(liability[0].get("opening_balance", 0), float_precision)
if equity:
opening_balance -= flt(equity[0].get("opening_balance", 0), float_precision)
opening_balance = flt(opening_balance, float_precision)
if opening_balance:
return _("Previous Financial Year is not closed"),opening_balance
return None,None
def get_chart_data(filters, columns, asset, liability, equity):
x_intervals = ['x'] + [d.get("label") for d in columns[2:]]
labels = [d.get("label") for d in columns[2:]]
asset_data, liability_data, equity_data = [], [], []
for p in columns[2:]:
if asset:
asset_data.append(asset[-2].get(p.get("fieldname")))
@ -129,23 +129,25 @@ def get_chart_data(filters, columns, asset, liability, equity):
liability_data.append(liability[-2].get(p.get("fieldname")))
if equity:
equity_data.append(equity[-2].get(p.get("fieldname")))
columns = [x_intervals]
datasets = []
if asset_data:
columns.append(["Assets"] + asset_data)
datasets.append({'title':'Assets', 'values': asset_data})
if liability_data:
columns.append(["Liabilities"] + liability_data)
datasets.append({'title':'Liabilities', 'values': liability_data})
if equity_data:
columns.append(["Equity"] + equity_data)
datasets.append({'title':'Equity', 'values': equity_data})
chart = {
"data": {
'x': 'x',
'columns': columns
'labels': labels,
'datasets': datasets
}
}
if not filters.accumulated_values:
chart["chart_type"] = "bar"
chart["type"] = "bar"
else:
chart["type"] = "line"
return chart

View File

@ -107,6 +107,8 @@ class GrossProfitGenerator(object):
def process(self):
self.grouped = {}
self.grouped_data = []
for row in self.si_list:
if self.skip_row(row, self.product_bundles):
continue
@ -150,7 +152,6 @@ class GrossProfitGenerator(object):
def get_average_rate_based_on_group_by(self):
# sum buying / selling totals for group
self.grouped_data = []
for key in self.grouped.keys():
if self.filters.get("group_by") != "Invoice":
for i, row in enumerate(self.grouped[key]):

View File

@ -8,15 +8,15 @@ from frappe.utils import flt
from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data)
def execute(filters=None):
period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
filters.periodicity, filters.accumulated_values, filters.company)
income = get_data(filters.company, "Income", "Credit", period_list, filters = filters,
accumulated_values=filters.accumulated_values,
accumulated_values=filters.accumulated_values,
ignore_closing_entries=True, ignore_accumulated_values_for_fy= True)
expense = get_data(filters.company, "Expense", "Debit", period_list, filters=filters,
accumulated_values=filters.accumulated_values,
accumulated_values=filters.accumulated_values,
ignore_closing_entries=True, ignore_accumulated_values_for_fy= True)
net_profit_loss = get_net_profit_loss(income, expense, period_list, filters.company)
@ -61,7 +61,7 @@ def get_net_profit_loss(income, expense, period_list, company):
def get_chart_data(filters, columns, income, expense, net_profit_loss):
x_intervals = ['x'] + [d.get("label") for d in columns[2:]]
labels = [d.get("label") for d in columns[2:]]
income_data, expense_data, net_profit = [], [], []
@ -73,27 +73,24 @@ def get_chart_data(filters, columns, income, expense, net_profit_loss):
if net_profit_loss:
net_profit.append(net_profit_loss.get(p.get("fieldname")))
columns = [x_intervals]
datasets = []
if income_data:
columns.append(["Income"] + income_data)
datasets.append({'title': 'Income', 'values': income_data})
if expense_data:
columns.append(["Expense"] + expense_data)
datasets.append({'title': 'Expense', 'values': expense_data})
if net_profit:
columns.append(["Net Profit/Loss"] + net_profit)
datasets.append({'title': 'Net Profit/Loss', 'values': net_profit})
chart = {
"data": {
'x': 'x',
'columns': columns,
'colors': {
'Income': '#5E64FF',
'Expense': '#b8c2cc',
'Net Profit/Loss': '#ff5858'
}
'labels': labels,
'datasets': datasets
}
}
if not filters.accumulated_values:
chart["chart_type"] = "bar"
chart["type"] = "bar"
else:
chart["type"] = "line"
return chart

View File

@ -0,0 +1,84 @@
import unittest
from erpnext.accounts.party import get_party_shipping_address
from frappe.test_runner import make_test_objects
class TestUtils(unittest.TestCase):
@classmethod
def setUpClass(cls):
super(TestUtils, cls).setUpClass()
make_test_objects('Address', ADDRESS_RECORDS)
def test_get_party_shipping_address(self):
address = get_party_shipping_address('Customer', '_Test Customer 1')
self.assertEqual(address, '_Test Billing Address 2 Title-Billing')
def test_get_party_shipping_address2(self):
address = get_party_shipping_address('Customer', '_Test Customer 2')
self.assertEqual(address, '_Test Shipping Address 2 Title-Shipping')
ADDRESS_RECORDS = [
{
"doctype": "Address",
"address_type": "Billing",
"address_line1": "Address line 1",
"address_title": "_Test Billing Address Title",
"city": "Lagos",
"country": "Nigeria",
"links": [
{
"link_doctype": "Customer",
"link_name": "_Test Customer 2",
"doctype": "Dynamic Link"
}
]
},
{
"doctype": "Address",
"address_type": "Shipping",
"address_line1": "Address line 2",
"address_title": "_Test Shipping Address 1 Title",
"city": "Lagos",
"country": "Nigeria",
"links": [
{
"link_doctype": "Customer",
"link_name": "_Test Customer 2",
"doctype": "Dynamic Link"
}
]
},
{
"doctype": "Address",
"address_type": "Shipping",
"address_line1": "Address line 3",
"address_title": "_Test Shipping Address 2 Title",
"city": "Lagos",
"country": "Nigeria",
"is_shipping_address": "1",
"links": [
{
"link_doctype": "Customer",
"link_name": "_Test Customer 2",
"doctype": "Dynamic Link"
}
]
},
{
"doctype": "Address",
"address_type": "Billing",
"address_line1": "Address line 4",
"address_title": "_Test Billing Address 2 Title",
"city": "Lagos",
"country": "Nigeria",
"is_shipping_address": "1",
"links": [
{
"link_doctype": "Customer",
"link_name": "_Test Customer 1",
"doctype": "Dynamic Link"
}
]
}
]

View File

@ -15,6 +15,8 @@ frappe.ui.form.on("Purchase Order", {
},
onload: function(frm) {
set_schedule_date(frm);
erpnext.queries.setup_queries(frm, "Warehouse", function() {
return erpnext.queries.warehouse(frm.doc);
});
@ -24,6 +26,29 @@ frappe.ui.form.on("Purchase Order", {
},
});
frappe.ui.form.on("Purchase Order Item", {
item_code: function(frm) {
frappe.call({
method: "get_last_purchase_rate",
doc: frm.doc,
callback: function(r, rt) {
frm.trigger('calculate_taxes_and_totals');
}
})
},
schedule_date: function(frm, cdt, cdn) {
var row = locals[cdt][cdn];
if (row.schedule_date) {
if(!frm.doc.schedule_date) {
erpnext.utils.copy_value_in_all_row(frm.doc, cdt, cdn, "items", "schedule_date");
} else {
set_schedule_date(frm);
}
}
}
});
erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend({
refresh: function(doc, cdt, cdn) {
var me = this;
@ -107,12 +132,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
},
validate: function() {
// set default schedule date as today if missing.
(this.frm.doc.items || []).forEach(function(d) {
if(!d.schedule_date) {
d.schedule_date = frappe.datetime.nowdate();
}
})
set_schedule_date(this.frm);
},
make_stock_entry: function() {
@ -201,7 +221,12 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
items_add: function(doc, cdt, cdn) {
var row = frappe.get_doc(cdt, cdn);
this.frm.script_manager.copy_from_first_row("items", row, ["schedule_date"]);
if(doc.schedule_date) {
row.schedule_date = doc.schedule_date;
refresh_field("schedule_date", cdn, "items");
} else {
this.frm.script_manager.copy_from_first_row("items", row, ["schedule_date"]);
}
},
unclose_purchase_order: function(){
@ -225,8 +250,15 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
cur_frm.cscript.calculate_taxes_and_totals();
}
})
}
},
items_on_form_rendered: function() {
set_schedule_date(this.frm);
},
schedule_date: function() {
set_schedule_date(this.frm);
}
});
// for backward compatibility: combine new and previous states
@ -268,8 +300,10 @@ cur_frm.cscript.on_submit = function(doc, cdt, cdn) {
}
}
cur_frm.cscript.schedule_date = function(doc, cdt, cdn) {
erpnext.utils.copy_value_in_all_row(doc, cdt, cdn, "items", "schedule_date");
function set_schedule_date(frm) {
if(frm.doc.schedule_date){
erpnext.utils.copy_value_in_all_row(frm.doc, frm.doc.doctype, frm.doc.name, "items", "schedule_date");
}
}
frappe.provide("erpnext.buying");

View File

@ -291,9 +291,40 @@
"search_index": 1,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "schedule_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Reqd By Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -1206,37 +1237,6 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.docstatus===0 && (doc.items && doc.items.length)",
"fieldname": "get_last_purchase_rate",
"fieldtype": "Button",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Get last purchase rate",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@ -2948,418 +2948,13 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "subscription",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Subscription",
"length": 0,
"no_copy": 1,
"options": "Subscription",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"collapsible_depends_on": "is_recurring",
"columns": 0,
"depends_on": "eval:doc.docstatus<2 && !doc.__islocal",
"fieldname": "recurring_order",
"fieldtype": "Section Break",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Recurring Order",
"length": 0,
"no_copy": 0,
"options": "fa fa-time",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Settings",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.docstatus<2",
"description": "",
"fieldname": "is_recurring",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Is Recurring",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "is_recurring",
"description": "",
"fieldname": "recurring_id",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Reference Document",
"length": 0,
"no_copy": 1,
"options": "Purchase Order",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"description": "",
"fieldname": "recurring_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Frequency",
"length": 0,
"no_copy": 1,
"options": "Monthly\nQuarterly\nHalf-yearly\nYearly",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"description": "",
"fieldname": "repeat_on_day_of_month",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Repeat on Day of Month",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"description": "",
"fieldname": "end_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "End Date",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"fieldname": "submit_on_creation",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Submit on creation",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"description": "",
"fieldname": "notify_by_email",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Notify by email",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.notify_by_email && doc.recurring_id === doc.name",
"description": "",
"fieldname": "notification_email_address",
"fieldtype": "Code",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Notification Email Address",
"length": 0,
"no_copy": 1,
"options": "Email",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.notify_by_email && doc.recurring_id === doc.name",
"fieldname": "recurring_print_format",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Recurring Print Format",
"length": 0,
"no_copy": 0,
"options": "Print Format",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break83",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "This Document",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "is_recurring",
"depends_on": "",
"description": "",
"fieldname": "from_date",
"fieldtype": "Date",
@ -3390,7 +2985,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "is_recurring",
"depends_on": "",
"description": "",
"fieldname": "to_date",
"fieldtype": "Date",
@ -3421,10 +3016,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "is_recurring",
"description": "",
"fieldname": "next_date",
"fieldtype": "Date",
"fieldname": "column_break_97",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@ -3432,11 +3025,11 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Next Date",
"length": 0,
"no_copy": 1,
"no_copy": 0,
"permlevel": 0,
"print_hide": 1,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
@ -3445,6 +3038,37 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "subscription",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Subscription",
"length": 0,
"no_copy": 1,
"options": "Subscription",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
@ -3458,7 +3082,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-09-19 11:22:30.190589",
"modified": "2017-10-24 12:52:11.272306",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",

View File

@ -20,8 +20,8 @@ form_grid_templates = {
}
class PurchaseOrder(BuyingController):
def __init__(self, arg1, arg2=None):
super(PurchaseOrder, self).__init__(arg1, arg2)
def __init__(self, *args, **kwargs):
super(PurchaseOrder, self).__init__(*args, **kwargs)
self.status_updater = [{
'source_dt': 'Purchase Order Item',
'target_dt': 'Material Request Item',
@ -41,6 +41,7 @@ class PurchaseOrder(BuyingController):
self.set_status()
self.validate_supplier()
self.validate_schedule_date()
validate_for_items(self)
self.check_for_closed_status()
@ -116,14 +117,13 @@ class PurchaseOrder(BuyingController):
d.discount_percentage = last_purchase_details['discount_percentage']
d.base_rate = last_purchase_details['base_rate'] * (flt(d.conversion_factor) or 1.0)
d.price_list_rate = d.base_price_list_rate / conversion_rate
d.rate = d.base_rate / conversion_rate
d.last_purchase_rate = d.base_rate / conversion_rate
else:
msgprint(_("Last purchase rate not found"))
item_last_purchase_rate = frappe.db.get_value("Item", d.item_code, "last_purchase_rate")
if item_last_purchase_rate:
d.base_price_list_rate = d.base_rate = d.price_list_rate \
= d.rate = item_last_purchase_rate
= d.last_purchase_rate = item_last_purchase_rate
# Check for Closed status
def check_for_closed_status(self):

View File

@ -118,6 +118,7 @@ class TestPurchaseOrder(unittest.TestCase):
"company": "_Test Company",
"supplier" : "_Test Supplier",
"is_subcontracted" : "No",
"schedule_date": add_days(nowdate(), 1),
"currency" : frappe.db.get_value("Company", "_Test Company", "default_currency"),
"conversion_factor" : 1,
"items" : get_same_items(),
@ -149,6 +150,7 @@ def create_purchase_order(**args):
if args.transaction_date:
po.transaction_date = args.transaction_date
po.schedule_date = add_days(nowdate(), 1)
po.company = args.company or "_Test Company"
po.supplier = args.customer or "_Test Supplier"
po.is_subcontracted = args.is_subcontracted or "No"

View File

@ -30,7 +30,8 @@
],
"supplier": "_Test Supplier",
"supplier_name": "_Test Supplier",
"transaction_date": "2013-02-12"
"transaction_date": "2013-02-12",
"schedule_date": "2013-02-13"
},
{
"advance_paid": 0.0,
@ -63,6 +64,7 @@
],
"supplier": "_Test Supplier",
"supplier_name": "_Test Supplier",
"transaction_date": "2013-02-12"
"transaction_date": "2013-02-12",
"schedule_date": "2013-02-13"
}
]

View File

@ -1,7 +1,7 @@
QUnit.module('Buying');
QUnit.test("test: purchase order", function(assert) {
assert.expect(11);
assert.expect(16);
let done = assert.async();
frappe.run_serially([
@ -13,12 +13,21 @@ QUnit.test("test: purchase order", function(assert) {
{items: [
[
{"item_code": 'Test Product 4'},
{"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
{"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 2)},
{"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)},
{"qty": 5},
{"uom": 'Unit'},
{"rate": 100},
{"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))}
],
[
{"item_code": 'Test Product 1'},
{"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
{"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)},
{"qty": 2},
{"uom": 'Unit'},
{"rate": 100},
{"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))}
]
]},
@ -30,14 +39,20 @@ QUnit.test("test: purchase order", function(assert) {
() => {
// Get supplier details
assert.ok(cur_frm.doc.supplier_name == 'Test Supplier', "Supplier name correct");
assert.ok($('div.control-value.like-disabled-input.for-description').text().includes('Contact 3'), "Contact display correct");
assert.ok(cur_frm.doc.schedule_date == frappe.datetime.add_days(frappe.datetime.now_date(), 1), "Schedule Date correct");
assert.ok(cur_frm.doc.contact_email == 'test@supplier.com', "Contact email correct");
// Get item details
assert.ok(cur_frm.doc.items[0].item_name == 'Test Product 4', "Item name correct");
assert.ok(cur_frm.doc.items[0].description == 'Test Product 4', "Description correct");
assert.ok(cur_frm.doc.items[0].qty == 5, "Quantity correct");
assert.ok(cur_frm.doc.items[0].schedule_date == frappe.datetime.add_days(frappe.datetime.now_date(), 2), "Schedule Date correct");
assert.ok(cur_frm.doc.items[1].item_name == 'Test Product 1', "Item name correct");
assert.ok(cur_frm.doc.items[1].description == 'Test Product 1', "Description correct");
assert.ok(cur_frm.doc.items[1].qty == 2, "Quantity correct");
assert.ok(cur_frm.doc.items[1].schedule_date == cur_frm.doc.schedule_date, "Schedule Date correct");
// Calculate total
assert.ok(cur_frm.doc.total == 500, "Total correct");
assert.ok(cur_frm.doc.total == 700, "Total correct");
// Get terms
assert.ok(cur_frm.doc.terms == 'This is a term.', "Terms correct");
},
@ -54,7 +69,7 @@ QUnit.test("test: purchase order", function(assert) {
() => frappe.tests.click_button('Submit'),
() => frappe.tests.click_button('Yes'),
() => frappe.timeout(0.3),
() => frappe.timeout(1),
() => {
assert.ok(cur_frm.doc.status == 'To Receive and Bill', "Submitted successfully");

View File

@ -0,0 +1,99 @@
QUnit.module('Buying');
QUnit.test("test: purchase order with last purchase rate", function(assert) {
assert.expect(5);
let done = assert.async();
frappe.run_serially([
() => {
return frappe.tests.make('Purchase Order', [
{supplier: 'Test Supplier'},
{is_subcontracted: 'No'},
{currency: 'INR'},
{items: [
[
{"item_code": 'Test Product 4'},
{"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
{"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)},
{"qty": 1},
{"rate": 800},
{"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))}
],
[
{"item_code": 'Test Product 1'},
{"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
{"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)},
{"qty": 1},
{"rate": 400},
{"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))}
]
]}
]);
},
() => {
// Get item details
assert.ok(cur_frm.doc.items[0].item_name == 'Test Product 4', "Item 1 name correct");
assert.ok(cur_frm.doc.items[1].item_name == 'Test Product 1', "Item 2 name correct");
},
() => frappe.timeout(1),
() => frappe.tests.click_button('Submit'),
() => frappe.tests.click_button('Yes'),
() => frappe.timeout(3),
() => frappe.tests.click_button('Close'),
() => frappe.timeout(1),
() => {
return frappe.tests.make('Purchase Order', [
{supplier: 'Test Supplier'},
{is_subcontracted: 'No'},
{currency: 'INR'},
{items: [
[
{"item_code": 'Test Product 4'},
{"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
{"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)},
{"qty": 1},
{"rate": 600},
{"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))}
],
[
{"item_code": 'Test Product 1'},
{"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
{"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)},
{"qty": 1},
{"rate": 200},
{"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))}
]
]}
]);
},
() => frappe.timeout(2),
// Get the last purchase rate of items
() => {
assert.ok(cur_frm.doc.items[0].last_purchase_rate == 800, "Last purchase rate of item 1 correct");
},
() => {
assert.ok(cur_frm.doc.items[1].last_purchase_rate == 400, "Last purchase rate of item 2 correct");
},
() => frappe.tests.click_button('Submit'),
() => frappe.tests.click_button('Yes'),
() => frappe.timeout(3),
() => frappe.tests.click_button('Close'),
() => frappe.timeout(1),
() => {
assert.ok(cur_frm.doc.status == 'To Receive and Bill', "Submitted successfully");
},
() => done()
]);
});

View File

@ -11,6 +11,7 @@ QUnit.test("test: purchase order with taxes and charges", function(assert) {
{is_subcontracted: 'No'},
{buying_price_list: 'Test-Buying-USD'},
{currency: 'USD'},
{"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
{items: [
[
{"item_code": 'Test Product 4'},

View File

@ -140,7 +140,7 @@
"allow_on_submit": 1,
"bold": 1,
"collapsible": 0,
"columns": 0,
"columns": 2,
"fieldname": "schedule_date",
"fieldtype": "Date",
"hidden": 0,
@ -148,7 +148,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Reqd By Date",
"length": 0,
@ -382,7 +382,7 @@
"allow_on_submit": 0,
"bold": 1,
"collapsible": 0,
"columns": 2,
"columns": 1,
"fieldname": "qty",
"fieldtype": "Float",
"hidden": 0,
@ -655,6 +655,37 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "last_purchase_rate",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Last Purchase Rate",
"length": 0,
"no_copy": 0,
"options": "currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@ -718,7 +749,7 @@
"allow_on_submit": 0,
"bold": 1,
"collapsible": 0,
"columns": 3,
"columns": 2,
"fieldname": "rate",
"fieldtype": "Currency",
"hidden": 0,
@ -1714,7 +1745,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2017-08-02 22:15:47.411235",
"modified": "2017-10-05 19:47:12.433095",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",

View File

@ -816,7 +816,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-07-21 14:06:46.309322",
"modified": "2017-10-17 17:27:06.281494",
"modified_by": "Administrator",
"module": "Buying",
"name": "Request for Quotation",
@ -903,26 +903,6 @@
"submit": 0,
"write": 0
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Supplier",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
},
{
"amend": 0,
"apply_user_permissions": 0,

View File

@ -138,7 +138,7 @@ class RequestforQuotation(BuyingController):
'user_fullname': full_name
}
subject = _("Request for Quotation")
subject = _("Request for Quotation: {0}".format(self.name))
template = "templates/emails/request_for_quotation.html"
sender = frappe.session.user not in STANDARD_USERS and frappe.session.user or None
message = frappe.get_template(template).render(args)

View File

@ -27,6 +27,7 @@ QUnit.test("test: request_for_quotation", function(assert) {
{tc_name: 'Test Term 1'}
]);
},
() => frappe.timeout(3),
() => {
assert.ok(cur_frm.doc.transaction_date == date, "Date correct");
assert.ok(cur_frm.doc.company == cur_frm.doc.company, "Company correct");
@ -38,7 +39,7 @@ QUnit.test("test: request_for_quotation", function(assert) {
assert.ok(cur_frm.doc.message_for_supplier == 'Please supply the specified items at the best possible rates', "Reply correct");
assert.ok(cur_frm.doc.tc_name == 'Test Term 1', "Term name correct");
},
() => frappe.timeout(0.3),
() => frappe.timeout(3),
() => cur_frm.print_doc(),
() => frappe.timeout(1),
() => {
@ -65,7 +66,7 @@ QUnit.test("test: request_for_quotation", function(assert) {
assert.ok(cur_frm.doc.docstatus == 1, "Quotation request submitted");
},
() => frappe.click_button('Send Supplier Emails'),
() => frappe.timeout(4),
() => frappe.timeout(6),
() => {
assert.ok($('div.modal.fade.in > div.modal-dialog > div > div.modal-body.ui-front > div.msgprint').text().includes("Email sent to supplier Test Supplier"), "Send emails working");
},

View File

@ -56,7 +56,8 @@ QUnit.test("test: supplier", function(assert) {
() => frappe.click_button('New Contact'),
() => {
return frappe.tests.set_form_values(cur_frm, [
{first_name: "Contact 3"}
{first_name: "Contact 3"},
{email_id: "test@supplier.com"}
]);
},
() => cur_frm.save(),

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import flt
from frappe.utils import flt, nowdate, add_days
from frappe.model.mapper import get_mapped_doc
from erpnext.controllers.buying_controller import BuyingController
@ -151,5 +151,4 @@ def make_quotation(source_name, target_doc=None):
}
}, target_doc)
return doclist
return doclist

View File

@ -27,12 +27,13 @@ QUnit.test("test: supplier quotation", function(assert) {
{terms: 'This is a term'}
]);
},
() => frappe.timeout(3),
() => {
// Get Supplier details
assert.ok(cur_frm.doc.supplier == 'Test Supplier', "Supplier correct");
assert.ok(cur_frm.doc.company == cur_frm.doc.company, "Company correct");
// Get Contact details
assert.ok(cur_frm.doc.contact_display == 'Contact 3', "Conatct correct");
assert.ok(cur_frm.doc.contact_person == 'Contact 3-Test Supplier', "Conatct correct");
assert.ok(cur_frm.doc.contact_email == 'test@supplier.com', "Email correct");
// Get uom
assert.ok(cur_frm.doc.items[0].uom == 'Unit', "Multi uom correct");

View File

@ -315,11 +315,16 @@ def get_data():
"name": "Payment Gateway Account",
"description": _("Setup Gateway accounts.")
},
{
"type": "doctype",
"name": "POS Settings",
"description": _("Setup mode of POS (Online / Offline)")
},
{
"type": "doctype",
"name": "POS Profile",
"label": _("Point-of-Sale Profile"),
"description": _("Rules to calculate shipping amount for a sale")
"description": _("Setup default values for POS Invoices")
},
{
"type": "doctype",

View File

@ -1,3 +1,5 @@
# coding=utf-8
from __future__ import unicode_literals
from frappe import _
@ -127,7 +129,6 @@ def get_data():
{
"module_name": "POS",
"color": "#589494",
"icon": "fa fa-th",
"icon": "octicon octicon-credit-card",
"type": "page",
"link": "pos",
@ -267,6 +268,30 @@ def get_data():
"color": "#FF888B",
"icon": "octicon octicon-plus",
"type": "module",
"label": _("Healthcare")
"label": _("Healthcare"),
},
{
"module_name": "Hub",
"color": "#009248",
"icon": "/assets/erpnext/images/hub_logo.svg",
"type": "page",
"link": "hub",
"label": _("Hub")
},
{
"module_name": "Data Import Tool",
"color": "#7f8c8d",
"icon": "octicon octicon-circuit-board",
"type": "page",
"link": "data-import-tool",
"label": _("Data Import Tool")
},
{
"module_name": "Restaurant",
"color": "#EA81E8",
"icon": "🍔",
"_doctype": "Restaurant",
"link": "List/Restaurant",
"label": _("Restaurant")
}
]

View File

@ -176,6 +176,10 @@ def get_data():
{
"label": _("Training"),
"items": [
{
"type": "doctype",
"name": "Training Program"
},
{
"type": "doctype",
"name": "Training Event"

View File

@ -0,0 +1,24 @@
from __future__ import unicode_literals
from frappe import _
def get_data():
return [
{
"label": _("Setup"),
"items": [
{
"type": "doctype",
"name": "Hub Settings"
},
]
},
{
"label": _("Hub"),
"items": [
{
"type": "page",
"name": "hub"
},
]
},
]

View File

@ -123,6 +123,12 @@ def get_data():
"is_query_report": True,
"name": "BOM Search",
"doctype": "BOM"
},
{
"type": "report",
"is_query_report": True,
"name": "BOM Stock Report",
"doctype": "BOM"
}
]
},

View File

@ -15,8 +15,8 @@ from erpnext.exceptions import InvalidCurrency
force_item_fields = ("item_group", "barcode", "brand", "stock_uom")
class AccountsController(TransactionBase):
def __init__(self, arg1, arg2=None):
super(AccountsController, self).__init__(arg1, arg2)
def __init__(self, *args, **kwargs):
super(AccountsController, self).__init__(*args, **kwargs)
@property
def company_currency(self):
@ -83,6 +83,9 @@ class AccountsController(TransactionBase):
self.set(fieldname, today())
break
# set taxes table if missing from `taxes_and_charges`
self.set_taxes()
def calculate_taxes_and_totals(self):
from erpnext.controllers.taxes_and_totals import calculate_taxes_and_totals
calculate_taxes_and_totals(self)
@ -187,9 +190,6 @@ class AccountsController(TransactionBase):
if stock_qty != len(get_serial_nos(item.get('serial_no'))):
item.set(fieldname, value)
elif fieldname == "conversion_factor" and not item.get("conversion_factor"):
item.set(fieldname, value)
if ret.get("pricing_rule"):
# if user changed the discount percentage then set user's discount percentage ?
item.set("discount_percentage", ret.get("discount_percentage"))
@ -262,9 +262,9 @@ class AccountsController(TransactionBase):
if not account_currency:
account_currency = get_account_currency(gl_dict.account)
if gl_dict.account and self.doctype not in ["Journal Entry",
if gl_dict.account and self.doctype not in ["Journal Entry",
"Period Closing Voucher", "Payment Entry"]:
self.validate_account_currency(gl_dict.account, account_currency)
set_balance_in_account_currency(gl_dict, account_currency, self.get("conversion_rate"), self.company_currency)

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _, msgprint
from frappe.utils import flt,cint, cstr
from frappe.utils import flt,cint, cstr, getdate
from erpnext.accounts.party import get_party_details
from erpnext.stock.get_item_details import get_conversion_factor
@ -61,7 +61,7 @@ class BuyingController(StockController):
# set contact and address details for supplier, if they are not mentioned
if getattr(self, "supplier", None):
self.update_if_missing(get_party_details(self.supplier, party_type="Supplier", ignore_permissions=self.flags.ignore_permissions))
self.update_if_missing(get_party_details(self.supplier, party_type="Supplier", ignore_permissions=self.flags.ignore_permissions, doctype=self.doctype, company=self.company))
self.set_missing_item_details(for_validate)
@ -408,3 +408,16 @@ class BuyingController(StockController):
"actual_qty": -1*flt(d.consumed_qty),
}))
def validate_schedule_date(self):
if not self.schedule_date:
self.schedule_date = min([d.schedule_date for d in self.get("items")])
if self.schedule_date:
for d in self.get('items'):
if not d.schedule_date:
d.schedule_date = self.schedule_date
if d.schedule_date and getdate(d.schedule_date) < getdate(self.transaction_date):
frappe.throw(_("Expected Date cannot be before Transaction Date"))
else:
frappe.throw(_("Please enter Schedule Date"))

View File

@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import cstr, flt
import json
import json, copy
class ItemVariantExistsError(frappe.ValidationError): pass
class InvalidItemAttributeValueError(frappe.ValidationError): pass
@ -175,29 +175,42 @@ def copy_attributes_to_variant(item, variant):
# copy non no-copy fields
exclude_fields = ["naming_series", "item_code", "item_name", "show_in_website",
"show_variant_in_website", "opening_stock", "variant_of", "valuation_rate", "variant_based_on"]
"show_variant_in_website", "opening_stock", "variant_of", "valuation_rate"]
if item.variant_based_on=='Manufacturer':
# don't copy manufacturer values if based on part no
exclude_fields += ['manufacturer', 'manufacturer_part_no']
allow_fields = [d.field_name for d in frappe.get_all("Variant Field", fields = ['field_name'])]
if "variant_based_on" not in allow_fields:
allow_fields.append("variant_based_on")
for field in item.meta.fields:
# "Table" is part of `no_value_field` but we shouldn't ignore tables
if (field.reqd or field.fieldname in allow_fields) and field.fieldname not in exclude_fields:
if variant.get(field.fieldname) != item.get(field.fieldname):
variant.set(field.fieldname, item.get(field.fieldname))
if field.fieldtype == "Table":
variant.set(field.fieldname, [])
for d in item.get(field.fieldname):
row = copy.deepcopy(d)
if row.get("name"):
row.name = None
variant.append(field.fieldname, row)
else:
variant.set(field.fieldname, item.get(field.fieldname))
variant.variant_of = item.name
variant.has_variants = 0
if not variant.description:
variant.description = ''
variant.description = ""
if item.variant_based_on=='Item Attribute':
if variant.attributes:
variant.description += "\n"
attributes_description = ""
for d in variant.attributes:
variant.description += "<p>" + d.attribute + ": " + cstr(d.attribute_value) + "</p>"
attributes_description += "<div>" + d.attribute + ": " + cstr(d.attribute_value) + "</div>"
if attributes_description not in variant.description:
variant.description += attributes_description
def make_variant_item_code(template_item_code, template_item_name, variant):
"""Uses template's item code and abbreviations to make variant's item code"""

View File

@ -165,6 +165,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
and (tabItem.`{key}` LIKE %(txt)s
or tabItem.item_group LIKE %(txt)s
or tabItem.item_name LIKE %(txt)s
or tabItem.barcode LIKE %(txt)s
or tabItem.description LIKE %(txt)s)
{fcond} {mcond}
order by
@ -172,7 +173,8 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
if(locate(%(_txt)s, item_name), locate(%(_txt)s, item_name), 99999),
idx desc,
name, item_name
limit %(start)s, %(page_len)s """.format(key=searchfield,
limit %(start)s, %(page_len)s """.format(
key=searchfield,
fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'),
mcond=get_match_cond(doctype).replace('%', '%%')),
{

View File

@ -49,7 +49,8 @@ class SellingController(StockController):
if getattr(self, "customer", None):
from erpnext.accounts.party import _get_party_details
party_details = _get_party_details(self.customer,
ignore_permissions=self.flags.ignore_permissions)
ignore_permissions=self.flags.ignore_permissions,
doctype=self.doctype, company=self.company)
if not self.meta.get_field("sales_team"):
party_details.pop("sales_team")

View File

@ -42,10 +42,28 @@ class Opportunity(TransactionBase):
if not self.with_items:
self.items = []
def make_new_lead_if_required(self):
"""Set lead against new opportunity"""
if not (self.lead or self.customer) and self.contact_email:
# check if customer is already created agains the self.contact_email
customer = frappe.db.sql("""select
distinct `tabDynamic Link`.link_name as customer
from
`tabContact`,
`tabDynamic Link`
where `tabContact`.email_id='{0}'
and
`tabContact`.name=`tabDynamic Link`.parent
and
ifnull(`tabDynamic Link`.link_name, '')<>''
and
`tabDynamic Link`.link_doctype='Customer'
""".format(self.contact_email), as_dict=True)
if customer and customer[0].customer:
self.customer = customer[0].customer
self.enquiry_from = "Customer"
return
lead_name = frappe.db.get_value("Lead", {"email_id": self.contact_email})
if not lead_name:
sender_name = get_fullname(self.contact_email)

View File

@ -4,6 +4,7 @@ from __future__ import unicode_literals
import frappe
from frappe.utils import today
from erpnext.crm.doctype.lead.lead import make_customer
from erpnext.crm.doctype.opportunity.opportunity import make_quotation
import unittest
@ -25,12 +26,45 @@ class TestOpportunity(unittest.TestCase):
doc = frappe.get_doc('Opportunity', doc.name)
self.assertEquals(doc.status, "Quotation")
def test_make_new_lead_if_required(self):
args = {
"doctype": "Opportunity",
"contact_email":"new.opportunity@example.com",
"enquiry_type": "Sales",
"with_items": 0,
"transaction_date": today()
}
# new lead should be created against the new.opportunity@example.com
opp_doc = frappe.get_doc(args).insert(ignore_permissions=True)
self.assertTrue(opp_doc.lead)
self.assertEquals(opp_doc.enquiry_from, "Lead")
self.assertEquals(frappe.db.get_value("Lead", opp_doc.lead, "email_id"),
'new.opportunity@example.com')
# create new customer and create new contact against 'new.opportunity@example.com'
customer = make_customer(opp_doc.lead).insert(ignore_permissions=True)
frappe.get_doc({
"doctype": "Contact",
"email_id": "new.opportunity@example.com",
"first_name": "_Test Opportunity Customer",
"links": [{
"link_doctype": "Customer",
"link_name": customer.name
}]
}).insert(ignore_permissions=True)
opp_doc = frappe.get_doc(args).insert(ignore_permissions=True)
self.assertTrue(opp_doc.customer)
self.assertEquals(opp_doc.enquiry_from, "Customer")
self.assertEquals(opp_doc.customer, customer.name)
def make_opportunity(**args):
args = frappe._dict(args)
opp_doc = frappe.get_doc({
"doctype": "Opportunity",
"enquiry_from": "Customer" or args.enquiry_from,
"enquiry_from": args.enquiry_from or "Customer",
"enquiry_type": "Sales",
"with_items": args.with_items or 0,
"transaction_date": today()

View File

@ -21,23 +21,13 @@ frappe.query_reports["Minutes to First Response for Opportunity"] = {
get_chart_data: function (columns, result) {
return {
data: {
x: 'Date',
columns: [
['Date'].concat($.map(result, function (d) { return d[0]; })),
['Mins to first response'].concat($.map(result, function (d) { return d[1]; }))
]
// rows: [['Date', 'Mins to first response']].concat(result)
labels: result.map(d => d[0]),
datasets: [{
title: 'Mins to first response',
values: result.map(d => d[1])
}]
},
axis: {
x: {
type: 'timeseries',
tick: {
format: frappe.ui.py_date_format
}
}
},
chart_type: 'line',
type: 'line',
}
}
}

View File

@ -103,6 +103,7 @@ def make_material_request(item_code, qty):
mr.material_request_type = "Purchase"
mr.transaction_date = frappe.flags.current_date
mr.schedule_date = frappe.utils.add_days(mr.transaction_date, 7)
mr.append("items", {
"doctype": "Material Request Item",
@ -128,6 +129,7 @@ def make_subcontract():
po = frappe.new_doc("Purchase Order")
po.is_subcontracted = "Yes"
po.supplier = get_random("Supplier")
po.schedule_date = frappe.utils.add_days(frappe.flags.current_date, 7)
item_code = get_random("Item", {"is_sub_contracted_item": 1})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

View File

@ -16,7 +16,9 @@ To set credit limit go to Customer - Master
<img class="screenshot" alt="Credit Limit" src="/docs/assets/img/accounts/credit-limit-1.png">
Go to the 'More Info section' and enter the amount in the field Credit Limit.
Go to the 'CREDIT LIMIT' section and enter the amount in the field Credit Limit.
If you leave CREDIT LIMIT as 0.00, it has no effect.
In case a need arises to allow more credit to the customer as a good-will, the
Credit Controller has access to submit order even if credit limit is crossed.
@ -26,6 +28,25 @@ has expired, go to accounting settings and make changes.
In the field Credit Controller, select the role who would be authorized to
accept orders or raise credit limits of customers.
To set credit limit at Customer Group Level go to Selling -> Customers -> Customer Group
Go to the 'CREDIT LIMIT' field and enter the amount.
If you leave CREDIT LIMIT as 0.00, it has no effect.
To set credit limit at Company level go to Account -> Company
Go to the 'ACCOUNT SETTINGS' section and enter the amount in the CREDIT LIMIT field.
If you leave CREDIT LIMIT as 0.00, it has no effect.
For 'CREDIT LIMIT' check functionality, Priority (High to Low) is as below
1) Customer
2) Customer Group
3) Company
#### Figure 2: Credit Controller

View File

@ -56,6 +56,17 @@ Integer field holds numeric value, without decimal place.
Link field is connected to another master from where it fetches data. For example, in the Quotation master, Customer is a Link field.
- Geolocation
Use Geolocation field to store GeoJSON <a href="https://tools.ietf.org/html/rfc7946#section-3.3">featurecollection</a>. Stores polygons, lines and points. Internally it uses following custom properties for identifying a circle.
```
{
"point_type": "circle",
"radius": 10.00
}
```
- Password
Password field will have decode value in it.
@ -84,4 +95,4 @@ Table will be (sort of) Link field which renders another docytpe within the curr
Text Editor is text field. It has text-formatting options. In ERPNext, this field is generally used for defining Terms and Conditions.
<!-- markdown -->
<!-- markdown -->

View File

@ -0,0 +1,9 @@
# Hospitality
ERPNext Hospitality module is designed to handle workflows for Hotels and Restaurants. This is still in early development stage.
### Manage Restaurants
The Restaurant module in ERPNext will help you manage a chain of restaurants. You can create Restaurants, Menus, Tables, Reservations and a manage Order Entry and Billing.
{index}

View File

@ -0,0 +1,4 @@
restaurant
restaurant-menu
reservations
order-entry

View File

@ -0,0 +1,26 @@
# Restaurant Order Entry
The Restaurant Order Entry is the screen where the waiters will punch in orders related to a particular table.
This screen makes it easy for the waiters in your restaurant to punch in orders from various tables.
When the guest places an order, the waiter will select the table number and add the items in the Order Entry. This can be changed until it is time for the bill. Unless you bill a table, you can change the items and they will automatically appear when you select the table ID.
To place an order you can select an item and click the enter key so that the item will be updated in the items table.
<img class="screenshot" alt="Order Entry" src="/docs/assets/img/restaurant/order-entry.png">
You can also choose items with the POS style item selector.
### Billing
When it is time for billing, you just choose the bill and you can select the customer and mode of payment. On saving, a Sales Invoice is generated and the order section becomes empty.
<img class="screenshot" alt="Order Entry" src="/docs/assets/img/restaurant/order-entry-bill.png">
### Sales Invoice
To print the invoice, you can click on the Invoice Link and print the invoice
<img class="screenshot" alt="Sales Invoice" src="/docs/assets/img/restaurant/restaurant-invoice.png">

View File

@ -0,0 +1,13 @@
# Restaurant Reservations
Once you have setup the restaurant and tables, you can start taking in reservations for your restaurant.
To take a reservation, just make a new Restaurant Reservation from the Restaurant Page and set the time, number of people and name of the guest.
<img class="screenshot" alt="Reservation" src="/docs/assets/img/restaurant/reservation.png">
### Kanban
As your guests walk in, You can also manage the reservations by making a simple Kanban board for the same.
<img class="screenshot" alt="Reservation Kanban Board" src="/docs/assets/img/restaurant/reservation-kanban.png">

View File

@ -0,0 +1,7 @@
# Restaurant Menu
For every restaurant you must set an active Restaurant Menu from which orders can be placed. You can also set the rates for each of the item for the day.
When you save the Restaurant Menu, a Price List is created for that Menu and all pricing is linked to that price list. This way you can easily control the items on offer and pricing from the menu.
<img class="screenshot" alt="Restaurant Menu" src="/docs/assets/img/restaurant/restaurant-menu.png">

View File

@ -0,0 +1,19 @@
# Restaurant
The Restaurant record represents one restaurant in your organization. To create a new Restaurant, just set the name, Company and Default Customer.
You can set a unique numbering prefix for each of your restaurants. All invoices for that restuarant will follow that numbering prefix.
If you have a default Sales Taxes and Charges Template, you can add it so that the same charge + tax will be applicable for all invoices in the restaurant.
<img class="screenshot" alt="Restaurant" src="/docs/assets/img/restaurant/restaurant.png">
After your restaurant is created, you can add Tables and Menus for that restaurant
### Adding Tables
You can add a Restaurant Table by creating a new Restaurant Table from the dashboard.
<img class="screenshot" alt="Restaurant Table" src="/docs/assets/img/restaurant/restaurant-table.png">

View File

@ -1,8 +1,13 @@
# Training
### Training Program
Create Training Program and schedule Training Events under it. It has a dashboard linked to Training Event to view which event is under the Training Program.
<img class="screenshot" alt="Employee" src="/docs/assets/img/human-resources/training_program.png">
### Training Event
Schedule seminars, workshops, conferences etc using Training Event. You can also invite your employees to attend the event using this feature.
Schedule seminars, workshops, conferences etc using Training Event linked to a Training Program. You can also invite your employees to attend the event using this feature.
<img class="screenshot" alt="Employee" src="/docs/assets/img/human-resources/training_event.png">
@ -14,11 +19,11 @@ By default the status of the employee will be 'Open'.
<img class="screenshot" alt="Employee" src="/docs/assets/img/human-resources/training_event_employee.png">
When you submit the Training Event, a notifcation will be sent to the employee notifying that the Training has been scheduled. This is sent via Email Alert "Training Scheduled". You can modifiy this Email Alert to customize the message.
When you submit the Training Event, a notification will be sent to the employee notifying that the Training has been scheduled. This is sent via Email Alert "Training Scheduled". You can modify this Email Alert to customize the message.
### Training Result
After compleation of the training Employee-wise training results can be stored based on the Feedback received from the Trainer.
After completion of the training Employee-wise training results can be stored based on the Feedback received from the Trainer.
<img class="screenshot" alt="Employee" src="/docs/assets/img/human-resources/training_result.png">

View File

@ -97,8 +97,8 @@ fiscal year.
A Cost Center is like an Account, but the only difference is that its
structure represents your business more closely than Accounts.
For example, in your Chart of Accounts, you can separate your expenses by its type
(i.e., travel, marketing, etc.). In your Chart of Cost Centers, you can separate
For example, in your Chart of Accounts, you can separate your expenses by its type
(i.e., travel, marketing, etc.). In your Chart of Cost Centers, you can separate
them by product line or business group (e.g., online sales, retail sales, etc.).
> Accounts > Chart of Cost Centers
@ -316,7 +316,7 @@ A record of the monthly salary given to an Employee.
#### Salary Structure
A template identifying all the components of an Employees' salary (earnings),
A template identifying all the components of an Employees' salary (earnings),
tax and other social security deductions.
> Human Resource > Salary and Payroll > Salary Structure

View File

@ -1,7 +1,3 @@
# Getting Started With Erpnext
<!-- Getting Started with ERPNext-->
# Getting Started with ERPNext
There are many ways to get started with ERPNext.

View File

@ -1,9 +1,5 @@
# The Champion
<!-- no-heading -->
<h1 class="white">The Champion</h1>
<img alt="Champion" class="screenshot" src="/docs/assets/img/setup/implementation-image.png">
We have seen dozens of ERP implementations over the past few years and we

View File

@ -1,13 +1,14 @@
# Student Admission
The admission process begins with filling the admission form. The Student Admission record enables to intitate your admission process for a given **Academic year**. ERPNext admission module allow you to create an admission record which can be then published on the ERPNext generate website.
The admission process begins with filling the admission form. The Student Admission record enables to initiate your admission process for a given **Academic year**. ERPNext admission module allows you to create an admission record which can be then published on the ERPNext generate website.
To create a Student Admission record go to :
**Schools** >> **Admissions** >> **Student Admission** >>
<img class="screenshot" alt="Student Applicant" src="/docs/assets/img/schools/student/student-admission.gif">
<img class="screenshot" alt="Student Applicant" src="/docs/assets/img/schools/admission/student-admission.gif">
Once an admission record is created, the age eligibility criteria can be determined for the every program. Similarly, you can also determine the application fee and naming series for every student applicant. If you keep the naming series blank then the default naming series will be applied for every student applicant.
Once a admission record is created it can be published on the website and the student can apply from the web portal itself.
The information provided in the Student Admission records will be used for the validation and creation of the Student Admission records (only if student admission link is filled there)

View File

@ -1,22 +0,0 @@
If you have a contract with the Customer where your organization gives bill to the Customer on a monthly, quarterly, half-yearly or annual basis, you can use subscription feature to make auto invoicing.
<img class="screenshot" alt="Subscription" src="{{docs_base_url}}/assets/img/subscription/subscription.png">
#### Scenario
Subscription for your hosted ERPNext account requires yearly renewal. We use Sales Invoice for generating proforma invoices. To automate proforma invoicing for renewal, we set original Sales Invoice on the subscription form. Recurring proforma invoice is created automatically just before customer's account is about to expire, and requires renewal. This recurring Proforma Invoice is also emailed automatically to the customer.
To set the subscription for the sales invoice
Goto Subscription > select base doctype "Sales Invoice" > select base docname "Invoice No" > Save
<img class="screenshot" alt="Subscription" src="{{docs_base_url}}/assets/img/subscription/subscription.gif">
**From Date and To Date**: This defines contract period with the customer.
**Repeat on Day**: If frequency is set as Monthly, then it will be day of the month on which recurring invoice will be generated.
**Notify By Email**: If you want to notify the user about auto recurring invoice.
**Print Format**: Select a print format to define document view which should be emailed to customer.
**Disabled**: It will stop to make auto recurring documents against the subscription

View File

@ -1,3 +1,4 @@
introduction
accounts
projects
schools

View File

@ -0,0 +1,6 @@
# Costo de Actividad
El costo de la actividad registra la tasa de facturación por hora y la tasa de costos de un empleado en comparación con un tipo de actividad.
El sistema hace uso de esta tasa mientras hace registros de tiempo. Se usa para Costeo de proyectos.
<img class="screenshot" alt="Activity Cost" src="/docs/assets/img/project/activity_cost.png">

View File

@ -0,0 +1,15 @@
# Tipo de Actividad
Los tipos de actividad son la lista de los diferentes tipos de actividades sobre las que se hacen registro de tiempo.
<img class="screenshot" alt="Activity Type" src="/docs/assets/img/project/activity_type.png">
Por defecto, los siguientes tipos de actividades son creados.
* Planning
* Research
* Proposal Writing
* Execution
* Communication
{next}

View File

@ -0,0 +1,3 @@
# Artículos
{index}

View File

@ -0,0 +1 @@
project-costing

View File

@ -0,0 +1,40 @@
# Costeo de proyectos
Cada proyecto tiene multiples tareas asociadas a el. Para hacer el seguimiento del costo actual de un proyecto, primeramente en términos de servicios, el usuario
tiene que crear un registro de tiempo basado en el tiempo que invirtió en una tarea del proyecto. Siguiendo los pasos de como puedes hacer el seguimiento del costo actual de un servicio usando el proyecto.
#### Tipo de actividad
Tipo de actividad es un maestro de los servicios ofrecidos por su personal. Puedes agregar un nuevo Tipo de Actividad desde:
`Project > Activity Type > New`
#### Costo de actividad
Costo de actividad es un maestro donde puedes hacer el seguimiento de los montos de facturación y costo de cada empleado, y por cada tipo de Tipo de Actividad.
<img alt="Activity Cost" class="screenshot" src="/docs/assets/img/articles/Screen Shot 2015-06-11 at 4.57.01 pm.png">
#### Registro de Tiempo
Basados en el tiempo actual invertido en una Tarea del Proyecto, El empleado va a crear un registro de tiempo.
<img alt="Time Log" class="screenshot" src="/docs/assets/img/articles/Screen Shot 2015-06-11 at 4.59.49 pm.png">
Al momento de seleccionar el Tipo de Actividad en el Registro de tiempo, el monto de Facturación y Costo del empleado va a ser traído de su respectivo registro en el master de Costo de Actividad.
<img alt="Time Log Costing" class="screenshot" src="/docs/assets/img/articles/Screen Shot 2015-06-11 at 5.00.06 pm.png">
Multiplicando esos montos con el total de número de horas en el registro de tiempo, nos da el monto de costos y Facturación para el registro de tiempo específico.
#### Costeo en Proyectos y Tareas
Basados en el total de registros de tiempos creados por una tarea en específico, su costo va a ser actualizado en el registro maestro de la tarea, o sea, en el detalle de la tarea.
<img alt="Costing in Task" class="screenshot" src="/docs/assets/img/articles/Screen Shot 2015-06-11 at 5.02.54 pm.png">
De la misma manera, el detalle del Proyecto va a actualizar su costo basado en el total de registros de tiempo a ese proyecto, y las tareas asociadas a ese proyecto.
<img alt="Costing in Project" class="screenshot" src="/docs/assets/img/articles/Screen Shot 2015-06-11 at 5.02.29 pm.png">
<!-- markdown -->

View File

@ -0,0 +1,15 @@
# Proyectos
ERPNext le ayuda en la administración de su proyecto a traves de la creacion de tareas y
poder asignarlas a diferentes personas.
Las compras y las ventas también se pueden rastrear en relación con los proyectos y
esto puede ayudar a la empresa a controlar su presupuesto, entrega y rentabilidad para un proyecto.
Los proyectos pueden ser usados para manejar los proyectos internos, trabajos de manufacturación y
planificación de servicios. Para los trabajos de servicios, los Time Sheets (hojas de tiempo) pueden ser creadas
para facturar a los clientes, en caso que el proceso de facturación se haga basado en tiempo y dinero de tareas.
### Temas
{index}

View File

@ -0,0 +1,7 @@
tasks
project
time-log-batch
activity-type
activity-cost
articles
timesheet

View File

@ -0,0 +1,110 @@
# Proyecto
El manejo de proyectos en ERPNext se hace a traves de tareas. Puedes crear un proyecto y asignar varias tareas al mismo.
<img class="screenshot" alt="Project" src="/docs/assets/img/project/project.png">
También puedes hacer el seguimiento del % completado del proyecto usando diferentes métodos.
1. Tareas Completadas
2. Progreso de tareas
3. Peso de tarea
<img class="screenshot" alt="Project" src="/docs/assets/img/project/project-percent-complete.png">
Algunos ejemplos de como el % completado es cálculado basado en tareas.
<img class="screenshot" alt="Project" src="/docs/assets/img/project/percent-complete-calc.png">
<img class="screenshot" alt="Project" src="/docs/assets/img/project/percent-complete-formula.png">
### Manejando tareas
Los proyecto pueden ser divididos en multiples tareas.
Las tareas pueden ser creadas a traves del documento de Proyecto o pueden ser creadas via [Tarea](/docs/user/manual/en/projects/tasks.html)
<img class="screenshot" alt="Project" src="/docs/assets/img/project/project_task.png">
* Para ver las tareas creadas a un proyecto click en 'Tasks'
<img class="screenshot" alt="Project - View Task" src="/docs/assets/img/project/project_view_task.png">
<img class="screenshot" alt="Project - Task List" src="/docs/assets/img/project/project_task_list.png">
* También puedes ver las tareas desde la misma vista del proyecto.
<img class="screenshot" alt="Project - Task Grid" src="/docs/assets/img/project/project_task_grid.png">
* Para agregar peso a las tareas puedes seguir los pasos siguientes
<img class="screenshot" alt="Project - Task Grid" src="/docs/assets/img/project/tasks.png">
<img class="screenshot" alt="Project - Task Grid" src="/docs/assets/img/project/task-weights.png">
### Manejando tiempo
ERPNext usa [Time Log](/docs/user/manual/en/projects/time-log.html) para hacer el seguimiento del progreso de un Proyecto.
Puedes crear registros de tiempo sobre cada Tarea.
El tiempo actual de inicio y finalización junto con el costo deben ser actualizados basados en los Registros de Tiempo.
* Para ver los Registros de Tiempo realizados a un proyecto, dar click en 'Time Logs'
<img class="screenshot" alt="Project - View Time Log" src="/docs/assets/img/project/project_view_time_log.png">
<img class="screenshot" alt="Project - Time Log List" src="/docs/assets/img/project/project_time_log_list.png">
* Puedes agregar un registro de tiempo directamente y luego asociarlo con el proyecto.
<img class="screenshot" alt="Project - Link Time Log" src="/docs/assets/img/project/project_time_log_link.png">
### Gestión de gastos
Puede reservar la [Reclamación de gastos](/docs/user/manual/en/human-resources/expense-claim.html) contra una tarea de proyecto.
El sistema actualizará el monto total de las reclamaciones de gastos en la sección de costos del proyecto.
* Para ver las reclamaciones de gastos realizadas en un proyecto, haga clic en 'Reclamaciones de gastos'
<img class="screenshot" alt="Project - View Expense Claim" src="/docs/assets/img/project/project_view_expense_claim.png">
* También puede crear un Reclamo de gastos directamente y vincularlo al Proyecto.
<img class="screenshot" alt="Project - Link Expense Claim" src="/docs/assets/img/project/project_expense_claim_link.png">
* El monto total de los Reclamos de gastos reservados contra un proyecto se muestra en 'Reclamo de gastos totales' en la Sección de Costos del proyecto
<img class="screenshot" alt="Project - Total Expense Claim" src="/docs/assets/img/project/project_total_expense_claim.png">
### Centro de Costo
Puedes crear un [Cost Center](/docs/user/manual/en/accounts/setup/cost-center.html) sobre un proyecto o usar un centro de costo existente para hacer el seguimiento de todos los gastos realizados al proyecto.
<img class="screenshot" alt="Project - Cost Center" src="/docs/assets/img/project/project_cost_center.png">
###Costeo del proyecto
La sección Costeo del proyecto le ayuda a rastrear el tiempo y los gastos incurridos en relación con el proyecto.
<img class="screenshot" alt="Project - Costing" src="/docs/assets/img/project/project_costing.png">
* La sección de cálculo de costos se actualiza según los registros de tiempo realizados.
* El margen bruto es la diferencia entre el monto total de costos y el monto total de facturación
###Facturación
Puedes crear/enlazar una [Sales Order](/docs/user/manual/en/selling/sales-order.html) a un proyecto. Una vez asociada puedes usar el módulo de ventas para facturar a un cliente sobre el proyecto.
<img class="screenshot" alt="Project - Sales Order" src="/docs/assets/img/project/project_sales_order.png">
###Gantt Chart
Un Gantt Chart muestra la planificación del proyecto.
ERPNext te provee con una vista para visualizar las tareas de forma calendarizada usando un Gantt Chart (Hoja de Gantt).
* Para visualizar el gantt chart de un proyecto, ve hasta el proyecto y dar click en 'Gantt Chart'
<img class="screenshot" alt="Project - View Gantt Chart" src="/docs/assets/img/project/project_view_gantt_chart.png">
<img class="screenshot" alt="Project - Gantt Chart" src="/docs/assets/img/project/project_gantt_chart.png">
{next}

Some files were not shown because too many files have changed in this diff Show More